自定義View心法——View工作流程

前言

本文的目的有兩個(gè):

給對(duì)自定義View感興趣的人一些入門的指引

給正在使用自定義View的人一些更深入的解析

自定義View一直都被認(rèn)為是Android開發(fā)高手的必備技能绵咱,而穩(wěn)中帶皮的學(xué)習(xí)View的基礎(chǔ)體系,這是自定義View的必經(jīng)之路星压,如果自定義View如果設(shè)計(jì)的不好或者不考慮性能的話會(huì)造成很大的問題剖毯。所以我們進(jìn)入View工作流程的分析。

一、Android的UI層級(jí)繪制體系

Android中的Activity是作為應(yīng)用程序的載體存在的,它代表一個(gè)完整的用戶界面并提供了窗口進(jìn)行視圖繪制。

在這里宪摧,我們這里所說的視圖繪制,實(shí)質(zhì)上就是在對(duì)View及其子類進(jìn)行操作颅崩。而View作為視圖控件的頂層父類几于,在本文中會(huì)對(duì)其進(jìn)行詳細(xì)分析。我們以Android的UI層級(jí)繪制體系為切入點(diǎn)對(duì)View進(jìn)行探究挨摸。

圖1 View的層級(jí)結(jié)構(gòu)

Android的UI層級(jí)繪制體系如圖1所示孩革。

繪制體系中做了這些事情

①當(dāng)調(diào)用 Activity 的setContentView 方法后會(huì)調(diào)用PhoneWindow 類的setContentView方法(PhoneWindow是抽象類Windiw的實(shí)現(xiàn)類,Window用來描述Activity視圖最頂端的窗口的顯示內(nèi)容和行為動(dòng)作)得运。

②PhoneWindow類的setContentView方法中最終會(huì)生成一個(gè)DecorView對(duì)象(DectorView是是PhoneWindow的內(nèi)部類膝蜈,繼承自FrameLayout)。

③DecorView容器中包含根布局熔掺,根布局中包含一個(gè)id為content的FrameLayout布局饱搏,Activity加載布局的xml最后通過LayoutInflater將xml文件中的內(nèi)容解析成View層級(jí)體系,最后填加到id為content的FrameLayout布局中置逻。

至此推沸,View最終就會(huì)顯示到手機(jī)屏幕上。

二券坞、View的視圖繪制流程剖析

1鬓催、DecorView被加載到Window中

DecorView被加載到Window的過程中,WindowManager起到了關(guān)鍵性的作用恨锚,最后交給ViewRootImpl做詳細(xì)處理宇驾,通過如下的局部ActivityThread的源碼分析這一點(diǎn)可以得到印證(在這里我只展示核心源碼,詳細(xì)源碼可以在代碼中查看)猴伶。

