日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

View的绘制-draw流程详解

發(fā)布時(shí)間:2025/3/20 编程问答 52 豆豆
生活随笔 收集整理的這篇文章主要介紹了 View的绘制-draw流程详解 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

目錄

作用

根據(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 方法 @Override 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)條)
//View 類(lèi) /***手動(dòng)將此視圖(及其所有子項(xiàng))渲染到給定的Canvas。在調(diào)用此函數(shù)前,視圖必須已經(jīng)完成了完整布局(layout)。*一般我們?cè)谧远x控件繼承 View 的時(shí)候,不要重寫(xiě) draw 方法,只需重寫(xiě) onDraw 方法*/ public void draw(Canvas canvas) {....// Step 1, draw the background, if neededint 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// 繪制內(nèi)容if (!dirtyOpaque) onDraw(canvas);// Step 4, draw the children//繪制 children dispatchDraw(canvas);drawAutofilledHighlight(canvas);// Overlay is part of the content and draws beneath Foregroundif (mOverlay != null && !mOverlay.isEmpty()) {mOverlay.getOverlayView().dispatchDraw(canvas);}// Step 6, draw decorations (foreground, scrollbars)//繪制裝飾 (前景色,滾動(dòng)條)onDrawForeground(canvas);// Step 7, draw the default focus highlightdrawDefaultFocusHighlight(canvas);if (debugDraw()) {debugDrawFocus(canvas);}// we're done...return;}.... } 復(fù)制代碼

我們對(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) @Override 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);}@Overrideprotected void onLayout(boolean changed, int l, int t, int r, int b) {//onLayout 在這里必須重寫(xiě),因?yàn)樵?ViewGroup 中 onLayout是一個(gè)抽象方法}//重寫(xiě) onDraw 方法@Overrideprotected 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)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。