成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

粗談繪制任務(wù)和繪制流程

開(kāi)發(fā) 項(xiàng)目管理
當(dāng)有繪制任務(wù)的時(shí)候,會(huì)將這個(gè)任務(wù)交給Choreographer,然后再等下一個(gè)VSync信號(hào)來(lái)的時(shí)候,執(zhí)行到ViewRootImpl的performTraversals方法。

[[395776]]

前言

今天是2028年4月26日,天氣晴,我請(qǐng)了一天假在家陪女兒。

正在陪女兒畫畫的我,被女兒?jiǎn)柕剑?/p>

??:“爸爸,媽媽說(shuō)你的工作是可以把我們想到的東西變到手機(jī)上,是這樣嗎?”

??:“對(duì)呀,厲害吧~”

??:“那你可以把我們家的小狗狗變到手機(jī)上嗎?”

??:“當(dāng)然可以了,不過(guò)手機(jī)是很笨的東西,必須我們把所有的規(guī)則寫好,他才能聽(tīng)我們的話~”

??:“什么規(guī)則呀“

簡(jiǎn)述繪制流程

你看,手機(jī)屏幕只有這么大,所以我們先要確定狗狗的大小,該畫多大的狗狗,可以畫多大的狗狗。

這就是測(cè)量的過(guò)程。

接著,我們要確定狗狗放在哪里,左上角還是中間還是右下角?

這就是布局的過(guò)程。

最后,我們就要畫出狗狗的樣子,是斑點(diǎn)狗還是大狼狗,是小白狗還是小黑狗。

這就是繪畫的過(guò)程。

所以,在手機(jī)上變出一只狗狗,或者變出任何一個(gè)東西都需要三個(gè)步驟:

  • 測(cè)量(measure)
  • 布局(layout)
  • 繪畫(draw)

繪制任務(wù)的來(lái)源

把視線拉回到成年人的世界。

第一次界面繪制

上篇文章說(shuō)到,當(dāng)有繪制任務(wù)的時(shí)候,會(huì)將這個(gè)任務(wù)交給Choreographer,然后再等下一個(gè)VSync信號(hào)來(lái)的時(shí)候,執(zhí)行到ViewRootImpl的performTraversals方法。

那么這個(gè)任務(wù)到底從何而來(lái)呢?回顧下Activity的顯示過(guò)程:

  • 首先在setContentView方法中,創(chuàng)建了DecorView。
  • 然后在handleResumeActivity方法中,執(zhí)行了addView方法將DecorView添加到WindowManager。
  • 最后設(shè)置DecorView對(duì)用戶可見(jiàn)。

所以在第二步addView方法中,肯定進(jìn)行了與View繪制有關(guān)的操作:

  1. //WindowManagerGlobal.java 
  2.  public void addView() { 
  3.         synchronized (mLock) { 
  4.             root = new ViewRootImpl(view.getContext(), display); 
  5.             view.setLayoutParams(wparams); 
  6.             mViews.add(view); 
  7.             mRoots.add(root); 
  8.             mParams.add(wparams); 
  9.             try { 
  10.                 root.setView(view, wparams, panelParentView); 
  11.             }  
  12.         } 
  13.     } 
  14.  
  15.     //ViewRootImpl.java 
  16.     public void setView() { 
  17.         synchronized (this) { 
  18.          //繪制 
  19.          requestLayout(); 
  20.          //調(diào)用WMS的addWindow方法 
  21.          res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes, 
  22.                             getHostVisibility(), mDisplay.getDisplayId(), mWinFrame, 
  23.                             mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, 
  24.                             mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel); 
  25.          //設(shè)置this(ViewRootImpl)為view(decorView)的parent 
  26.    view.assignParent(this); 
  27.         } 
  28.     } 
  29.  
  30.  
  31.     //ViewRootImpl.java 
  32.     @Override 
  33.     public void requestLayout() { 
  34.         if (!mHandlingLayoutInLayoutRequest) { 
  35.             checkThread(); 
  36.             mLayoutRequested = true
  37.             scheduleTraversals(); 
  38.         } 
  39.     } 
  40.  
  41.     ->scheduleTraversals() 
  42.     ->performMeasure() performLayout() performDraw() 
  43.     ->measure、layout、draw方法 