finalvoidhandleResumeActivity(IBinder token,booleanclearHide,booleanisForward,booleanreallyResume,intseq, String reason){? ? ? ? ActivityClientRecord r = mActivities.get(token);? ? ? ...//在這里執(zhí)行performResumeActivity的方法中會(huì)執(zhí)行Activity的onResume()方法r = performResumeActivity(token, clearHide, reason);? ? ? ...if(r.window ==null&& !a.mFinished && willBeVisible) {//PhoneWindow在這里獲取到r.window = r.activity.getWindow();//DecorView在這里獲取到View decor = r.window.getDecorView();? ? ? ? ? decor.setVisibility(View.INVISIBLE);//獲取ViewManager對(duì)象课舍,在這里getWindowManager()實(shí)質(zhì)上獲取的是ViewManager的子類對(duì)象WindowManagerViewManager wm = a.getWindowManager();? ? ? ? ? ...if(r.mPreserveWindow) {? ? ? ? ? ...//獲取ViewRootImpl對(duì)象ViewRootImpl impl = decor.getViewRootImpl();? ? ? ? ? ...? ? ? ? ? }if(a.mVisibleFromClient) {if(!a.mWindowAdded) {? ? ? ? ? ? ? ? ? a.mWindowAdded =true;//在這里WindowManager將DecorView添加到PhoneWindow中wm.addView(decor, l);? ? ? ? ? ? ? ? ? }? ? ? ? ? ? ? ? ? ? ...? ? ? ? ? }? ? ? ? ? ...? ? }

WindowManager將DecorView添加到PhoneWindow中,即addView()方法執(zhí)行時(shí)將視圖添加的動(dòng)作交給了ViewRoot他挎,ViewRoot作為接口筝尾,其實(shí)現(xiàn)類ViewRootImpl具體實(shí)現(xiàn)了addView()方法,最后办桨,視圖的具體繪制在performTraversals()中展開筹淫,如下圖2.1所示:

圖2.1 View繪制的代碼層級(jí)分析

2、ViewRootImpl的performTraversals()方法完成具體的視圖繪制流程

在源碼中ViewRootImpl中視圖具體繪制的流程如下:

privatevoidperformTraversals(){// cache mView since it is used so much below...//mView就是DecorView根布局finalView host = mView;//在Step3 成員變量mAdded賦值為true呢撞,因此條件不成立if(host ==null|| !mAdded)return;//是否正在遍歷mIsInTraversal =true;//是否馬上繪制ViewmWillDrawSoon =true;? ? ? ? ...//頂層視圖DecorView所需要窗口的寬度和高度intdesiredWindowWidth;intdesiredWindowHeight;? ? ? ? ...//在構(gòu)造方法中mFirst已經(jīng)設(shè)置為true贸街,表示是否是第一次繪制DecorViewif(mFirst) {? ? ? ? ? ? mFullRedrawNeeded =true;? ? ? ? ? ? mLayoutRequested =true;//如果窗口的類型是有狀態(tài)欄的庵寞,那么頂層視圖DecorView所需要窗口的寬度和高度就是除了狀態(tài)欄if(lp.type == WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL? ? ? ? ? ? ? ? ? ? || lp.type == WindowManager.LayoutParams.TYPE_INPUT_METHOD) {// NOTE -- system code, won't try to do compat mode.Point size =newPoint();? ? ? ? ? ? ? ? mDisplay.getRealSize(size);? ? ? ? ? ? ? ? desiredWindowWidth = size.x;? ? ? ? ? ? ? ? desiredWindowHeight = size.y;? ? ? ? ? ? }else{//否則頂層視圖DecorView所需要窗口的寬度和高度就是整個(gè)屏幕的寬高DisplayMetrics packageMetrics =? ? ? ? ? ? ? ? ? ? mView.getContext().getResources().getDisplayMetrics();? ? ? ? ? ? ? ? desiredWindowWidth = packageMetrics.widthPixels;? ? ? ? ? ? ? ? desiredWindowHeight = packageMetrics.heightPixels;? ? ? ? ? ? }? ? }? ...//獲得view寬高的測(cè)量規(guī)格,mWidth和mHeight表示窗口的寬高薛匪,lp.widthhe和lp.height表示DecorView根布局寬和高intchildWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);intchildHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);// Ask host how big it wants to be//執(zhí)行測(cè)量操作performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);? ...//執(zhí)行布局操作performLayout(lp, desiredWindowWidth, desiredWindowHeight);? ...//執(zhí)行繪制操作performDraw();}

該方法主要流程就體現(xiàn)了View繪制渲染的三個(gè)主要步驟,分別是測(cè)量脓鹃,擺放逸尖,繪制三個(gè)階段。流程圖如下圖2.2所示:

圖2.2 View的繪制流程

接下來瘸右,我們對(duì)于 performMeasure()娇跟、performLayout()、 performDraw()完成具體拆解分析太颤。實(shí)質(zhì)上最后就需要定位到View的onMeasure()苞俘、onLayout()、onDraw()方法中龄章。

三吃谣、MeasureSpec在View體系中的作用

1、MeasureSpec的作用

首先我們從performMeasure()入手分析做裙,在上面的內(nèi)容中岗憋,我們通過源碼可以看到 performMeasure()方法中傳入了childWidthMeasureSpec、childHeightMeasureSpec兩個(gè)int類型的值锚贱,performMeasure方法的源碼如下所示:

privatevoidperformMeasure(intchildWidthMeasureSpec,intchildHeightMeasureSpec){? ? ? ? Trace.traceBegin(Trace.TRACE_TAG_VIEW,"measure");try{? ? ? ? ? ? mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);? ? ? ? }finally{? ? ? ? ? ? Trace.traceEnd(Trace.TRACE_TAG_VIEW);? ? ? ? }}

這兩個(gè)值又傳遞到mView.measure(childWidthMeasureSpec, childHeightMeasureSpec)方法中仔戈,其中measure方法的核心源碼如下:

publicfinalvoidmeasure(intwidthMeasureSpec,intheightMeasureSpec){booleanoptical = isLayoutModeOptical(this);if(optical != isLayoutModeOptical(mParent)) {? ? ? ? ? ? Insets insets = getOpticalInsets();intoWidth? = insets.left + insets.right;intoHeight = insets.top? + insets.bottom;//根據(jù)原有寬高計(jì)算獲取不同模式下的具體寬高值widthMeasureSpec? = MeasureSpec.adjust(widthMeasureSpec,? optical ? -oWidth? : oWidth);? ? ? ? ? ? heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);? ? ? ? }? ? ? ? ...if(forceLayout || needsLayout) {// first clears the measured dimension flagmPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;? ? ? ? ? ? resolveRtlPropertiesIfNeeded();intcacheIndex = forceLayout ? -1: mMeasureCache.indexOfKey(key);if(cacheIndex <0|| sIgnoreMeasureCache) {// measure ourselves, this should set the measured dimension flag back//在該方法中子控件完成具體的測(cè)量onMeasure(widthMeasureSpec, heightMeasureSpec);? ? ? ? ? ? ? ? ...? ? ? ? ? ? }? ? ? ? ? ...? ? }

到這里我們應(yīng)該明確,childWidthMeasureSpec, childHeightMeasureSpec是MeasureSpec根據(jù)原有寬高計(jì)算獲取不同模式下的具體寬高值拧廊。

2监徘、MeasureSpec剖析

MeasureSpec是View的內(nèi)部類,內(nèi)部封裝了View的規(guī)格尺寸吧碾,以及View的寬高信息凰盔。在Measure的流程中,系統(tǒng)會(huì)將View的LayoutParams根據(jù)父容器是施加的規(guī)則轉(zhuǎn)換為MeasureSpec滤港,然后在onMeasure()方法中具體確定控件的寬高信息廊蜒。源碼及分析如下所示:

publicstaticclassMeasureSpec{//int類型占4個(gè)字節(jié),其中高2位表示尺寸測(cè)量模式溅漾,低30位表示具體的寬高信息privatestaticfinalintMODE_SHIFT =30;privatestaticfinalintMODE_MASK? =0x3<< MODE_SHIFT;/**@hide*/@IntDef({UNSPECIFIED, EXACTLY, AT_MOST})@Retention(RetentionPolicy.SOURCE)public@interfaceMeasureSpecMode {}//如下所示是MeasureSpec中的三種模式:UNSPECIFIED山叮、EXACTLY、AT_MOST? ? ? ? ? ? ? ? ? /**

? ? ? ? * Measure specification mode: The parent has not imposed any constraint

? ? ? ? * on the child. It can be whatever size it wants.

? ? ? ? */publicstaticfinalintUNSPECIFIED =0<< MODE_SHIFT;/**

? ? ? ? * Measure specification mode: The parent has determined an exact size

? ? ? ? * for the child. The child is going to be given those bounds regardless

? ? ? ? * of how big it wants to be.

? ? ? ? */publicstaticfinalintEXACTLY? ? =1<< MODE_SHIFT;/**

? ? ? ? * Measure specification mode: The child can be as large as it wants up

? ? ? ? * to the specified size.

? ? ? ? */publicstaticfinalintAT_MOST? ? =2<< MODE_SHIFT;//根據(jù)尺寸測(cè)量模式跟寬高具體確定控件的具體寬高publicstaticintmakeMeasureSpec(@IntRange(from =0, to = (1<< MeasureSpec.MODE_SHIFT)- 1)intsize,? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? @MeasureSpecModeintmode){if(sUseBrokenMakeMeasureSpec) {returnsize + mode;? ? ? ? ? ? }else{return(size & ~MODE_MASK) | (mode & MODE_MASK);? ? ? ? ? ? }? ? ? ? }/**? ? ? ? * Like {@link#makeMeasureSpec(int, int)}, but any spec with a mode of UNSPECIFIED? ? ? ? * will automatically get a size of 0. Older apps expect this.? ? ? ? *? ? ? ? *@hideinternal use only for compatibility with system widgets and older apps? ? ? ? */publicstaticintmakeSafeMeasureSpec(intsize,intmode){if(sUseZeroUnspecifiedMeasureSpec && mode == UNSPECIFIED) {return0;? ? ? ? ? ? }returnmakeMeasureSpec(size, mode);? ? ? ? }//獲取尺寸模式/**? ? ? ? * Extracts the mode from the supplied measure specification.? ? ? ? *? ? ? ? *@parammeasureSpec the measure specification to extract the mode from? ? ? ? *@return{@linkandroid.view.View.MeasureSpec#UNSPECIFIED},? ? ? ? *? ? ? ? {@linkandroid.view.View.MeasureSpec#AT_MOST} or? ? ? ? *? ? ? ? {@linkandroid.view.View.MeasureSpec#EXACTLY}? ? ? ? */@MeasureSpecModepublicstaticintgetMode(intmeasureSpec){//noinspection ResourceTypereturn(measureSpec & MODE_MASK);? ? ? ? }//獲取寬高信息/**? ? ? ? * Extracts the size from the supplied measure specification.? ? ? ? *? ? ? ? *@parammeasureSpec the measure specification to extract the size from? ? ? ? *@returnthe size in pixels defined in the supplied measure specification? ? ? ? */publicstaticintgetSize(intmeasureSpec){return(measureSpec & ~MODE_MASK);? ? ? ? }//將控件的尺寸模式添履、寬高信息進(jìn)行拆解查看屁倔,并對(duì)不同模式下的寬高信息進(jìn)行不同的處理staticintadjust(intmeasureSpec,intdelta){finalintmode = getMode(measureSpec);intsize = getSize(measureSpec);if(mode == UNSPECIFIED) {// No need to adjust size for UNSPECIFIED mode.returnmakeMeasureSpec(size, UNSPECIFIED);? ? ? ? ? ? }? ? ? ? ? ? size += delta;if(size <0) {? ? ? ? ? ? ? ? Log.e(VIEW_LOG_TAG,"MeasureSpec.adjust: new size would be negative! ("+ size +") spec: "+ toString(measureSpec) +" delta: "+ delta);? ? ? ? ? ? ? ? size =0;? ? ? ? ? ? }returnmakeMeasureSpec(size, mode);? ? ? ? }/**? ? ? ? * Returns a String representation of the specified measure? ? ? ? * specification.? ? ? ? *? ? ? ? *@parammeasureSpec the measure specification to convert to a String? ? ? ? *@returna String with the following format: "MeasureSpec: MODE SIZE"? ? ? ? */publicstaticStringtoString(intmeasureSpec){intmode = getMode(measureSpec);intsize = getSize(measureSpec);? ? ? ? ? ? StringBuilder sb =newStringBuilder("MeasureSpec: ");if(mode == UNSPECIFIED)? ? ? ? ? ? ? ? sb.append("UNSPECIFIED ");elseif(mode == EXACTLY)? ? ? ? ? ? ? ? sb.append("EXACTLY ");elseif(mode == AT_MOST)? ? ? ? ? ? ? ? sb.append("AT_MOST ");elsesb.append(mode).append(" ");? ? ? ? ? ? sb.append(size);returnsb.toString();? ? ? ? }? ? }

MeasureSpec的常量中指定了兩種內(nèi)容,一種為尺寸模式暮胧,一種為具體的寬高信息锐借。其中高2位表示尺寸測(cè)量模式问麸,低30位表示具體的寬高信息。

尺寸測(cè)量模式有如下三種:

尺寸測(cè)量模式的3種類型

①UNSPECIFIED:未指定模式钞翔,父容器不限制View的大小严卖,一般用于系統(tǒng)內(nèi)部的測(cè)量

②AT_MOST:最大模式,對(duì)應(yīng)于在xml文件中指定控件大小為wrap_content屬性布轿,子View的最終大小是父View指定的大小值哮笆,并且子View的大小不能大于這個(gè)值

③EXACTLY :精確模式,對(duì)應(yīng)于在xml文件中指定控件為match_parent屬性或者是具體的數(shù)值汰扭,父容器測(cè)量出View所需的具體大小

我?guī)湍憧偨Y(jié)一下

對(duì)于每一個(gè)View稠肘,都持有一個(gè)MeasureSpec,MeasureSpec保存了該View的尺寸測(cè)量模式以及具體的寬高信息萝毛,MeasureSpec受自身的LayoutParams和父容器的MeasureSpec共同影響项阴。

四、View的Measure流程分析

1笆包、View樹的Measure測(cè)量流程邏輯圖

2环揽、View的Measure流程分析

那么在上文3.1的分析中,我們能夠明確在measure方法中最后調(diào)用onMeasure()方法完成子View的具體測(cè)量色查,onMeasure()方法的源碼如下所示:

protectedvoidonMeasure(intwidthMeasureSpec,intheightMeasureSpec){? ? ? ? setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),? ? ? ? ? ? ? ? getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));? ? }

