View的绘制-draw流程详解
目錄
作用
根據(jù) measure 測(cè)量出的寬高,layout 布局的位置,渲染整個(gè) View 樹(shù),將界面呈現(xiàn)出來(lái)。
具體分析
以下源碼基于版本27
DecorView 的draw 流程
在《View的繪制-measure流程詳解》中說(shuō)過(guò),View 的繪制流程是從 ViewRootViewImpl 中的 performMeasure()、performLayout、performDraw 開(kāi)始的。在執(zhí)行完 performMeasure() 、performLayout 后,開(kāi)始執(zhí)行 performDraw 方法:(以下源碼有所刪減)
//ViewRootViewImpl 類(lèi)private void performDraw() {....draw(fullRedrawNeeded);....} ------------------------------------------------------------------------- //ViewRootViewImpl 類(lèi) private void draw(boolean fullRedrawNeeded) {....mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);.... }------------------------------------------------------------------------- //ThreadedRenderer 類(lèi) void draw(View view, AttachInfo attachInfo, DrawCallbacks callbacks) {....updateRootDisplayList(view, callbacks);.... } ------------------------------------------------------------------------- //ThreadedRenderer 類(lèi) private void updateRootDisplayList(View view, DrawCallbacks callbacks) {....updateViewTreeDisplayList(view);.... } ------------------------------------------------------------------------- //ThreadedRenderer 類(lèi) private void updateViewTreeDisplayList(View view) {view.mPrivateFlags |= View.PFLAG_DRAWN;view.mRecreateDisplayList = (view.mPrivateFlags & View.PFLAG_INVALIDATED)== View.PFLAG_INVALIDATED;view.mPrivateFlags &= ~View.PFLAG_INVALIDATED;//這里調(diào)用了 View 的 updateDisplayListIfDirty 方法 //這個(gè) View 其實(shí)就是 DecorViewview.updateDisplayListIfDirty();view.mRecreateDisplayList = false; } 復(fù)制代碼接下來(lái)查看 View 的 updateDisplayListIfDirty 方法:
//View 類(lèi)public RenderNode updateDisplayListIfDirty() {....if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {dispatchDraw(canvas);drawAutofilledHighlight(canvas);if (mOverlay != null && !mOverlay.isEmpty()) {mOverlay.getOverlayView().draw(canvas);}if (debugDraw()) {debugDrawFocus(canvas);}} else {/*最終調(diào)用了 DecorView 的 draw 方法,為什么沒(méi)有走上面的 dispatchDraw(canvas)我也母雞啊,我是Debug 斷點(diǎn)調(diào)試曉得走這里的,哈哈*/draw(canvas);}.... } ------------------------------------------------------------------------- //DecorView 重寫(xiě)了 draw 方法。所以走到了 DecorView 的 draw 方法 public void draw(Canvas canvas) {//調(diào)用父類(lèi) (View)的 draw 方法super.draw(canvas);if (mMenuBackground != null) {mMenuBackground.draw(canvas);} } 復(fù)制代碼以上流程,推薦兩篇文章:ViewRootImpl的performDraw過(guò)程 ~~~~~~~~~~~~~~~~~淺談ondraw的前世今身
View 的 draw 流程
就這樣, View 的繪制就開(kāi)始啦。主要有四個(gè)步驟:
- drawBackground 繪制背景色
- onDraw 繪制內(nèi)容
- dispatchDraw 繪制 children
- onDrawForeground 繪制裝飾(前景,滾動(dòng)條)
我們對(duì)四個(gè)步驟進(jìn)行分析:
//View 類(lèi) //繪制背景 private void drawBackground(Canvas canvas) {final Drawable background = mBackground;//如果沒(méi)有設(shè)置背景,就不進(jìn)行繪制if (background == null) {return;}//如果設(shè)置了背景嗎,且背景的大小發(fā)生了改變,//就用 layout 計(jì)算出的四個(gè)邊界值來(lái)確定背景的邊界setBackgroundBounds();// Attempt to use a display list if requested.if (canvas.isHardwareAccelerated() && mAttachInfo != null&& mAttachInfo.mThreadedRenderer != null) {mBackgroundRenderNode = getDrawableRenderNode(background, mBackgroundRenderNode);final RenderNode renderNode = mBackgroundRenderNode;if (renderNode != null && renderNode.isValid()) {setBackgroundRenderNodeProperties(renderNode);((DisplayListCanvas) canvas).drawRenderNode(renderNode);return;}}final int scrollX = mScrollX;final int scrollY = mScrollY;if ((scrollX | scrollY) == 0) {//調(diào)用 Drawable 的 draw 方法來(lái)進(jìn)行背景的繪制background.draw(canvas);} else {//平移畫(huà)布canvas.translate(scrollX, scrollY);//調(diào)用 Drawable 的 draw 方法來(lái)進(jìn)行背景的繪制background.draw(canvas);canvas.translate(-scrollX, -scrollY);} } ------------------------------------------------------------------------- //View 類(lèi) //繪制內(nèi)容 protected void onDraw(Canvas canvas) {/*View 中的 onDraw 是一個(gè)空實(shí)現(xiàn)。也不難理解,當(dāng)我們自定義控件繼承 View 的時(shí)候,需要重寫(xiě) onDraw 方法,通過(guò) Canvas 和 Paint 來(lái)進(jìn)行內(nèi)容的繪制*/ } ------------------------------------------------------------------------- //View 類(lèi) //繪制 children protected void dispatchDraw(Canvas canvas) {/*View 中的 dispatchDraw 也是一個(gè)空實(shí)現(xiàn)。因?yàn)閱为?dú)一個(gè) View 本身是沒(méi)有子元素的,不需要繪制 children */ }------------------------------------------------------------------------- //View 類(lèi) //繪制裝飾 public void onDrawForeground(Canvas canvas) {//繪制指示器onDrawScrollIndicators(canvas);//繪制滾動(dòng)條onDrawScrollBars(canvas);final Drawable foreground = mForegroundInfo != null ? mForegroundInfo.mDrawable : null;if (foreground != null) {if (mForegroundInfo.mBoundsChanged) {mForegroundInfo.mBoundsChanged = false;final Rect selfBounds = mForegroundInfo.mSelfBounds;final Rect overlayBounds = mForegroundInfo.mOverlayBounds;if (mForegroundInfo.mInsidePadding) {selfBounds.set(0, 0, getWidth(), getHeight());} else {selfBounds.set(getPaddingLeft(), getPaddingTop(),getWidth() - getPaddingRight(), getHeight() - getPaddingBottom());}final int ld = getLayoutDirection();Gravity.apply(mForegroundInfo.mGravity, foreground.getIntrinsicWidth(),foreground.getIntrinsicHeight(), selfBounds, overlayBounds, ld);foreground.setBounds(overlayBounds);}//調(diào)用 Drawable 的 draw 方法,繪制前景色foreground.draw(canvas);} } 復(fù)制代碼以上就是 View 的繪制流程了。ViewGroup 本身是繼承 View 的,它的基本繪制流程也是通過(guò)父類(lèi) View 進(jìn)行的,只不過(guò)它重寫(xiě)了 dispatchDraw 方法,來(lái)進(jìn)行子元素的繪制。下面我們來(lái)進(jìn)行具體分析:
ViewGroup 的繪制 dispatchDraw 流程
//ViewGroup 類(lèi) protected void dispatchDraw(Canvas canvas) {....for (int i = 0; i < childrenCount; i++) {while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) {final View transientChild = mTransientViews.get(transientIndex);if ((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE ||transientChild.getAnimation() != null) {more |= drawChild(canvas, transientChild, drawingTime);}transientIndex++;if (transientIndex >= transientCount) {transientIndex = -1;}}final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {//調(diào)用 drawChild 方法,進(jìn)行繪制子元素more |= drawChild(canvas, child, drawingTime);}}.... } ------------------------------------------------------------------------- //ViewGroup 類(lèi) protected boolean drawChild(Canvas canvas, View child, long drawingTime) {//調(diào)用 View 的 draw 方法,這里要注意,調(diào)用的是 View 的三個(gè)參數(shù)的 draw 方法return child.draw(canvas, this, drawingTime); }復(fù)制代碼在 View 中還有一個(gè) draw(Canvas canvas) 的重載方法,就是 draw(Canvas canvas, ViewGroup parent, long drawingTime):
//View 類(lèi) /*** ViewGroup.drawChild()調(diào)用此方法以使每個(gè)子視圖自己繪制。* 這是View專(zhuān)門(mén)根據(jù)圖層類(lèi)型和硬件加速來(lái)渲染行為的地方。*/ boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {....//是否支持硬件加速boolean drawingWithRenderNode = mAttachInfo != null&& mAttachInfo.mHardwareAccelerated&& hardwareAcceleratedCanvas;if (layerType == LAYER_TYPE_SOFTWARE || !drawingWithRenderNode) {if (layerType != LAYER_TYPE_NONE) {//未開(kāi)啟//調(diào)用 View 的 buildDrawingCache 方法buildDrawingCache(true);}cache = getDrawingCache(true);}//開(kāi)啟了硬件加速if (drawingWithRenderNode) {//調(diào)用 View 的 updateDisplayListIfDirty 方法renderNode = updateDisplayListIfDirty();if (!renderNode.isValid()) {// Uncommon, but possible. If a view is removed from the hierarchy during the call// to getDisplayList(), the display list will be marked invalid and we should not// try to use it again.renderNode = null;drawingWithRenderNode = false;}}.... } 復(fù)制代碼分別查看 buildDrawingCache 和 updateDisplayListIfDirty 方法:
//View 類(lèi) public void buildDrawingCache(boolean autoScale) {....buildDrawingCacheImpl(autoScale);.... } ------------------------------------------------------------------------- //View 類(lèi) private void buildDrawingCacheImpl(boolean autoScale) {....// 如果不需要進(jìn)行自身繪制,就直接調(diào)用 dispatchDraw 繪制子 Children//否則就直接調(diào)用 View 的 draw 方法if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {mPrivateFlags &= ~PFLAG_DIRTY_MASK;dispatchDraw(canvas);drawAutofilledHighlight(canvas);if (mOverlay != null && !mOverlay.isEmpty()) {mOverlay.getOverlayView().draw(canvas);}} else {draw(canvas);}.... } -------------------------------------------------------------------------//View 類(lèi) public RenderNode updateDisplayListIfDirty() {....// 如果不需要進(jìn)行自身繪制,就直接調(diào)用 dispatchDraw 繪制子 Children//否則就直接調(diào)用 View 的 draw 方法if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {dispatchDraw(canvas);drawAutofilledHighlight(canvas);if (mOverlay != null && !mOverlay.isEmpty()) {mOverlay.getOverlayView().draw(canvas);}if (debugDraw()) {debugDrawFocus(canvas);}} else {draw(canvas);}.... } 復(fù)制代碼如此,從頂層 DecorView 的 draw 方法開(kāi)始,然后調(diào)用 dispatchDraw 方法循環(huán)遍歷繪制子元素,如果子元素是繼承了 ViewGroup ,就再次循環(huán)調(diào)用 dispatchDraw 方法,一層層往下遞歸調(diào)用,直到每一個(gè)子元素都被繪制完成,整個(gè) draw 流程也就結(jié)束了。
tips: 在我們使用真機(jī)進(jìn)行源碼斷點(diǎn)調(diào)試的時(shí)候,可能會(huì)出現(xiàn)源碼不能打斷點(diǎn)的情況,或者斷點(diǎn)沒(méi)有走在該走的地方。這是因?yàn)閲?guó)內(nèi)手機(jī)廠商基本都是定制系統(tǒng),可能修改了源碼。這個(gè)時(shí)候可以使用模擬器進(jìn)行斷點(diǎn)調(diào)試。注意:模擬器版本號(hào)要和項(xiàng)目編譯版本號(hào)一致!
setWillNotDraw 解析
在 View 中有一個(gè)方法是 setWillNotDraw:
//View 類(lèi) /*** If this view doesn't do any drawing on its own, set this flag to* allow further optimizations. By default, this flag is not set on* View, but could be set on some View subclasses such as ViewGroup.** Typically, if you override {@link #onDraw(android.graphics.Canvas)}* you should clear this flag.** @param willNotDraw whether or not this View draw on its own*/ public void setWillNotDraw(boolean willNotDraw) {setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK); } 復(fù)制代碼從注釋上看,如果此視圖本身不執(zhí)行任何繪制,就設(shè)置為 true,系統(tǒng)會(huì)進(jìn)行一些繪制優(yōu)化。View 本身是默認(rèn)設(shè)置為 false 的,沒(méi)有啟動(dòng)這個(gè)優(yōu)化標(biāo)記(這也不難理解,因?yàn)橐话阄覀冏远x控件繼承 View 的時(shí)候,是要重寫(xiě) onDraw 方法進(jìn)行繪制的)。ViewGroup 默認(rèn)是開(kāi)啟這個(gè)優(yōu)化標(biāo)記的。當(dāng)然如果明確 ViewGroup 是要通過(guò) onDraw 方法進(jìn)行繪制的時(shí)候,要手動(dòng)關(guān)閉這個(gè)標(biāo)記( setWillNotDraw(false) )。
示例:
我們自定義一個(gè)控件,繼承 ViewGroup,重寫(xiě) onDraw 方法。
public class MyViewGroup extends ViewGroup {public MyViewGroup(Context context) {super(context);setWillNotDraw(false);}public MyViewGroup(Context context, AttributeSet attrs) {super(context, attrs);//這里如果不調(diào)用這句話(huà),我們?cè)谑褂玫臅r(shí)候,onDraw 方法不會(huì)被調(diào)用setWillNotDraw(false);}protected void onLayout(boolean changed, int l, int t, int r, int b) {//onLayout 在這里必須重寫(xiě),因?yàn)樵?ViewGroup 中 onLayout是一個(gè)抽象方法}//重寫(xiě) onDraw 方法protected void onDraw(Canvas canvas) {canvas.drawColor(Color.BLACK);} } 復(fù)制代碼xml 中使用
<?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"><com.ownchan.miclock.study.MyViewGroupandroid:layout_width="match_parent"android:background="@color/black"android:layout_height="match_parent"/> </FrameLayout> 復(fù)制代碼當(dāng)我們的自定義控件在繼承 ViewGroup 的時(shí)候,如果需要重寫(xiě) onDraw 方法進(jìn)行繪制,需要執(zhí)行 setWillNotDraw(false) 。
推薦一個(gè)詳解 draw 和 onDraw 調(diào)用時(shí)機(jī)好文: 你真的了解Android ViewGroup的draw和onDraw的調(diào)用時(shí)機(jī)嗎
總結(jié)
參考文檔
《Android開(kāi)發(fā)藝術(shù)探索》第四章-View的工作原理
自定義View Draw過(guò)程- 最易懂的自定義View原理系列(4)
ViewRootImpl的performDraw過(guò)程
你真的了解Android ViewGroup的draw和onDraw的調(diào)用時(shí)機(jī)嗎
淺談ondraw的前世今身
轉(zhuǎn)載于:https://juejin.im/post/5cc17280e51d4514df4206b4
總結(jié)
以上是生活随笔為你收集整理的View的绘制-draw流程详解的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: CentOS 7.6安装使用Ansibl
- 下一篇: 位运算和时间复杂度的分析