在addView方法中,創(chuàng)建了ViewRootImpl,執(zhí)行了setView方法,在這里調(diào)用了requestLayout方法開(kāi)始了View的繪制工作。

所以這里就是Activity顯示界面所做的第一次繪制來(lái)源。

那后續(xù)界面上的元素改變帶來(lái)的繪制呢?

View.requestLayout

首先看看在View中調(diào)用requestLayout方法會(huì)怎么繪制,比如TextView.setText,最后就會(huì)執(zhí)行到requestLayout

  1. //View.java 
  2. public void requestLayout() { 
  3.         
  4.  //設(shè)置兩個(gè)標(biāo)志位 
  5.        mPrivateFlags |= PFLAG_FORCE_LAYOUT; 
  6.        mPrivateFlags |= PFLAG_INVALIDATED; 
  7.  
  8.        //執(zhí)行父view的requestLayout方法 
  9.        if (mParent != null && !mParent.isLayoutRequested()) { 
  10.            mParent.requestLayout(); 
  11.        } 
  12.    } 

精簡(jiǎn)之后的代碼,主要干了兩件事:

1、設(shè)置兩個(gè)標(biāo)志位,PFLAG_FORCE_LAYOUT 和 PFLAG_INVALIDATED。

2、執(zhí)行父View的requestLayout方法。

這里的標(biāo)志位暫且按下不表,待會(huì)就會(huì)遇到。從第二點(diǎn)可以看到View會(huì)一直向上執(zhí)行requestLayout方法,而頂層的View就是DecorView,DecorView的parent就是ViewRootImpl。

所以最后還是執(zhí)行到了ViewRootImpl的requestLayout方法,開(kāi)始整個(gè)View樹(shù)的 測(cè)量、布局、繪畫。

  1. //ViewRootImpl.java 
  2.     @Override 
  3.     public void requestLayout() { 
  4.         if (!mHandlingLayoutInLayoutRequest) { 
  5.             checkThread(); 
  6.             mLayoutRequested = true
  7.             scheduleTraversals(); 
  8.         } 
  9.     } 

其中,mLayoutRequested字段設(shè)置為true,這是第二個(gè)標(biāo)志位,待會(huì)也會(huì)遇到。

但是這有點(diǎn)奇怪哦?我一個(gè)View改變了,為什么整個(gè)界面的View樹(shù)都需要重新繪制呢?

這是因?yàn)槊總€(gè)子View直接或多或少都會(huì)產(chǎn)生聯(lián)系,比如一個(gè)RelativeLayout,一個(gè)View在TextView的右邊,一個(gè)View在TextView的下面。

那么當(dāng)TextView長(zhǎng)度寬度變化了,那么其他的View自然也需要跟著變化,所以就必須整個(gè)View樹(shù)進(jìn)行重新繪制,保證布局的完整性。

View.invalidate/postInvalidate

還有一種觸發(fā)繪制的情況就是View.invalidate/postInvalidate,postInvalidate一般用于子線程,最后也會(huì)調(diào)用到invalidate方法,就不單獨(dú)說(shuō)了。

invalidate方法一般用于View內(nèi)部的重新繪畫,比如同樣是TextView.setText,也會(huì)觸發(fā)invalidate方法。

  1. public void invalidate(boolean invalidateCache) { 
  2.         invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, invalidateCache, true); 
  3.     } 
  4.  
  5.  void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache, 
  6.             boolean fullInvalidate) { 
  7.  
  8.         if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS) 
  9.                 || (invalidateCache && (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID) 
  10.                 || (mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED 
  11.                 || (fullInvalidate && isOpaque() != mLastIsOpaque)) { 
  12.  
  13.             mPrivateFlags |= PFLAG_DIRTY; 
  14.  
  15.             final ViewParent p = mParent; 
  16.             if (p != null && ai != null && l < r && t < b) { 
  17.                 damage.set(l, t, r, b); 
  18.                 p.invalidateChild(this, damage); 
  19.             } 
  20.  
  21.         } 
  22.     } 