setMeasuredDimension()方法在onMeasure()中被調(diào)用薯演,被用于存儲(chǔ)測(cè)繪的寬度、高度秧了,而不這樣做的話會(huì)觸發(fā)測(cè)繪時(shí)的異常跨扮。

protectedfinalvoidsetMeasuredDimension(intmeasuredWidth,intmeasuredHeight){booleanoptical = isLayoutModeOptical(this);if(optical != isLayoutModeOptical(mParent)) {? ? ? ? ? ? Insets insets = getOpticalInsets();intopticalWidth? = insets.left + insets.right;intopticalHeight = insets.top? + insets.bottom;? ? ? ? ? ? measuredWidth? += optical ? opticalWidth? : -opticalWidth;? ? ? ? ? ? measuredHeight += optical ? opticalHeight : -opticalHeight;? ? ? ? }? ? ? ? setMeasuredDimensionRaw(measuredWidth, measuredHeight);? ? }

在setMeasuredDimension()方法中傳入的是getDefaultSize(),接著分析getDefaultSize()中做了哪些操作:

publicstaticintgetDefaultSize(intsize,intmeasureSpec){intresult = size;intspecMode = MeasureSpec.getMode(measureSpec);intspecSize = MeasureSpec.getSize(measureSpec);switch(specMode) {caseMeasureSpec.UNSPECIFIED:? ? ? ? ? ? result = size;break;caseMeasureSpec.AT_MOST:caseMeasureSpec.EXACTLY:? ? ? ? ? ? result = specSize;break;? ? ? ? }returnresult;? ? }

