本篇博客主要記錄自定義佈局的方法和注意事項。
(一直對自定義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);
}
}