可以看到,這里調(diào)用了invalidateInternal方法,并且傳入了可繪制的區(qū)域,最后調(diào)用了父view的invalidateChild方法。

  1. public final void invalidateChild(View child, final Rect dirty) { 
  2.         ViewParent parent = this; 
  3.         if (attachInfo != null) { 
  4.             do { 
  5.                 parent = parent.invalidateChildInParent(location, dirty); 
  6.             } while (parent != null); 
  7.         } 
  8.     } 

一個(gè)dowhile循環(huán),不斷調(diào)用父View的invalidateChildInParent方法。

也就是會(huì)執(zhí)行ViewGroup的invalidateChildInParent,最后再執(zhí)行ViewRootImpl的invalidateChildInParent方法,我們就直接看ViewRootImpl:

  1. //ViewRootImpl.java 
  2.  public ViewParent invalidateChildInParent(int[] location, Rect dirty) { 
  3.          
  4.         invalidateRectOnScreen(dirty); 
  5.         return null
  6.     } 
  7.  
  8.     private void invalidateRectOnScreen(Rect dirty) { 
  9.         if (!mWillDrawSoon && (intersected || mIsAnimating)) { 
  10.             scheduleTraversals(); 
  11.         } 
  12.     } 

完事,果不其然,又到了scheduleTraversals繪制方法。

(這其中還有很多關(guān)于Dirty區(qū)域的繪制和轉(zhuǎn)換我省略了,Dirty區(qū)域就是需要重新繪圖的區(qū)域)

那invalidate和requestLayout有什么區(qū)別呢?繼續(xù)研究scheduleTraversals方法。

peformTraversals

接下來(lái)就看看peformTraversals方法是怎么觸發(fā)到三大繪制流程的。

  1. private void performTraversals() { 
  2.  boolean layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw); 
  3.  //測(cè)量 
  4.  if (layoutRequested) { 
  5.         windowSizeMayChange |= measureHierarchy(host, lp, res, 
  6.                     desiredWindowWidth, desiredWindowHeight); 
  7.     } 
  8.  
  9.     //布局 
  10.     final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw); 
  11.     if (didLayout) { 
  12.         performLayout(lp, mWidth, mHeight); 
  13.     } 
  14.  
  15.     //繪畫 
  16.     boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible; 
  17.     if (!cancelDraw) { 
  18.         performDraw(); 
  19.     } 

我只保留了與三大繪制流程相關(guān)的直接代碼,可以看到:

1、測(cè)量過(guò)程的前提是layoutRequested為true,與mLayoutRequested有關(guān)。

2、布局過(guò)程的前提是didLayout,也與mLayoutRequested有關(guān)。

3、繪畫過(guò)程的前提是!cancelDraw。

而mLayoutRequested字段是在requestlayout方法中進(jìn)行設(shè)置的,invalidate方法中并沒(méi)有設(shè)置。所以我們可以初步斷定,只有requestLayout方法才會(huì)執(zhí)行到onMeasure和onLayout。

測(cè)量(measureHierarchy)

  1. private boolean measureHierarchy() { 
  2.  
  3.         childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width); 
  4.         childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height); 
  5.         performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); 
  6.  
  7.         return windowSizeMayChange; 
  8.     } 
  9.  
  10.  private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) { 
  11.         try { 
  12.             mView.measure(childWidthMeasureSpec, childHeightMeasureSpec); 
  13.         }  
  14.     } 
  15.  
  16.  public final void measure(int widthMeasureSpec, int heightMeasureSpec) { 
  17.         final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT; 
  18.         final boolean needsLayout = specChanged 
  19.                 && (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize); 
  20.  
  21.         if (forceLayout || needsLayout) { 
  22.             // first clears the measured dimension flag 
  23.             onMeasure(widthMeasureSpec, heightMeasureSpec); 
  24.  
  25.             mPrivateFlags |= PFLAG_LAYOUT_REQUIRED; 
  26.         } 
  27.  
  28.     } 

在measure方法中,我們判斷了兩個(gè)字段forceLayout和needsLayout,當(dāng)其中有一個(gè)為true的時(shí)候,才會(huì)繼續(xù)執(zhí)行onMeasure。其中forceLayout字段代表的是mPrivateFlags標(biāo)志位是不是PFLAG_FORCE_LAYOUT。