通過上文對(duì)MeasureSpec的分析验毡,在這里我們就能明確衡创,getDefaultSize實(shí)質(zhì)上就是根據(jù)測(cè)繪模式確定子View的具體大小,而對(duì)于自定義View而言晶通,子View的寬高信息不僅由自身決定璃氢,如果它被包裹在ViewGroup中就需要具體測(cè)量得到其精確值。

3狮辽、View的Measure過程中遇到的問題以及解決方案

View 的measure過程和Activity的生命周期方法不是同步執(zhí)行的一也,因此無法保證Activity執(zhí)行了onCreate、onStart喉脖、onResume時(shí)某個(gè)View已經(jīng)測(cè)量完畢了椰苟。如果View還沒有測(cè)量完畢,那么獲得的寬和高都是0树叽。下面是3種解決該問題的方法:

①Activity/View的onWindowsChanged()方法

onWindowFocusChanged()方法表示 View 已經(jīng)初始化完畢了舆蝴,寬高已經(jīng)準(zhǔn)備好了,這個(gè)時(shí)候去獲取是沒問題的。這個(gè)方法會(huì)被調(diào)用多次洁仗,當(dāng)Activity繼續(xù)執(zhí)行或者暫停執(zhí)行的時(shí)候层皱,這個(gè)方法都會(huì)被調(diào)用,代碼如下:

