一漱挚、 measure
了解measure過程前,需要先了解傳遞尺寸(寬 / 高測(cè)量值)的2個(gè)類:
- ViewGroup.LayoutParams類()
- 指定視圖View 的高度(height) 和 寬度(width)等布局參數(shù)
- MeasureSpecs 類(父視圖對(duì)子視圖的測(cè)量要求)
- 測(cè)量規(guī)格(MeasureSpec) = 測(cè)量模式(mode) + 測(cè)量大小(size),
- 通過使用二進(jìn)制,將測(cè)量模式(mode) & 測(cè)量大小(size)打包成一個(gè)int值來,并提供了打包 & 解包的方法
- 測(cè)量模式(Mode)的類型有3種:UNSPECIFIED哮笆、EXACTLY 和
AT_MOST
二、MeasureSpec的計(jì)算
/**
* 源碼分析:getChildMeasureSpec()
* 作用:根據(jù)父視圖的MeasureSpec & 布局參數(shù)LayoutParams汰扭,計(jì)算單個(gè)子View的MeasureSpec
* 注:子view的大小由父view的MeasureSpec值 和 子view的LayoutParams屬性 共同決定
**/
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
//參數(shù)說明
* @param spec 父view的詳細(xì)測(cè)量值(MeasureSpec)
* @param padding view當(dāng)前尺寸的的內(nèi)邊距和外邊距(padding,margin)
* @param childDimension 子視圖的布局參數(shù)(寬/高)
//父view的測(cè)量模式
int specMode = MeasureSpec.getMode(spec);
//父view的大小
int specSize = MeasureSpec.getSize(spec);
//通過父view計(jì)算出的子view = 父大小-邊距(父要求的大小稠肘,但子view不一定用這個(gè)值)
int size = Math.max(0, specSize - padding);
//子view想要的實(shí)際大小和模式(需要計(jì)算)
int resultSize = 0;
int resultMode = 0;
//通過父view的MeasureSpec和子view的LayoutParams確定子view的大小
// 當(dāng)父view的模式為EXACITY時(shí),父view強(qiáng)加給子view確切的值
//一般是父view設(shè)置為match_parent或者固定值的ViewGroup
switch (specMode) {
case MeasureSpec.EXACTLY:
// 當(dāng)子view的LayoutParams>0萝毛,即有確切的值
if (childDimension >= 0) {
//子view大小為子自身所賦的值项阴,模式大小為EXACTLY
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
// 當(dāng)子view的LayoutParams為MATCH_PARENT時(shí)(-1)
} else if (childDimension == LayoutParams.MATCH_PARENT) {
//子view大小為父view大小,模式為EXACTLY
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
// 當(dāng)子view的LayoutParams為WRAP_CONTENT時(shí)(-2)
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
//子view決定自己的大小笆包,但最大不能超過父view环揽,模式為AT_MOST
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// 當(dāng)父view的模式為AT_MOST時(shí),父view強(qiáng)加給子view一個(gè)最大的值庵佣。(一般是父view設(shè)置為wrap_content)
case MeasureSpec.AT_MOST:
// 道理同上
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// 當(dāng)父view的模式為UNSPECIFIED時(shí)歉胶,父容器不對(duì)view有任何限制,要多大給多大
// 多見于ListView巴粪、GridView
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
// 子view大小為子自身所賦的值
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// 因?yàn)楦竩iew為UNSPECIFIED通今,所以MATCH_PARENT的話子類大小為0
resultSize = 0;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// 因?yàn)楦竩iew為UNSPECIFIED,所以WRAP_CONTENT的話子類大小為0
resultSize = 0;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
其中的規(guī)律總結(jié):(以子View為標(biāo)準(zhǔn)肛根,橫向觀察)
三辫塌、measure過程
- View measure過程
流程 measure()->onMeasure()->setMeasureDimension()->getDefaultSize()
/**
* 源碼分析:measure()
* 定義:Measure過程的入口;屬于View.java類 & final類型派哲,即子類不能重寫此方法
* 作用:基本測(cè)量邏輯的判斷
**/
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
// 參數(shù)說明:View的寬 / 高測(cè)量規(guī)格
...
int cacheIndex = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT ? -1 : mMeasureCache.indexOfKey(key);
if (cacheIndex < 0 || sIgnoreMeasureCache) {
onMeasure(widthMeasureSpec, heightMeasureSpec);
// 計(jì)算視圖大小 ->>分析1
} else {
...
}
}
/**
* 分析1:onMeasure()
* 作用:a. 根據(jù)View寬/高的測(cè)量規(guī)格計(jì)算View的寬/高值:getDefaultSize()
* b. 存儲(chǔ)測(cè)量后的View寬 / 高:setMeasuredDimension()
**/
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 參數(shù)說明:View的寬 / 高測(cè)量規(guī)格
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
// setMeasuredDimension() :獲得View寬/高的測(cè)量值 ->>分析2
// 傳入的參數(shù)通過getDefaultSize()獲得 ->>分析3
}
/**
* 分析2:setMeasuredDimension()
* 作用:存儲(chǔ)測(cè)量后的View寬 / 高
* 注:該方法即為我們重寫onMeasure()所要實(shí)現(xiàn)的最終目的
**/
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
//參數(shù)說明:測(cè)量后子View的寬 / 高值
// 將測(cè)量后子View的寬 / 高值進(jìn)行傳遞
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}
// 由于setMeasuredDimension()的參數(shù)是從getDefaultSize()獲得的
// 下面我們繼續(xù)看getDefaultSize()的介紹
}
/**
* 分析3:getDefaultSize()
* 作用:根據(jù)View寬/高的測(cè)量規(guī)格計(jì)算View的寬/高值
**/
public static int getDefaultSize(int size, int measureSpec) {
// 參數(shù)說明:
// size:提供的默認(rèn)大小
// measureSpec:寬/高的測(cè)量規(guī)格(含模式 & 測(cè)量大辛狻)
// 設(shè)置默認(rèn)大小
int result = size;
// 獲取寬/高測(cè)量規(guī)格的模式 & 測(cè)量大小
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
// 模式為UNSPECIFIED時(shí),使用提供的默認(rèn)大小 = 參數(shù)Size
case MeasureSpec.UNSPECIFIED:
result = size;
break;
// 模式為AT_MOST,EXACTLY時(shí)狮辽,使用View測(cè)量后的寬/高值 = measureSpec中的Size
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
// 返回View的寬/高值
return result;
}
- ViewGroup measure過程
- 遍歷 測(cè)量所有子View的尺寸
- 將所有子View的尺寸進(jìn)行合并,最終得到ViewGroup父視圖的測(cè)量值
- measure->onMeasure(重寫)->measureChildren->measurechild->getChildMeasureSpec->遍歷子View測(cè)量巢寡、合并->setMeasureDimension->完成
- viewGroup沒有默認(rèn)實(shí)現(xiàn),需要根據(jù)不同的需求進(jìn)行實(shí)現(xiàn)
/**
* 根據(jù)自身的測(cè)量邏輯復(fù)寫onMeasure()喉脖,分為3步
* 1. 遍歷所有子View & 測(cè)量:measureChildren()
* 2. 合并所有子View的尺寸大小,最終得到ViewGroup父視圖的測(cè)量值(自身實(shí)現(xiàn))
* 3. 存儲(chǔ)測(cè)量后View寬/高的值:調(diào)用setMeasuredDimension()
**/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 定義存放測(cè)量后的View寬/高的變量
int widthMeasure ;
int heightMeasure ;
// 1. 遍歷所有子View & 測(cè)量(measureChildren())
// ->> 分析1
measureChildren(widthMeasureSpec, heightMeasureSpec);
// 2. 合并所有子View的尺寸大小抑月,最終得到ViewGroup父視圖的測(cè)量值
void measureCarson{
... // 自身實(shí)現(xiàn)
}
// 3. 存儲(chǔ)測(cè)量后View寬/高的值:調(diào)用setMeasuredDimension()
// 類似單一View的過程树叽,此處不作過多描述
setMeasuredDimension(widthMeasure, heightMeasure);
}
// 從上可看出:
// 復(fù)寫onMeasure()有三步,其中2步直接調(diào)用系統(tǒng)方法
// 需自身實(shí)現(xiàn)的功能實(shí)際僅為步驟2:合并所有子View的尺寸大小
/**
* 分析1:measureChildren()
* 作用:遍歷子View & 調(diào)用measureChild()進(jìn)行下一步測(cè)量
**/
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
// 參數(shù)說明:父視圖的測(cè)量規(guī)格(MeasureSpec)
final int size = mChildrenCount;
final View[] children = mChildren;
// 遍歷所有子view
for (int i = 0; i < size; ++i) {
final View child = children[i];
// 調(diào)用measureChild()進(jìn)行下一步的測(cè)量 ->>分析1
if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
measureChild(child, widthMeasureSpec, heightMeasureSpec);
}
}
}
/**
* 分析2:measureChild()
* 作用:a. 計(jì)算單個(gè)子View的MeasureSpec
* b. 測(cè)量每個(gè)子View最后的寬 / 高:調(diào)用子View的measure()
**/
protected void measureChild(View child, int parentWidthMeasureSpec,
int parentHeightMeasureSpec) {
// 1. 獲取子視圖的布局參數(shù)
final LayoutParams lp = child.getLayoutParams();
// 2. 根據(jù)父視圖的MeasureSpec & 布局參數(shù)LayoutParams谦絮,計(jì)算單個(gè)子View的MeasureSpec
// getChildMeasureSpec() 請(qǐng)看上面第2節(jié)儲(chǔ)備知識(shí)處
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,// 獲取 ChildView 的 widthMeasureSpec
mPaddingLeft + mPaddingRight, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,// 獲取 ChildView 的 heightMeasureSpec
mPaddingTop + mPaddingBottom, lp.height);
// 3. 將計(jì)算好的子View的MeasureSpec值傳入measure()题诵,進(jìn)行最后的測(cè)量
// 下面的流程即類似單一View的過程洁仗,此處不作過多描述
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
簡(jiǎn)單說一下linearlayout的onMeasure流程,
首先根據(jù)布局繪制方向走不同的邏輯
如果是垂直方向性锭,拿到view總數(shù)后遍歷子view的高度并記錄
得到所有子View的尺寸大小赠潦,最終得到ViewGroup的測(cè)量值
將得到的view寬高記錄下來
是否使用weight,如果使用,需要測(cè)量2遍草冈。
view的measure過程她奥,如果自定義viewGroup,需要重寫onMeasure(),這里主要有三個(gè)步驟,1:調(diào)用measureChilren()測(cè)量子view,2:根據(jù)邏輯把計(jì)算寬高得到父view的大小怎棱。3:存儲(chǔ)測(cè)量后父view的大小哩俭。
在measureChildren中,循環(huán)測(cè)量所有view,根據(jù)父view的寬高拳恋、mode和自身的layoutparams凡资,得到每個(gè)子view的寬高和mode后調(diào)用單個(gè)view的measure流程。
單個(gè)view的measure流程谬运,是在measure里調(diào)用onmeasure方法里計(jì)算view的寬高隙赁,并記錄。view的layout過程吩谦,也先說一下viewGroup鸳谜,完成父view的位置測(cè)量,然后在onlayout中確定子view的位置式廷,然后循環(huán)遍歷子view咐扭,走view的layout過程,view的layout過程滑废,判斷自身的位置和大小是否改變蝗肪,如果改變了就調(diào)用onlayout方法重新調(diào)整位置。
draw總結(jié)蠕趁。先說一下view的draw()流程薛闪,本身draw里面分為幾個(gè)步驟,首先畫背景俺陋,然后ondraw畫自身的內(nèi)容豁延,然后分發(fā)畫子view(因?yàn)闆]有子view,所以是空實(shí)現(xiàn))腊状,最后畫裝飾诱咏,如滾動(dòng)和前景等。而viewGroup缴挖,主要是在onDraw方法中袋狞,實(shí)現(xiàn)自身的繪制邏輯,然后dispatchDraw,遍歷子view走單獨(dú)view的繪制流程苟鸯。