本篇博客主要记录自定义布局的方法和注意事项。
(一直对自定义View感兴趣,学习后怕忘记,特此总结记录。学习View过程中,主要参考了鸿洋_大神的博客。)
【张鸿洋的博客】:http://blog.csdn.net/lmj623565791/article/details/38339817
一、自定义布局需要实现的方法
1. 首先要重写onMeasure()方法:onMeasure方法主要完成对此自定义布局尺寸的测量。
2. 然后要重写onLayout()方法:onLayout方法完成此自定义布局中childView位置的指定。
3. 要定义一个内部类,返回LayoutParams,用于确定childView支持哪些属性。
二、方法详解
1. onMeasure():
①:在定义布局XML文件时,我们要对布局控件定义两个属性。一个是android:layout_width,另一个是 android:layout_height。这些属性值可以选择match_parent、npx、或者wrap_content。
②:父控件会传递给子控件一个MeasureSpec,可以获得父控件对子控件宽高的测量模式和测量值。
当子控件属性是match_parent和npx时,测量模式是EXACTLY。
当子控件属性是wrap_content时,测量模式是AT_MOST。此时子控件的尺寸是由子控件的内容决定。宽高值不是父控件传入 的测量值,而需要在自己的onMeasure()方法中确定。
③:在系统测量和绘制View时,主要将布局解析成View树。通过getChildAt(i)可以获得对应的子控件。
onMeasure()方法中必须调用setMeasuredDimension()方法设置本自定义控件尺寸。下面是onMeasure():
//主要进行测量和确定CircleMenu的尺寸,判断是根据父控件测量的值设置,还是根据子控件的大小设置尺寸
//必须调用setMeasuredDimension()方法
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
//获得父控件传递给CircleMenu的测量值和测量模式
int widthMode= MeasureSpec.getMode(widthMeasureSpec);
int widthSize= MeasureSpec.getSize(widthMeasureSpec);
int heightMode= MeasureSpec.getMode(heightMeasureSpec);
int heightSize= MeasureSpec.getSize(heightMeasureSpec);
Log.d(CRICLEMENU_TAG, widthMode+","+widthSize+","+heightMode+","+heightSize);
//测量出所有子view的尺寸
measureChildren(widthMeasureSpec, heightMeasureSpec);
//测量出的子view宽,高
int cWidth;
int cHeight;
//布局上面两个view的宽度,下面两个view的宽度,左边两个view的高度,右边两个view的高度
int tWidth=0;
int bWidth=0;
int lHeight=0;
int rHeight=0;
//tWidth,bWidth中的最大宽度;lHeight,rHeight中的最大高度
int maxWidth=0;
int maxHeight=0;
//若是 wrap_content,获得CircleMenu的大小
//获得布局中子view的数量
int cCount= getChildCount();
Log.d(CRICLEMENU_TAG, "cCount="+cCount);
for(int i=0; i<cCount; i++){
View childview= getChildAt(i);
cWidth= childview.getMeasuredWidth();
cHeight= childview.getMeasuredHeight();
if(i==0){
tWidth+=cWidth;
lHeight+=cHeight;
Log.d(CRICLEMENU_TAG, "i="+i+", tWidth="+tWidth+", lHeight="+lHeight);
}
if (i==1) {
tWidth+=cWidth;
rHeight+=cHeight;
Log.d(CRICLEMENU_TAG, "i="+i+", tWidth="+tWidth+", rHeight="+rHeight);
}
if(i==2){
bWidth+=cWidth;
lHeight+=cHeight;
Log.d(CRICLEMENU_TAG, "i="+i+", bWidth="+bWidth+", lHeight="+lHeight);
}
if(i==3){
bWidth+=cWidth;
rHeight+=cHeight;
Log.d(CRICLEMENU_TAG, "i="+i+", bWidth="+bWidth+", rHeight="+rHeight);
}
}
//获取最大宽度,最大高度
maxWidth= Math.max(tWidth, bWidth);
maxHeight= Math.max(lHeight, rHeight);
setMeasuredDimension((widthMode==MeasureSpec.EXACTLY)?widthSize:maxWidth,
(heightMode==MeasureSpec.EXACTLY)?heightSize:maxHeight);
Log.d(CRICLEMENU_TAG, ((widthMode==MeasureSpec.EXACTLY)?widthSize:maxWidth)+","
+((heightMode==MeasureSpec.EXACTLY)?heightSize:maxHeight));
} //end测量布局尺寸结束
①此方法中必须调用childView.layout(cl,ct,cr,cb),其中四个参数是子控件相对父控件上下左右四个边的距离。
//为子view安排位置,必须调用layout()方法
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
//获得子view的个数
int cCount= getChildCount();
//定义子view的长宽,和margin
int mChildWidth=0;
int mChildHeight=0;
MarginLayoutParams mChildParams=null;
//获得子view的长宽
for(int i=0;i<cCount;i++){
View childview=getChildAt(i);
mChildWidth=childview.getMeasuredWidth();
mChildHeight=childview.getMeasuredHeight();
mChildParams=(MarginLayoutParams) childview.getLayoutParams();
//定义子view四条边与父控件的距离
int cl=0;
int ct=0;
int cr=0;
int cb=0;
switch (i) {
case 0:
cl=mChildParams.leftMargin;
ct=mChildParams.topMargin;
Log.v(CRICLEMENU_TAG, cl+","+ct);
break;
case 1:
cl=mChildParams.leftMargin+mChildWidth;
ct=mChildParams.topMargin;
Log.v(CRICLEMENU_TAG, cl+","+ct);
case 2:
cl=mChildParams.leftMargin;
ct=mChildParams.topMargin+mChildHeight;
Log.v(CRICLEMENU_TAG, cl+","+ct);
case 3:
cl=getWidth()-mChildParams.rightMargin;
ct=mChildParams.topMargin+mChildHeight;
Log.v(CRICLEMENU_TAG, cl+","+ct);
default:
break;
}
cr=cl+mChildWidth;
cb=ct+mChildHeight;
//使用layout进行子view的布局
childview.layout(cl, ct, cr, cb);
//打印出子view四个边距离父控件的距离
Log.d(CRICLEMENU_TAG, cl+","+ct+","+cr+","+cb);
}
}