publicvoidonWindowFocusChanged(booleanhasWindowFocus){super.onWindowFocusChanged(hasWindowFocus);if(hasWindowFocus){intwidth=view.getMeasuredWidth();intheight=view.getMeasuredHeight();? ? ? }? ? ? ? }

②View.post(runnable)方法

通過post將一個(gè) Runnable投遞到消息隊(duì)列的尾部赠潦,然后等待Looper調(diào)用此runnable的時(shí)候View也已經(jīng)初始化好了

@OverrideprotectedvoidonStart(){super.onStart();? ? ? view.post(newRunnable() {@Overridepublicvoidrun(){intwidth=view.getMeasuredWidth();intheight=view.getMeasuredHeight();? ? ? ? ? }? ? ? });? }

③ViewTreeObsever

使用 ViewTreeObserver 的眾多回調(diào)方法可以完成這個(gè)功能叫胖,比如使用onGlobalLayoutListener 接口,當(dāng) View樹的狀態(tài)發(fā)生改變或者View樹內(nèi)部的View的可見性發(fā)生改變時(shí)她奥,onGlobalLayout 方法將被回調(diào)臭家。伴隨著View樹的變化,這個(gè)方法也會(huì)被多次調(diào)用方淤。

@OverrideprotectedvoidonStart(){super.onStart();? ? ? ViewTreeObserver viewTreeObserver=view.getViewTreeObserver();? ? ? viewTreeObserver.addOnGlobalLayoutListener(newViewTreeObserver.OnGlobalLayoutListener() {@OverridepublicvoidonGlobalLayout(){? ? ? ? ? ? ? view.getViewTreeObserver().removeOnGlobalLayoutListener(this);intwidth=view.getMeasuredWidth();intheight=view.getMeasuredHeight();? ? ? ? ? }? ? ? });? }

