以下記載為在自定義ViewGroup话侧,并向其中放入控件時(shí)的方法的理解灸芳,后期在能力提升上來(lái)后,會(huì)重新修改記錄是尔;
一、onMeasure
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
1.1
翻譯View中父類(lèi)對(duì)該方法的注釋是:
- 測(cè)量視圖及其內(nèi)容开仰,以確定測(cè)量的寬度和測(cè)量的高度,這個(gè)方法應(yīng)該由子類(lèi)覆蓋拟枚,以提供對(duì)其內(nèi)容的精確和高效的度量;
- 重寫(xiě)此方法時(shí)众弓,必須使用setMeasuredDimension(int,int)來(lái)存儲(chǔ)此視圖的測(cè)量寬度和高度恩溅,如果不這樣做,將觸發(fā)lllegalStateException異常谓娃。也可以直接調(diào)用父類(lèi)的onMeasure方法脚乡;
- 測(cè)量的基類(lèi)默認(rèn)為背景大小,出入度量允許較大的大小滨达,子類(lèi)應(yīng)該覆蓋以提供更好的內(nèi)容度量奶稠;
- 如果這個(gè)方法被重寫(xiě),那么子類(lèi)的責(zé)任就是確保被測(cè)量的高度和寬度至少是視圖的最小高度和寬度捡遍;
上面說(shuō)這么多锌订,我理解就是:自定義時(shí),子類(lèi)可復(fù)寫(xiě)此方法画株,并測(cè)量好控件的寬高辆飘,最終調(diào)用setMeasuredDimension(int width,int height)方法給定控件的寬高
這個(gè)方法最終的目的就是測(cè)量控件的寬高啦辐;
1.2
在解釋這兩個(gè)參數(shù)前,需要先說(shuō)一個(gè)類(lèi)MeasureSpec:封裝了從父級(jí)傳遞到子級(jí)的布局需求蜈项,每一項(xiàng)測(cè)量都表示對(duì)寬度和高度的要求芹关,MeasureSpec由一個(gè)尺寸和一個(gè)模式組成;
它有三種模式:
MeasureSpec的三種模式 | 解釋 |
---|---|
MeasureSpec.UNSPECIFLED | 他的父控件沒(méi)有給他任何約束 他可以是他想要的任意大小尺寸空間 |
MeasureSpec.EXACTLY | 他的父控件已經(jīng)確定了約束尺寸 不管該控件想要多大战得,都會(huì)賦予該確定的尺寸 布局屬性(MATCH_PARENT/固定值) |
MeasureSpec.AT_MOST | 子元素的大小可以達(dá)到指定的大小 布局常用(wrap_content) |
獲取模式:getMode(int)
獲取尺寸:getSize(int)
1.3
經(jīng)過(guò)1.2的解釋充边,現(xiàn)在我們?cè)賮?lái)看傳遞進(jìn)來(lái)的兩個(gè)參數(shù):
- 參數(shù)解釋
int widthMeasureSpec : 父控件給當(dāng)前控件的水平寬度和約束條件
int heightMeasureSpec: 父控件給當(dāng)前控件的垂直高度和約束條件
根據(jù)參數(shù)我們可以分別得到寬高的模式和尺寸:
//寬度模式
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
//寬度尺寸
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
//高度模式
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
//高度尺寸
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
為什么傳入的參數(shù)是int類(lèi)型?其實(shí)是MeasureSpec是View的一個(gè)內(nèi)部類(lèi)常侦,其中有一個(gè)makeMeasureSpec(int size,int mode )方法將size和mode打包成一個(gè)32位的int值浇冰,這樣可以減少內(nèi)存的分配。所以我們可以使用getMode()和getSize()獲取到父控件給與的約束和尺寸聋亡;
1.4
現(xiàn)在我們已經(jīng)知道父控件給與本控件的模式和尺寸肘习,現(xiàn)在看看,如何測(cè)量該ViewGroup中子控件的寬高坡倔;
在ViewGroup中漂佩,提供了三個(gè)關(guān)于測(cè)量子控件的方法:
/**
* 訪問(wèn)所有的子控件并測(cè)量他們,包含他們的約束條件和padding罪塔,并且跳過(guò)了狀態(tài)
*為Gone的子控件
* @param widthMeasureSpec 父控件給與本ViewGroup的寬度約束
* @param heightMeasureSpec 父控件給與本ViewGroup的高度約束
*/
protected void measureChildren(int widhtMeasureSpec,int heightMeasureSpec);
/**
*訪問(wèn)這個(gè)ViewGroup中的單個(gè)子控件投蝉,并測(cè)量他,包含了這個(gè)視圖的約束條件和padding
* @param child 要測(cè)量的子控件
* @param parentWidthMeasureSpec 父控件的寬度約束要求
* @param parentHeightMeasureSpec 父控件的高度約束要求
*/
protected void measureChild(View child,
int parentWidthMeasureSpec,int parentHeightMeasureSpec);
/**
* 測(cè)量這個(gè)子控件征堪,包含這個(gè)視圖的約束要求以及他的填充和邊緣瘩缆,
* 這個(gè)子控件必須有 MarginLayoutParams獲得是在getChildMeasureSpec中完成滴
*/
protect void measureChildWithMargins(View child,
int parentWidthMeasureSpec,int widthUsed,
int parentHeightMeasureSpec 佃蚜,int heightused);
我現(xiàn)在只使用了第一種方法 measureChildren()庸娱,其他兩種沒(méi)研究也不會(huì)用,就不說(shuō)了谐算;
當(dāng)使用measureChildren() 方法后熟尉,我們就可以獲取子控件的測(cè)量寬高了
view.getMeasuredWidth();
view.getMeasuredHeight();
下面是我在學(xué)習(xí)時(shí)用到的onMeasure中的代碼:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
Log.i(TAG, "onMeasure: widhtSize = " + widthSize + ", heigthSize = " + heightSize);
measureChildren(widthMeasureSpec,heightMeasureSpec);
int width = getMaxWidth(widthMode,widthSize);
int height = getMaxHeight(heightMode,heightSize);
setMeasuredDimension(width,height ); //確定控件的寬高大小并設(shè)置
}
public int getMaxWidth(int widthMode,int widthSize) {
int childNum = getChildCount();
int maxWidth = 0;
Log.i(TAG, "getMaxWidth: 寬度模式");
switch (widthMode) {
case MeasureSpec.AT_MOST:
Log.i(TAG, "getMaxWidth: MeasureSpec.AT_MOST 設(shè)置了最大范圍(一般是WRAP_CONTENT)");
for (int i = 0; i < childNum; i++) {
View childView = getChildAt(i);
int childMeasuredWidth = childView.getMeasuredWidth();
if (i == 0){
maxWidth += childMeasuredWidth ;
continue;
}
if (maxWidth + childMeasuredWidth + mHorizontalSpace > widthSize){
maxWidth = widthSize;
break;
}else{
maxWidth += (childMeasuredWidth + mHorizontalSpace);
}
}
break;
case MeasureSpec.EXACTLY:
Log.i(TAG, "getMaxWidth: MeasureSpec.EXACTLY 精確(一般是MATCH_PARENT和固定值)");
maxWidth = widthSize ;
break;
case MeasureSpec.UNSPECIFIED: //沒(méi)用過(guò),所以沒(méi)有寫(xiě)值
Log.i(TAG, "getMaxWidth: MeasureSpec.UNSPECIFIED ");
break;
default:
break;
}
return maxWidth;
}
public int getMaxHeight(int heightMode,int heightSize){
int height = 0 ;
Log.i(TAG, "getMaxHeight: 高度模式");
switch (heightMode){
case MeasureSpec.EXACTLY:
Log.i(TAG, "getMaxHeight: MeasureSpec.EXACTLY");
height = heightSize;
break;
case MeasureSpec.AT_MOST:
Log.i(TAG, "getMaxHeight: MeasureSpec.AT_MOST");
//自己計(jì)算想要的高度值
break;
case MeasureSpec.UNSPECIFIED: //沒(méi)用過(guò)
Log.i(TAG, "getMaxHeight: MeasureSpec.UNSPECIFIED");
break;
}
return height;
}
二洲脂、 onLayout()
為View和其子View分配一個(gè)大小和位置
自定義ViewGroup則需要實(shí)現(xiàn)從ViewGroup中的抽象方法:
ViewGroup.class:
@Override
protected abstract void onLayout(boolean changed,
int l, int t, int r, int b);
而這個(gè)方法使用@Override標(biāo)注斤儿,是其是重寫(xiě)View類(lèi)中的onLayout
View.class:
/**
* 當(dāng)這個(gè)View和其子View被分配一個(gè)大小和位置時(shí)訪問(wèn)layout。
* 帶有子類(lèi)的派生類(lèi)應(yīng)該重寫(xiě)此方法恐锦,并在每個(gè)子類(lèi)上調(diào)用布局
* @param changed 當(dāng)前View的大小和位置被改變了
* @param left 左邊位置(相對(duì)于父視圖)
* @param top 頂部位置(相對(duì)于父視圖)
* @param right 右邊位置(相對(duì)于父視圖)
* @param bottom 底部位置(相對(duì)于父視圖)
*/
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
由上面可見(jiàn)雇毫,繼承ViewGroup時(shí),實(shí)現(xiàn)的onLayout方法的參數(shù)是該控件相對(duì)于父控件的位置踩蔚,如圖理解該位置:上面是我自己畫(huà)的一個(gè)草圖,應(yīng)該看到還是很清楚的
我這里既然說(shuō)的是ViewGroup枚粘,那么肯定還需要確定其子控件的位置馅闽,這就需要我們自己給出子控件的相對(duì)于父控件的left,top,right,bottom的值,然后調(diào)用:child.layout(left,top,right,bottom);
例:
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
Log.i(TAG, "onLayout: ################################");
Log.i(TAG, "onLayout: l="+l+",t="+t+",r="+r+",b="+b);
int height = b - t; //父控件本身的高
Log.i(TAG, "onLayout: 控件的高 = " + height );
int left = getPaddingLeft();
int top = getPaddingTop();
int childNum = getChildCount();
for (int i = 0; i < childNum; i++) {
View child = getChildAt(i);
int childWidth = child.getMeasuredWidth();
int childHeight = child.getMeasuredHeight();
Log.i(TAG, "onLayout: left=" + left + ",top="+ (height/2 - childHeight/2) + ",right="+(left+childWidth) + ",bottom="+ ( height/2 + childHeight/2) );
// child.layout(left, top, left + childWidth, b);
child.layout(left, height/2 - childHeight/2, left + childWidth, height/2 + childHeight/2);
left += (childWidth + mHorizontalSpace);
}
}
本文中的代碼主卫,僅做簡(jiǎn)單的思路學(xué)習(xí)參考孽椰,還是需要以實(shí)際需求為主;