PFLAG_FORCE_LAYOUT?是不是有點(diǎn)熟悉。剛才在View.requestLayout方法中,就對(duì)每個(gè)View都設(shè)置了這個(gè)標(biāo)志,所以才能觸發(fā)到onMeasure進(jìn)行測(cè)量。

所以requestLayout方法通過(guò)這個(gè)標(biāo)志位 PFLAG_FORCE_LAYOUT,使每個(gè)子View都能進(jìn)入到onMeasure流程。

布局(performLayout)

  1. private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, 
  2.            int desiredWindowHeight) { 
  3.        final View host = mView; 
  4.        host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight()); 
  5.    } 
  6.  
  7.    public void layout(int l, int t, int r, int b) { 
  8.   
  9.        if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) { 
  10.            onLayout(changed, l, t, r, b); 
  11.        } 
  12.        
  13.    } 

可以看到在layout方法中,是通過(guò)PFLAG_LAYOUT_REQUIRED標(biāo)記來(lái)決定是否執(zhí)行onLayout方法,而這個(gè)標(biāo)記是在onMeasure方法執(zhí)行之后設(shè)置的。

說(shuō)明了只要onMeasure方法執(zhí)行了,那么onLayout方法肯定也會(huì)執(zhí)行,這兩個(gè)方法是兄弟伙的關(guān)系,有你就有我。

繪畫(performDraw)

  1. private void performDraw() { 
  2.        boolean canUseAsync = draw(fullRedrawNeeded); 
  3.    } 
  4.  
  5.    private boolean draw(boolean fullRedrawNeeded){ 
  6.     if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) { 
  7.      if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, 
  8.                        scalingRequired, dirty, surfaceInsets)) { 
  9.                    return false
  10.          } 
  11.      } 
  12.         return useAsyncReport; 
  13.  
  14.    } 
  15.  
  16.    private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff, 
  17.            boolean scalingRequired, Rect dirty, Rect surfaceInsets) { 
  18.  
  19.        mView.draw(canvas); 
  20.        return true
  21.    } 
  22.  
  23.    public void draw(Canvas canvas) { 
  24.        final int privateFlags = mPrivateFlags; 
  25.        mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN; 
  26.  
  27.        /* 
  28.         * Draw traversal performs several drawing steps which must be executed 
  29.         * in the appropriate order
  30.         * 
  31.         *      1. Draw the background 
  32.         *      2. If necessary, save the canvas' layers to prepare for fading 
  33.         *      3. Draw view's content 
  34.         *      4. Draw children 
  35.         *      5. If necessary, draw the fading edges and restore layers 
  36.         *      6. Draw decorations (scrollbars for instance) 
  37.         */ 
  38.  
  39.        // Step 1, draw the background, if needed 
  40.        drawBackground(canvas); 
  41.  
  42.        // Step 2, save the canvas' layers 
  43.        canvas.saveUnclippedLayer.. 
  44.  
  45.        // Step 3, draw the content 
  46.        onDraw(canvas); 
  47.  
  48.        // Step 4, draw the children 
  49.        dispatchDraw(canvas); 
  50.  
  51.        // Step 5, draw the fade effect and restore layers 
  52.        canvas.drawRect.. 
  53.  
  54.        // Step 6, draw decorations (foreground, scrollbars) 
  55.        onDrawForeground(canvas); 
  56.  
  57.    } 

先看第二步draw(boolean fullRedrawNeeded)方法:

在該方法中,判斷了dirty是否為空,只有不為空的話才會(huì)繼續(xù)執(zhí)行下去。dirty是什么?剛才也說(shuō)過(guò),就是需要重繪的區(qū)域。

而我們調(diào)用invalidate方法的目的就是向上傳遞dirty區(qū)域,最終生成屏幕上需要重繪的dirty,requestLayout方法中并沒(méi)有對(duì)dirty區(qū)域進(jìn)行設(shè)定。

繼續(xù)看draw(Canvas canvas)方法,注釋還是比較清晰的,一共分為了六步:

  • 1、繪制背景
  • 2、保存圖層信息
  • 3、繪制內(nèi)容(onDraw)
  • 4、繪制children
  • 5、繪制邊緣
  • 6、繪制裝飾