當(dāng)然,在這里你可以通過setMeasuredDimension()方法對(duì)子View的具體寬高以及測(cè)量模式進(jìn)行指定蹄殃。

五携茂、View的layout流程分析

1、View樹的layout擺放流程邏輯圖

2诅岩、View的layout流程分析

layout 的作用是ViewGroup來確定子元素的位置讳苦,當(dāng) ViewGroup 的位置被確定后,在layout中會(huì)調(diào)用onLayout 吩谦,在onLayout中會(huì)遍歷所有的子元素并調(diào)用子元素的 layout 方法鸳谜。

在代碼中設(shè)置View的成員變量 mLeft,mTop式廷,mRight咐扭,mBottom 的值,這幾個(gè)值是在屏幕上構(gòu)成矩形區(qū)域的四個(gè)坐標(biāo)點(diǎn)滑废,就是該View顯示的位置蝗肪,不過這里的具體位置都是相對(duì)與父視圖的位置而言,而 onLayout 方法則會(huì)確定所有子元素位置蠕趁,ViewGroup在onLayout函數(shù)中通過調(diào)用其children的layout函數(shù)來設(shè)置子視圖相對(duì)與父視圖中的位置薛闪,具體位置由函數(shù) layout 的參數(shù)決定。下面我們先看View的layout 方法(只展示關(guān)鍵性代碼)如下:

/*?

*@param l view 左邊緣相對(duì)于父布局左邊緣距離

*@param t view 上邊緣相對(duì)于父布局上邊緣位置

*@param r view 右邊緣相對(duì)于父布局左邊緣距離

*@param b view 下邊緣相對(duì)于父布局上邊緣距離

*/publicvoidlayout(intl,intt,intr,intb){? ? ? ? ...//記錄 view 原始位置? intoldL = mLeft;intoldT = mTop;intoldB = mBottom;intoldR = mRight;//調(diào)用 setFrame 方法 設(shè)置新的 mLeft俺陋、mTop豁延、mBottom、mRight 值腊状,? //設(shè)置 View 本身四個(gè)頂點(diǎn)位置? //并返回 changed 用于判斷 view 布局是否改變? booleanchanged = 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? onLayout(changed, l, t, r, b);? ? ? ? ? ? ...? ? ? ? }? ? ? ? ...? ? }

六、View的draw流程分析

1寿酌、View樹的draw繪制流程邏輯圖

2胰苏、View的draw流程分析

在View的draw()方法的注釋中,說明了繪制流程中具體每一步的作用,源碼中對(duì)于draw()方法的注釋如下硕并,我們?cè)谶@里重點(diǎn)分析注釋中除第2法焰、第5步外的其他步驟。

