View的绘制-draw流程详解
目錄
作用
根據 measure 測量出的寬高,layout 布局的位置,渲染整個 View 樹,將界面呈現出來。
具體分析
以下源碼基于版本27
DecorView 的draw 流程
在《View的繪制-measure流程詳解》中說過,View 的繪制流程是從 ViewRootViewImpl 中的 performMeasure()、performLayout、performDraw 開始的。在執行完 performMeasure() 、performLayout 后,開始執行 performDraw 方法:(以下源碼有所刪減)
//ViewRootViewImpl 類private void performDraw() {....draw(fullRedrawNeeded);....} ------------------------------------------------------------------------- //ViewRootViewImpl 類 private void draw(boolean fullRedrawNeeded) {....mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);.... }------------------------------------------------------------------------- //ThreadedRenderer 類 void draw(View view, AttachInfo attachInfo, DrawCallbacks callbacks) {....updateRootDisplayList(view, callbacks);.... } ------------------------------------------------------------------------- //ThreadedRenderer 類 private void updateRootDisplayList(View view, DrawCallbacks callbacks) {....updateViewTreeDisplayList(view);.... } ------------------------------------------------------------------------- //ThreadedRenderer 類 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;//這里調用了 View 的 updateDisplayListIfDirty 方法 //這個 View 其實就是 DecorViewview.updateDisplayListIfDirty();view.mRecreateDisplayList = false; } 復制代碼接下來查看 View 的 updateDisplayListIfDirty 方法:
//View 類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 {/*最終調用了 DecorView 的 draw 方法,為什么沒有走上面的 dispatchDraw(canvas)我也母雞啊,我是Debug 斷點調試曉得走這里的,哈哈*/draw(canvas);}.... } ------------------------------------------------------------------------- //DecorView 重寫了 draw 方法。所以走到了 DecorView 的 draw 方法 public void draw(Canvas canvas) {//調用父類 (View)的 draw 方法super.draw(canvas);if (mMenuBackground != null) {mMenuBackground.draw(canvas);} } 復制代碼以上流程,推薦兩篇文章:ViewRootImpl的performDraw過程 ~~~~~~~~~~~~~~~~~淺談ondraw的前世今身
View 的 draw 流程
就這樣, View 的繪制就開始啦。主要有四個步驟:
- drawBackground 繪制背景色
- onDraw 繪制內容
- dispatchDraw 繪制 children
- onDrawForeground 繪制裝飾(前景,滾動條)
我們對四個步驟進行分析:
//View 類 //繪制背景 private void drawBackground(Canvas canvas) {final Drawable background = mBackground;//如果沒有設置背景,就不進行繪制if (background == null) {return;}//如果設置了背景嗎,且背景的大小發生了改變,//就用 layout 計算出的四個邊界值來確定背景的邊界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) {//調用 Drawable 的 draw 方法來進行背景的繪制background.draw(canvas);} else {//平移畫布canvas.translate(scrollX, scrollY);//調用 Drawable 的 draw 方法來進行背景的繪制background.draw(canvas);canvas.translate(-scrollX, -scrollY);} } ------------------------------------------------------------------------- //View 類 //繪制內容 protected void onDraw(Canvas canvas) {/*View 中的 onDraw 是一個空實現。也不難理解,當我們自定義控件繼承 View 的時候,需要重寫 onDraw 方法,通過 Canvas 和 Paint 來進行內容的繪制*/ } ------------------------------------------------------------------------- //View 類 //繪制 children protected void dispatchDraw(Canvas canvas) {/*View 中的 dispatchDraw 也是一個空實現。因為單獨一個 View 本身是沒有子元素的,不需要繪制 children */ }------------------------------------------------------------------------- //View 類 //繪制裝飾 public void onDrawForeground(Canvas canvas) {//繪制指示器onDrawScrollIndicators(canvas);//繪制滾動條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);}//調用 Drawable 的 draw 方法,繪制前景色foreground.draw(canvas);} } 復制代碼以上就是 View 的繪制流程了。ViewGroup 本身是繼承 View 的,它的基本繪制流程也是通過父類 View 進行的,只不過它重寫了 dispatchDraw 方法,來進行子元素的繪制。下面我們來進行具體分析:
ViewGroup 的繪制 dispatchDraw 流程
//ViewGroup 類 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) {//調用 drawChild 方法,進行繪制子元素more |= drawChild(canvas, child, drawingTime);}}.... } ------------------------------------------------------------------------- //ViewGroup 類 protected boolean drawChild(Canvas canvas, View child, long drawingTime) {//調用 View 的 draw 方法,這里要注意,調用的是 View 的三個參數的 draw 方法return child.draw(canvas, this, drawingTime); }復制代碼在 View 中還有一個 draw(Canvas canvas) 的重載方法,就是 draw(Canvas canvas, ViewGroup parent, long drawingTime):
//View 類 /*** ViewGroup.drawChild()調用此方法以使每個子視圖自己繪制。* 這是View專門根據圖層類型和硬件加速來渲染行為的地方。*/ 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) {//未開啟//調用 View 的 buildDrawingCache 方法buildDrawingCache(true);}cache = getDrawingCache(true);}//開啟了硬件加速if (drawingWithRenderNode) {//調用 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;}}.... } 復制代碼分別查看 buildDrawingCache 和 updateDisplayListIfDirty 方法:
//View 類 public void buildDrawingCache(boolean autoScale) {....buildDrawingCacheImpl(autoScale);.... } ------------------------------------------------------------------------- //View 類 private void buildDrawingCacheImpl(boolean autoScale) {....// 如果不需要進行自身繪制,就直接調用 dispatchDraw 繪制子 Children//否則就直接調用 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 類 public RenderNode updateDisplayListIfDirty() {....// 如果不需要進行自身繪制,就直接調用 dispatchDraw 繪制子 Children//否則就直接調用 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);}.... } 復制代碼如此,從頂層 DecorView 的 draw 方法開始,然后調用 dispatchDraw 方法循環遍歷繪制子元素,如果子元素是繼承了 ViewGroup ,就再次循環調用 dispatchDraw 方法,一層層往下遞歸調用,直到每一個子元素都被繪制完成,整個 draw 流程也就結束了。
tips: 在我們使用真機進行源碼斷點調試的時候,可能會出現源碼不能打斷點的情況,或者斷點沒有走在該走的地方。這是因為國內手機廠商基本都是定制系統,可能修改了源碼。這個時候可以使用模擬器進行斷點調試。注意:模擬器版本號要和項目編譯版本號一致!
setWillNotDraw 解析
在 View 中有一個方法是 setWillNotDraw:
//View 類 /*** 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); } 復制代碼從注釋上看,如果此視圖本身不執行任何繪制,就設置為 true,系統會進行一些繪制優化。View 本身是默認設置為 false 的,沒有啟動這個優化標記(這也不難理解,因為一般我們自定義控件繼承 View 的時候,是要重寫 onDraw 方法進行繪制的)。ViewGroup 默認是開啟這個優化標記的。當然如果明確 ViewGroup 是要通過 onDraw 方法進行繪制的時候,要手動關閉這個標記( setWillNotDraw(false) )。
示例:
我們自定義一個控件,繼承 ViewGroup,重寫 onDraw 方法。
public class MyViewGroup extends ViewGroup {public MyViewGroup(Context context) {super(context);setWillNotDraw(false);}public MyViewGroup(Context context, AttributeSet attrs) {super(context, attrs);//這里如果不調用這句話,我們在使用的時候,onDraw 方法不會被調用setWillNotDraw(false);}protected void onLayout(boolean changed, int l, int t, int r, int b) {//onLayout 在這里必須重寫,因為在 ViewGroup 中 onLayout是一個抽象方法}//重寫 onDraw 方法protected void onDraw(Canvas canvas) {canvas.drawColor(Color.BLACK);} } 復制代碼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> 復制代碼當我們的自定義控件在繼承 ViewGroup 的時候,如果需要重寫 onDraw 方法進行繪制,需要執行 setWillNotDraw(false) 。
推薦一個詳解 draw 和 onDraw 調用時機好文: 你真的了解Android ViewGroup的draw和onDraw的調用時機嗎
總結
參考文檔
《Android開發藝術探索》第四章-View的工作原理
自定義View Draw過程- 最易懂的自定義View原理系列(4)
ViewRootImpl的performDraw過程
你真的了解Android ViewGroup的draw和onDraw的調用時機嗎
淺談ondraw的前世今身
轉載于:https://juejin.im/post/5cc17280e51d4514df4206b4
總結
以上是生活随笔為你收集整理的View的绘制-draw流程详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: CentOS 7.6安装使用Ansibl
- 下一篇: Linux中一切皆文件