Android 的繪制過(guò)程可分為3個(gè)步驟即:measure(測(cè)量)长酗、layout(布局)烁竭、draw(繪制)。
一、 measure過(guò)程
1.1 MeasureSpec
MeasureSpec 是 View 測(cè)量過(guò)程中的一個(gè)關(guān)鍵參數(shù),很大程度上決定了 View 的寬高伯复,父容器會(huì)影響 View 的MeasureSpec 的創(chuàng)建妇汗,MeasureSpec 不是唯一由 LayoutParams 決定的慈参,LayoutParams 需要和父容器一起才能決定 View 的MeasureSpec灰殴,從而進(jìn)一步確定 View 的寬高娃承,在 View 測(cè)量過(guò)程中叠荠,系統(tǒng)會(huì)將該 View 的 LayoutParams 參數(shù)在父容器的約束下轉(zhuǎn)換成對(duì)應(yīng)的 MeasureSpec ,然后再根據(jù)這個(gè) measureSpec 來(lái)測(cè)量 View 的寬高。
MeasureSpec 代表一個(gè)32位 int 值哎甲,高2位代表 SpecMode(測(cè)量模式),低30位代表 SpecSize(在某個(gè)測(cè)量模式下的規(guī)格大小)缺猛,MeasureSpec 通過(guò)將 SpecMode 和 SpecSize 打包成一個(gè) int 值來(lái)避免過(guò)多的內(nèi)存分配似忧,為了方便操作,其提供了打包和解包方法源碼如下:
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
public static final int EXACTLY = 1 << MODE_SHIFT;
public static final int AT_MOST = 2 << MODE_SHIFT;
//通過(guò)將 SpecMode 和 SpecSize 打包,獲取 MeasureSpec
public static int makeMeasureSpec(int size, int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return (size & ~MODE_MASK) | (mode & MODE_MASK);
}
}
//將 MeasureSpec 解包獲取 SpecMode
public static int getMode(int measureSpec) {
return (measureSpec & MODE_MASK);
}
//將 MeasureSpec 解包獲取 SpecSize
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
SpecMode 有三類(lèi)诫硕,每一類(lèi)都表示特殊的含義:
UNSPECIFIED 父容器不對(duì) View 有任何的限制踏兜,要多大給多大饮醇,這種情況下一般用于系統(tǒng)內(nèi)部,表示一種測(cè)量的狀態(tài)。
EXACTLY 父容器已經(jīng)檢測(cè)出 View 所需要的精確大小,這個(gè)時(shí)候 View 的最終大小就是 SpecSize 所指定的值何缓,它對(duì)應(yīng)于LayoutParams 中的 match_parent 和具體的數(shù)值這兩種模式
-
AT_MOST 父容器指定了一個(gè)可用大小即 SpecSize碌廓,View 的大小不能大于這個(gè)值跟匆,具體是什么值要看不同 View 的具體實(shí)現(xiàn)虎忌。它對(duì)應(yīng)于 LayoutParams 中的 wrap_content贮勃。
?
1.2 測(cè)量過(guò)程
對(duì)于DecorView澎剥,它的 MeasureSpec 由窗口的尺寸和其自身的 LayoutParams 來(lái)決定锡溯;對(duì)于普通 View,它的MeasureSpec 由父容器的 MeasureSpec 和自身的 LayoutParams 來(lái)共同決定哑姚。
對(duì)普通的 View 的 measure 方法的調(diào)用祭饭,是由其父容器傳遞而來(lái)的,這里先看一下 ViewGroup 的 measureChildWithMargins 方法:
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
//第一步蜻懦,獲取子 View 的 LayoutParams ,也就是我們?cè)趚ml中設(shè)置的layout_width和layout_height甜癞。
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
//第二步,獲取子 view 的 WidthMeasureSpec宛乃,根據(jù)父View的測(cè)量規(guī)格和父View自己的Padding悠咱,
//還有子View的Margin和已經(jīng)用掉的空間大姓袅尽(widthUsed),就能算出子View的MeasureSpec析既。
//其中傳入的幾個(gè)參數(shù)說(shuō)明:
//parentWidthMeasureSpec 父容器的 WidthMeasureSpec
//mPaddingLeft + mPaddingRight view 本身的 Padding 值躬贡,即內(nèi)邊距值
//lp.leftMargin + lp.rightMargin view 本身的 Margin 值,即外邊距值
//widthUsed 父容器已經(jīng)被占用空間值
// lp.width view 本身期望的寬度 with 值
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+ widthUsed, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ heightUsed, lp.height);
// 第三步眼坏,根據(jù)獲取的子 veiw 的 WidthMeasureSpec 和 HeightMeasureSpec 對(duì)子 view 進(jìn)行測(cè)量
//對(duì)子 view 進(jìn)行測(cè)量
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
看一下 getChildMeasureSpec 方法:
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
// 獲取父容器的 specMode拂玻,父容器的測(cè)量模式影響子 View 的測(cè)量模式
int specMode = MeasureSpec.getMode(spec);
// 獲取父容器的 specSize 尺寸,這個(gè)尺寸是父容器用來(lái)約束子 View 大小的
int specSize = MeasureSpec.getSize(spec);
// 父容器尺寸減掉已經(jīng)被用掉的尺寸,得到是子View的大小.
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
// Parent has imposed an exact size on us
//父View是EXACTLY的類(lèi)型.
case MeasureSpec.EXACTLY:
//如果子view是一個(gè)固定的值宰译,即在xml中設(shè)置確定的值檐蚜。
if (childDimension >= 0) {
//則大小為固定的值。
resultSize = childDimension;
//模式是EXACTLY
resultMode = MeasureSpec.EXACTLY;
}
//如果子View的類(lèi)型是MATCH_PARENT沿侈。
else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
//子view大小為父容器大小闯第。
resultSize = size;
//模式是EXACTLY
resultMode = MeasureSpec.EXACTLY;
}
//如果子view的類(lèi)型是WRAP_CONTENT。
else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
//大小是最大值是size缀拭,具體由子view自己決定咳短,最大不能超過(guò)size。
resultSize = size;
//模式是AT_MOST
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent has imposed a maximum size on us
// 父容器為 AT_MOST 最大測(cè)量模式
case MeasureSpec.AT_MOST:
//如果子view是一個(gè)固定的值蛛淋,即在xml中設(shè)置確定的值咙好。
if (childDimension >= 0) {
// Child wants a specific size... so be it
//則大小為固定的值。
resultSize = childDimension;
//模式是EXACTLY
resultMode = MeasureSpec.EXACTLY;
}
//如果子View的類(lèi)型是MATCH_PARENT褐荷。
else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size, but our size is not fixed.
// Constrain child to not be bigger than us.
//大小是最大值是size勾效,具體由子view自己決定,最大不能超過(guò)size诚卸。
resultSize = size;
//模式是AT_MOST
resultMode = MeasureSpec.AT_MOST;
}
子View的width或height為 WRAP_CONTENT
else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
//同上
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent asked to see how big we want to be
//父View是UNSPECIFIED的
case MeasureSpec.UNSPECIFIED:
//如果子view是一個(gè)固定的值葵第,即在xml中設(shè)置確定的值。
if (childDimension >= 0) {
// Child wants a specific size... let him have it
//則大小為固定的值合溺。
resultSize = childDimension;
//模式是EXACTLY
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size... find out how big it should
// be
//子 View 尺寸為 0卒密,測(cè)量模式為 UNSPECIFIED
// 父容器不對(duì) View 有任何的限制,要多大給多大
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
//子 View 尺寸為 0棠赛,測(cè)量模式為 UNSPECIFIED
// 父容器不對(duì) View 有任何的限制哮奇,要多大給多大
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
以上代碼有點(diǎn)多,但是邏輯也很清晰:
-
如果父View傳進(jìn)來(lái)的模式是EXACTLY類(lèi)型睛约,也就是父View的大小是確定的鼎俘。
- 當(dāng)當(dāng)前的View是一個(gè)固定值,也就是我們?cè)趚ml里面定義改View的width和height是固定的值的大小是的時(shí)候辩涝,它最后的大小就是View設(shè)置的值贸伐。模式是MeasureSpec.EXACTLY。
- 當(dāng)當(dāng)前View的大小是設(shè)置成match_parent的時(shí)候怔揩,則當(dāng)前View 的大小是父VIew的大小size捉邢。模式是MeasureSpec.EXACTLY脯丝。
- 當(dāng)當(dāng)前的View設(shè)置成warp_content 內(nèi)容包裹時(shí),則View的大小由view自身決定伏伐,要多大就多大宠进,但是不能超過(guò)父View的大小size,其實(shí)這時(shí)候是無(wú)法知道自己的大小藐翎,要等到child.measure(childWidthMeasureSpec, childHeightMeasureSpec) 調(diào)用到時(shí)候才知道自身的大小材蹬。模式是MeasureSpec.AT_MOST。
-
如果父View傳進(jìn)來(lái)的模式是AT_MOST類(lèi)型吝镣, 最大測(cè)量模式.
- 當(dāng)當(dāng)前的View是一個(gè)固定值堤器,也就是我們?cè)趚ml里面定義改View的width和height是固定的值的大小是的時(shí)候,它最后的大小就是View設(shè)置的值赤惊。模式是MeasureSpec.EXACTLY吼旧。
- 當(dāng)當(dāng)前View的大小是設(shè)置成match_parent的時(shí)候凰锡,則當(dāng)前View 的大小是父VIew的大小size未舟。但是父View也不知道自己的大小,所以模式是MeasureSpec.AT_MOST掂为。
- 當(dāng)當(dāng)前的View設(shè)置成warp_content 內(nèi)容包裹時(shí)裕膀,父View的大小是不確定(只知道最大只能多大),子View又是WRAP_CONTENT勇哗,那么在子View的Content沒(méi)算出大小之前昼扛,子View的大小最大就是父View的大小,所以子View MeasureSpec mode的就是AT_MOST欲诺,而size 暫定父View的 size抄谐。
-
如果父View傳進(jìn)來(lái)的模式是UNSPECIFIED類(lèi)型
- 當(dāng)當(dāng)前的View是一個(gè)固定值,也就是我們?cè)趚ml里面定義改View的width和height是固定的值的大小是的時(shí)候扰法,它最后的大小就是View設(shè)置的值蛹含。模式是MeasureSpec.EXACTLY。
- 當(dāng)當(dāng)前View的大小是設(shè)置成match_parent的時(shí)候塞颁,子 View 尺寸為 0浦箱,測(cè)量模式為 UNSPECIFIED,父容器不對(duì) View 有任何的限制祠锣,要多大給多大酷窥。
- 當(dāng)當(dāng)前的View設(shè)置成warp_content 內(nèi)容包裹時(shí),子 View 尺寸為 0伴网,測(cè)量模式為 UNSPECIFIED蓬推,父容器不對(duì) View 有任何的限制,要多大給多大澡腾。
1.3 View的measure過(guò)程
分兩種情況:
- 如果只是一個(gè)原始的 View沸伏,通過(guò)
measure
方法就完成了測(cè)量過(guò)程募逞。 - 如果是一個(gè) ViewGroup 除了完成自己的測(cè)量過(guò)程還會(huì)遍歷調(diào)用所有子 View 的
measure
方法,而且各個(gè)子 View 還會(huì)遞歸執(zhí)行這個(gè)過(guò)程馋评。
1.3.1 measure
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
放接。。留特。
//調(diào)用onMeasure()開(kāi)始繪制.
onMeasure(widthMeasureSpec, heightMeasureSpec);
纠脾。。蜕青。
}
代碼有點(diǎn)長(zhǎng)苟蹈,主要看measure是一個(gè)final的方法,即不能被重寫(xiě)右核,里面真正調(diào)用的是onMeasure()方法慧脱。所以我們?cè)谧远xView的時(shí)候可以重寫(xiě)該方法。
1.3.2 onMeasure
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
//用于獲得View寬/高的測(cè)量值
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
onMeasure方法主要用來(lái)獲取View的寬和高的測(cè)量值贺喝。在往下看代碼前菱鸥,先看一下默認(rèn)值的獲取getDefaultSize().
1.3.3getDefaultSize
public static int getDefaultSize(int size, int measureSpec) {
//默認(rèn)大小.
int result = size;
//獲取測(cè)量模式
int specMode = MeasureSpec.getMode(measureSpec);
//獲取大小
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
當(dāng)傳進(jìn)來(lái)的模式是UNSPECIFIED的時(shí)候,默認(rèn)值是size躏鱼,看一下size的獲取getSuggestedMinimumWidth().
1.3.4 getSuggestedMinimumWidth
protected int getSuggestedMinimumWidth() {
//如果沒(méi)有設(shè)置背景氮采,view的最小寬度是mMinWidth:
// 1、mMinWidth = android:minWidth屬性所指定的值染苛,2鹊漠、若android:minWidth沒(méi)指定,則默認(rèn)為0
return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
如果沒(méi)有設(shè)置背景則返回的值是設(shè)置的android:minWidth屬性指定的值茶行,若android:minWidth沒(méi)指定躯概,則默認(rèn)為0。如果有背景,則取兩者的最大值畔师。
回過(guò)頭來(lái)再看娶靡,在onMeasure()里面調(diào)用setMeasuredDimension方法。
1.3.5 setMeasuredDimension
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
boolean optical = isLayoutModeOptical(this);
if (optical != isLayoutModeOptical(mParent)) {
Insets insets = getOpticalInsets();
int opticalWidth = insets.left + insets.right;
int opticalHeight = insets.top + insets.bottom;
measuredWidth += optical ? opticalWidth : -opticalWidth;
measuredHeight += optical ? opticalHeight : -opticalHeight;
}
setMeasuredDimensionRaw(measuredWidth, measuredHeight);
}
在這里又調(diào)用setMeasuredDimensionRaw 將測(cè)量后的View的寬高進(jìn)行存儲(chǔ)茉唉。
1.3.6 setMeasuredDimensionRaw
private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
//測(cè)量后的view寬高的值.
mMeasuredWidth = measuredWidth;
mMeasuredHeight = measuredHeight;
mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}
1.4 實(shí)際獲取View寬高
在實(shí)際開(kāi)發(fā)中固蛾,我們?cè)贏ctiivty中獲取View的寬高往往都是0,這是由于我們無(wú)法保證在Activity執(zhí)行生命周期中度陆,View已經(jīng)測(cè)量完成艾凯,如果還沒(méi)測(cè)量完成,這時(shí)候去獲取懂傀,那結(jié)果肯定是0.以下幾種方法可以獲取View的寬高趾诗。
-
Activity/View#onWindowsChanged 方法
public void onWindowFocusChanged(boolean hasWindowFocus) { super.onWindowFocusChanged(hasWindowFocus); if(hasWindowFocus){ int width=view.getMeasuredWidth(); int height=view.getMeasuredHeight(); } }
onWindowFocusChanged 方法表示 View 已經(jīng)初始化完畢了,寬高已經(jīng)準(zhǔn)備好了,這個(gè)時(shí)候去獲取是沒(méi)問(wèn)題的恃泪。這個(gè)方法會(huì)被調(diào)用多次郑兴,當(dāng) Activity 繼續(xù)執(zhí)行或者暫停執(zhí)行的時(shí)候,這個(gè)方法都會(huì)被調(diào)用贝乎。
-
View.post(runnable)
@Override protected void onStart() { super.onStart(); view.post(new Runnable() { @Override public void run() { int width=view.getMeasuredWidth(); int height=view.getMeasuredHeight(); } }); }
用post異步加入到消息隊(duì)列情连,這樣也是可以獲取到的。
-
ViewTreeObsever
@Override protected void onStart() { super.onStart(); ViewTreeObserver viewTreeObserver=view.getViewTreeObserver(); viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { view.getViewTreeObserver().removeOnGlobalLayoutListener(this); int width=view.getMeasuredWidth(); int height=view.getMeasuredHeight(); } }); }
開(kāi)一個(gè)子線程览效,觀察ViewTreeObserver 的回調(diào)也可以獲取view的寬高却舀。當(dāng) View 樹(shù)的狀態(tài)發(fā)生改變或者 View 樹(shù)內(nèi)部的 View 的可見(jiàn)性發(fā)生改變時(shí),onGlobalLayout 方法將被回調(diào)锤灿。伴隨著View樹(shù)的變化挽拔,這個(gè)方法也會(huì)被多次調(diào)用。
?
二但校、layout過(guò)程
layout 的作用是 ViewGroup 來(lái)確定子元素的位置螃诅,當(dāng) ViewGroup 的位置被確定后,在 layout 中會(huì)調(diào)用 onLayout 状囱,在 onLayout 中會(huì)遍歷所有的子元素并調(diào)用子元素的 layout 方法术裸,在子元素的 layout 方法中 onLayout 方法又會(huì)被調(diào)用,layout 方法是確定 View 本身在屏幕上顯示的具體位置浪箭。
2.1 layout
public void layout(int l, int t, int r, int b) {
if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
}
int oldL = mLeft;
int oldT = mTop;
int oldB = mBottom;
int oldR = mRight;
// 即初始化四個(gè)頂點(diǎn)的值穗椅,然后判斷當(dāng)前View大小和位置是否發(fā)生了變化并返回
//第1步,調(diào)用 setFrame 方法 設(shè)置新的 mLeft奶栖、mTop、mBottom门坷、mRight 值宣鄙,
//設(shè)置 View 本身四個(gè)頂點(diǎn)位置,并返回 changed 用于判斷 view 布局是否改變
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
//第二步,如果 view 位置改變那么調(diào)用 onLayout 方法設(shè)置子 view 位置
if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
//調(diào)用 onLayout,是一個(gè)空方法默蚌。
onLayout(changed, l, t, r, b);
mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnLayoutChangeListeners != null) {
ArrayList<OnLayoutChangeListener> listenersCopy =
(ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
int numListeners = listenersCopy.size();
for (int i = 0; i < numListeners; ++i) {
listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
}
}
}
mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
}
即初始化四個(gè)頂點(diǎn)的值冻晤,然后判斷當(dāng)前View大小和位置是否發(fā)生了變化并返回.
大致流程是,首先先調(diào)用setFrame()绸吸,設(shè)置View本身的4個(gè)點(diǎn)鼻弧,其中setOpticalFrame本身內(nèi)部也是調(diào)用setFrame(),所以最終都是通過(guò)調(diào)用setFrame()來(lái)設(shè)置view的4個(gè)點(diǎn)锦茁。View 的四個(gè)頂點(diǎn)一旦確定攘轩,那么 View 在父容器中的位置就確定了。然后第二步是onLayout码俩,開(kāi)始具體布局度帮。其實(shí)onLayout是一個(gè)空方法,是要我們繼承重寫(xiě)的。
2.1 onLayout
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
三笨篷、 draw過(guò)程
draw過(guò)程總共分6步瞳秽,實(shí)際是4步。
- Draw the background 繪制view背景
- If necessary, save the canvas' layers to prepare for fading
- Draw view's content 繪制view內(nèi)容
- Draw children 繪制子View
- If necessary, draw the fading edges and restore layers
- Draw decorations (scrollbars for instance) 繪制裝飾(漸變框率翅,滑動(dòng)條等等
public void draw(Canvas canvas) {
final int privateFlags = mPrivateFlags;
final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
(mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
// 第一步
int saveCount;
if (!dirtyOpaque) {
drawBackground(canvas);
}
// skip step 2 & 5 if possible (common case)
final int viewFlags = mViewFlags;
boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
if (!verticalEdges && !horizontalEdges) {
// Step 3, draw the content
if (!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
// Overlay is part of the content and draws beneath Foreground
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
// we're done...
return;
}
练俐。。冕臭。
// Step 3, draw the content
if (!dirtyOpaque) onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
// Step 5, draw the fade effect and restore layers
final Paint p = scrollabilityCache.paint;
final Matrix matrix = scrollabilityCache.matrix;
final Shader fade = scrollabilityCache.shader;
痰洒。。浴韭。丘喻。
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
}