/*

? ? ? ? * Draw traversal performs several drawing steps which must be executed

? ? ? ? * in the appropriate order:

? ? ? ? *

? ? ? ? *? ? ? 1. Draw the background(繪制背景)

? ? ? ? *? ? ? 2. If necessary, save the canvas' layers to prepare for fading(如果需要的話倔毙,保存畫布背景以展示漸變效果)

? ? ? ? *? ? ? 3. Draw view's content(繪制View的內(nèi)容)

? ? ? ? *? ? ? 4. Draw children(繪制子View)

? ? ? ? *? ? ? 5. If necessary, draw the fading edges and restore layers(如果需要的話埃仪,繪制漸變邊緣并恢復(fù)畫布圖層。)

? ? ? ? *? ? ? 6. Draw decorations (scrollbars for instance)(繪制裝飾(例如滾動(dòng)條scrollbar))

? ? ? ? */

①View中的drawBackground()繪制背景

核心源碼如下:

privatevoiddrawBackground(Canvas canvas){finalDrawable background = mBackground;if(background ==null) {return;? ? ? ? }? ? ? ? ...finalintscrollX = mScrollX;finalintscrollY = mScrollY;if((scrollX | scrollY) ==0) {? ? ? ? ? ? background.draw(canvas);? ? ? ? }else{? ? ? ? ? ? canvas.translate(scrollX, scrollY);? ? ? ? ? ? background.draw(canvas);? ? ? ? ? ? canvas.translate(-scrollX, -scrollY);? ? ? ? }? ? }

如果背景有偏移陕赃,實(shí)質(zhì)上對(duì)畫布首先做偏移處理卵蛉,然后在其上進(jìn)行繪制。

②View內(nèi)容的繪制

View內(nèi)容的繪制源碼如下所示:

protectedvoidonDraw(Canvas canvas){? ? }

該方法是空實(shí)現(xiàn)么库,就根據(jù)不同的內(nèi)容進(jìn)行不同的設(shè)置傻丝,自定義View中就需要重寫該方法加入我們自己的業(yè)務(wù)邏輯。

③子View的繪制

子View的繪制源碼如下所示:

protectedvoiddispatchDraw(Canvas canvas){? ? }

該方法同樣為空實(shí)現(xiàn)诉儒,而對(duì)于ViewGroup而言對(duì)子View進(jìn)行遍歷葡缰,并最終調(diào)用子View的onDraw方法進(jìn)行繪制。

④裝飾繪制

裝飾繪制的源碼如下所示(只展示核心源碼):

