效果图:
这是建行APP的一个界面,最近在学习自定义view,所以记录一下心得。
分析:
① 因为该view里面可以盛放很多子view,所以要继承ViewGroup,而不是view。
② view中的子孩子数据是不确定的,也就是从服务器上请求下来的数据,所以我们在使用该view的时候,要进行设置数据的操作。
③ 当使用者设置数据,肯定是一组中必须要有一个text和一个ImageView这两个数据,那么这个是确定的,所以我们可以先写出子view的xml,通过遍历传递过来的数据,一个个将子view 添加到viewGroup中。
④ 将子view添加到viewGroup中,要对子view进行测量以及摆放操作。
⑤测量,也就是重写onMeasure方法,需要注意的几点:
a.只是测量自身,不会测量子孩子。
b.MeasureSpec 是由32位int组成的,前两位是数据模式Mode,剩余是数据大小Size。
MeasureSpec = size + mode
c.其中Mode分为三种:
UNSPECIFIED数值为0<<30(后30位)
EXACTLY数值为1<<30, 明确的尺寸,如XXXdp,MATCH_PARENT
AT_MOST数值为2<<30,至多为多少,如WRAP_CONTENT,若控件本身没有默认尺寸,则系统会尽可能的把空间赋予控件,为MATCH_PARENT ~~~~~《注意这点》
⑥布局排列 onLayout
确定子view的位置,左上右下,在计算的时候,会使用到角度值,注意该角度对应每一个view的变化。
onMeasure方法中常用API:
setMeasureDimension(width,height):设置控件的最终尺寸
MeasureSpec spec= MeasureSpec.makeMeasureSpec(size,mode);用于指定MeasureSpec
MeasureSpec.getSize(measureSpec);通过MeasureSpec获取size
MeasureSpec.getMode(measureSpec);通过MeasureSpec获取mode
getSuggestedMinimumWidth():获得背景图的宽度,如果没有背景,则返回值为0
····
开撸:
1·创建一个类,继承ViewGroup,实现构造:
public class MenuView extends ViewGroup {
private String TAG = "MenuView";
public MenuView(Context context) {
this(context,null);
}
public MenuView(Context context, AttributeSet attrs) {
this(context, attrs,0);
}
public MenuView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
2.xml布局中使用
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorWhite3"
android:gravity="center"
android:orientation="vertical">
<zyh.demo.view.MenuView
android:id="@+id/frg_circle_mv"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/circle_bg"/>
</LinearLayout>
3.调用者中使用
private String[] texts = new String[]{"安全", "服务", "理财",
"汇款", "账户", "信用"};
private int[] imgs = new int[]{R.drawable.home_mbank_1_normal,
R.drawable.home_mbank_2_normal, R.drawable.home_mbank_3_normal,
R.drawable.home_mbank_4_normal, R.drawable.home_mbank_5_normal,
R.drawable.home_mbank_6_normal};
.....省略部分代码......
View view = inflater.inflate(R.layout.frg_circle, null, false);
MenuView menuView = (MenuView) view.findViewById(R.id.frg_circle_mv);
//给viewGroup设置数据,当然这个数据是模拟的假数据,不过要保证这两组数据要保持一致,不然会报角标越界
menuView.setData(texts,imgs);
4.对MenuView进行数据处理
public void setData(String[] texts, int[] imgs) {
for (int i = 0; i <texts.length ; i++) {
// 遍历使用者传递过来的数据,创建一个子view视图,并转换成view。
View view = View.inflate(getContext(), R.layout.view_menu, null);
TextView textView = (TextView) view.findViewById(R.id.menu_tv);
ImageView imageView = (ImageView) view.findViewById(R.id.menu_iv);
// 设置数据
textView.setText(texts[i]);
imageView.setImageResource(imgs[i]);
//添加到viewGroup中
addView(view);
}
}
子view视图
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="10dp"
android:layout_height="10dp">
<TextView
android:id="@+id/menu_tv"
android:layout_gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="sss"
android:textColor="@color/colorWhite"/>
<ImageView
android:id="@+id/menu_iv"
android:layout_gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/home_mbank_1_normal"/>
</LinearLayout>
5.重写onLayout方法
private int d = 350; //初次定义背景的直径,后期会有改动
/**弧度*/
private int stratAngle = 0;
@Override
protected void onLayout(boolean b, int i, int i1, int i2, int i3) {
for (int j = 0; j < getChildCount(); j++) {
View childView = getChildAt(j);
// 计算子view的宽度
int childWidth = childView.getMeasuredWidth();
// 自定义控件所在的圆到子视图之间的距离。
float temp = d / 3.0f;
// 动态的计算子view的左上右下
int left = (int) (d /2 + Math.round(temp * Math.cos(Math.toRadians(stratAngle))) - childWidth/2);
int right = left + childWidth;
int top = (int) (d / 2 + Math.round(temp * Math.sin(Math.toRadians(stratAngle))) - childWidth / 2);
int buttom = top + childWidth;
// 确定view布局的位置
childView.layout(left,top,right,buttom);
//stratAngle弧度是一个累加过程,不是固定的值
stratAngle += 360 / getChildCount();
}
}
6.重写onMeasure方法,如果不重写该方法的话,会导致看不到子view,因为没有测量
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 测量父布局
//android:gravity="center"
// super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int measuredWidth = 0;
int measuredHeight = 0;
//获取测量模式及测量的大小;
int mode = MeasureSpec.getMode(widthMeasureSpec);
int size = MeasureSpec.getSize(widthMeasureSpec);
// 判断测量模式
if(mode != MeasureSpec.EXACTLY) {
// 这个是不确定的值,最大为 wrap_content
// 有没有背景?
//获取背景
int suggestedMinimumWidth = getSuggestedMinimumWidth();
if (suggestedMinimumWidth == 0) { // 没有背景图
//默认宽度就是屏幕大小
measuredHeight = measuredWidth = getDefaultWidth();
}else {
measuredHeight = measuredWidth = Math.min(suggestedMinimumWidth,getDefaultWidth());
}
}else{ // 固定的数据,match_parent 或者 xxxdp
//如果是填充父窗体,也就是屏幕的宽高的较小值,《保证在屏幕可见范围内》
// 如果是固定的数值的话,也就是size,如果用户传入大无穷的数据,也是不可见的,所以屏幕的大小是一个界限。最大不过屏幕的大小
measuredHeight = measuredWidth = Math.min(size,getDefaultWidth());
}
d = measuredHeight;
//设置控件的最终尺寸
Log.e(TAG,"measuredHeight:"+measuredHeight);
setMeasuredDimension(measuredWidth,measuredHeight); //最终目的就是它~~~~~·
//遍历所有的子孩子,进行测量
for (int i = 0; i < getChildCount(); i++) {
View childAt = getChildAt(i);
// 对子孩子指定MeasureSpec,一定要指定子孩子的MeasureSpec,否则子孩子无法测量,进而不知道子孩子的摆放位置。
int makeMeasureSpec = MeasureSpec.makeMeasureSpec(d / 5, MeasureSpec.EXACTLY);
childAt.measure(makeMeasureSpec,makeMeasureSpec);
}
}
/**
* 获取屏幕的宽高的较小值
* */
private int getDefaultWidth (){
DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
int width = displayMetrics.widthPixels;
int height = displayMetrics.heightPixels;
return Math.min(width,height);
}
这样一步步的,自定义view就实现了,明个周六,又可以休息啦~~~~·加油 !!!