而我們常用的onDraw就是用于繪制內(nèi)容。

總結(jié)

到此,View的繪制大體流程就結(jié)束了。

當(dāng)然,其中還有大量細(xì)節(jié),比如具體的繪制流程、需要注意的細(xì)節(jié)、自定義View實(shí)現(xiàn)等等,我們后面慢慢說(shuō)道。

之前我們的問(wèn)題,現(xiàn)在也可以解答了,就是繪制的兩個(gè)請(qǐng)求:requestLayout和invalidate區(qū)別是什么?

  • requestLayout方法。會(huì)依次執(zhí)行performMeasure、performLayout、performDraw,但在performDraw方法中由于沒(méi)有dirty區(qū)域,一般情況下是不會(huì)執(zhí)行onDraw。也有特殊情況,比如頂點(diǎn)發(fā)生變化。
  • invalidate方法。由于沒(méi)有設(shè)置標(biāo)示,只會(huì)走onDraw流程進(jìn)行dirty區(qū)域重繪。

所以如果某個(gè)元素的改變涉及到寬高布局的改變,就需要執(zhí)行requestLayout()。如果某個(gè)元素之需要內(nèi)部區(qū)域進(jìn)行重新繪制,就執(zhí)行invalidate().

如果都需要,就先執(zhí)行requestLayout(),在執(zhí)行invalidate(),比如TextView.setText()。

參考

https://www.jianshu.com/p/e79a55c141d6

https://juejin.cn/post/6904518722564653070

本文轉(zhuǎn)載自微信公眾號(hào)「碼上積木」,可以通過(guò)以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系碼上積木公眾號(hào)。

 

責(zé)任編輯:武曉燕 來(lái)源: 碼上積木
相關(guān)推薦

2017-08-21 21:36:23

AndroidViewJava

2023-08-23 19:21:38

流程圖時(shí)序圖UML

2013-05-23 14:50:55

2020-03-02 18:56:03

PythonGNU Octave編程語(yǔ)言

2010-06-09 19:25:54

UML活動(dòng)圖

2011-02-18 11:22:01

2022-10-18 23:53:20

Python數(shù)據(jù)Matplotlib

2010-06-11 10:55:51

UML部署圖

2020-07-28 21:38:24

跨職能流程圖

2010-06-09 08:59:30

UML活動(dòng)圖

2013-04-15 14:23:21

2019-08-05 13:20:35

Android繪制代碼

2021-09-30 07:36:51

AndroidViewDraw

2011-08-17 14:32:44

iOS開(kāi)發(fā)繪制

2020-07-16 08:33:38

ViewGroupView

2015-07-22 10:57:36

watchOS圖表自定義

2022-07-15 16:04:22

R 語(yǔ)言

2010-06-08 10:35:38

UML圖

2010-06-08 10:51:48

UML活動(dòng)圖

2023-05-08 09:08:33

CSS前端
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: 亚洲国产中文字幕 | 中文字幕在线精品 | 狠狠躁夜夜躁人人爽天天高潮 | 精品国产一区二区在线 | 久热国产精品视频 | 成人区精品一区二区婷婷 | 成人av一区二区在线观看 | av中文在线 | 国产视频1 | 黄色欧美大片 | av一区二区三区 | 国产精品亚洲二区 | 91丨九色丨国产在线 | 免费看av大片 | 精品国产乱码久久久久久果冻传媒 | 国产精品成人在线播放 | 成人一区二区三区 | 色精品视频 | 91亚洲国产精品 | 成人在线视频一区二区三区 | 日韩国产高清在线观看 | 国产成人自拍av | 精品福利在线 | 亚洲成年在线 | 亚洲品质自拍视频网站 | 成人精品一区亚洲午夜久久久 | 日韩免费视频一区二区 | 三级国产三级在线 | 精品一区免费 | 亚洲一级在线 | 久久精品免费 | 久久久久久成人 | 在线免费看黄 | 亚洲一区二区三区在线播放 | 亚洲午夜精品在线观看 | 色综合区| 国产精品国产a级 | 8x国产精品视频一区二区 | 欧美一区二区三区日韩 | 免费黄色片视频 | 自拍偷拍av|