publicvoidonDrawForeground(Canvas canvas){//繪制前景裝飾onDrawScrollIndicators(canvas);? ? ? ? onDrawScrollBars(canvas);? ? ? ...? ? ? ? ? ? foreground.draw(canvas);? ? }

很明顯忱反,在這里onDrawForeground()方法用于繪制例如ScrollBar等其他裝飾泛释,并將它們顯示在視圖的最上層。

七温算、視圖重繪

1怜校、requestLayout重新繪制視圖

子View調(diào)用requestLayout方法,會(huì)標(biāo)記當(dāng)前View及父容器注竿,同時(shí)逐層向上提交茄茁,直到ViewRootImpl處理該事件,ViewRootImpl會(huì)調(diào)用三大流程蔓搞,從measure開始胰丁,對(duì)于每一個(gè)含有標(biāo)記位的view及其子View都會(huì)進(jìn)行測(cè)量、布局喂分、繪制锦庸。

2、invalidate在UI線程中重新繪制視圖

當(dāng)子View調(diào)用了invalidate方法后蒲祈,會(huì)為該View添加一個(gè)標(biāo)記位甘萧,同時(shí)不斷向父容器請(qǐng)求刷新,父容器通過計(jì)算得出自身需要重繪的區(qū)域梆掸,直到傳遞到ViewRootImpl中扬卷,最終觸發(fā)performTraversals方法,進(jìn)行開始View樹重繪流程(只繪制需要重繪的視圖)酸钦。

3怪得、postInvalidate在非UI線程中重新繪制視圖

這個(gè)方法與invalidate方法的作用是一樣的,都是使View樹重繪,但兩者的使用條件不同徒恋,postInvalidate是在非UI線程中調(diào)用蚕断,invalidate則是在UI線程中調(diào)用。

我要總結(jié)了

總結(jié)一下

一般來說入挣,如果View確定自身不再適合當(dāng)前區(qū)域亿乳,比如說它的LayoutParams發(fā)生了改變,需要父布局對(duì)其進(jìn)行重新測(cè)量径筏、擺放葛假、繪制這三個(gè)流程,往往使用requestLayout滋恬。而invalidate則是刷新當(dāng)前View聊训,使當(dāng)前View進(jìn)行重繪,不會(huì)進(jìn)行測(cè)量恢氯、布局流程魔眨,因此如果View只需要重繪而不需要測(cè)量,布局的時(shí)候酿雪,使用invalidate方法往往比requestLayout方法更高效。

作者:Alex_Payne

鏈接:http://www.reibang.com/p/af266ff378c6

來源:簡書

簡書著作權(quán)歸作者所有侄刽,任何形式的轉(zhuǎn)載都請(qǐng)聯(lián)系作者獲得授權(quán)并注明出處指黎。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市州丹,隨后出現(xiàn)的幾起案子醋安,更是在濱河造成了極大的恐慌,老刑警劉巖墓毒,帶你破解...
    沈念sama閱讀 217,509評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件吓揪,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡所计,警方通過查閱死者的電腦和手機(jī)柠辞,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來主胧,“玉大人叭首,你說我怎么就攤上這事∽俣埃” “怎么了焙格?”我有些...
    開封第一講書人閱讀 163,875評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長夷都。 經(jīng)常有香客問我眷唉,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,441評(píng)論 1 293
  • 正文 為了忘掉前任冬阳,我火速辦了婚禮蛤虐,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘摩泪。我一直安慰自己笆焰,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,488評(píng)論 6 392
  • 文/花漫 我一把揭開白布见坑。 她就那樣靜靜地躺著嚷掠,像睡著了一般。 火紅的嫁衣襯著肌膚如雪荞驴。 梳的紋絲不亂的頭發(fā)上不皆,一...
    開封第一講書人閱讀 51,365評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音熊楼,去河邊找鬼霹娄。 笑死,一個(gè)胖子當(dāng)著我的面吹牛鲫骗,可吹牛的內(nèi)容都是我干的犬耻。 我是一名探鬼主播,決...
    沈念sama閱讀 40,190評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼执泰,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼枕磁!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起术吝,我...
    開封第一講書人閱讀 39,062評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤计济,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后排苍,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體沦寂,經(jīng)...
    沈念sama閱讀 45,500評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,706評(píng)論 3 335
  • 正文 我和宋清朗相戀三年淘衙,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了传藏。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,834評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡彤守,死狀恐怖漩氨,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情遗增,我是刑警寧澤叫惊,帶...
    沈念sama閱讀 35,559評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站做修,受9級(jí)特大地震影響霍狰,放射性物質(zhì)發(fā)生泄漏抡草。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,167評(píng)論 3 328
  • 文/蒙蒙 一蔗坯、第九天 我趴在偏房一處隱蔽的房頂上張望康震。 院中可真熱鬧,春花似錦宾濒、人聲如沸腿短。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽橘忱。三九已至,卻和暖如春卸奉,著一層夾襖步出監(jiān)牢的瞬間钝诚,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評(píng)論 1 269
  • 我被黑心中介騙來泰國打工榄棵, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留凝颇,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,958評(píng)論 2 370
  • 正文 我出身青樓疹鳄,卻偏偏與公主長得像拧略,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子瘪弓,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,779評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容