Android之自定義View的死亡三部曲之(Draw)


前言

  • 大家好!本次我們將繼續(xù)學(xué)習(xí)Android之自定義View的死亡三部曲中的最后一部(Draw):畫出最真實(shí)的自己
  • 在此之前,我們?cè)?a target="_blank" rel="nofollow">Android之自定義View的死亡三部曲之(Measure) 中分析了View測(cè)測(cè)量過程境析,獲得了View的三圍數(shù)據(jù)-測(cè)量后獲得高和寬拣播,在Android之自定義View的死亡三部曲之(Layout) 中分析了View的測(cè)量過程荡短,經(jīng)過測(cè)量后桌吃,我們就能拿到View的left、top榄攀、right嗜傅、bottom四個(gè)點(diǎn)的值。那么我們剩下最后一步檩赢,將我的的View繪制出來吕嘀。
  • Ok,這次我們依然是以ViewRootImpl的performTraversals方法起點(diǎn)贞瞒。
    private void performTraversals() {
          ...
            if (!mStopped) {
          //1偶房、獲取頂層布局的childWidthMeasureSpec
            int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);  
          //2、獲取頂層布局的childHeightMeasureSpec
            int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
            //3军浆、測(cè)量開始測(cè)量
            performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);       
            }
          } 

          if (didLayout) {
          //4棕洋、執(zhí)行布局方法
            performLayout(lp, desiredWindowWidth, desiredWindowHeight);
            ...
          }
          if (!cancelDraw && !newSurface) {
           ...
          //5、開始繪制了哦
                performDraw();
            }
          } 
        ...
      }
  • 這次我們分析到performDraw方法了乒融。好的掰盘,我們一起來看下performDraw里面的代碼吧,我只保留來于本次分析相關(guān)的關(guān)鍵代碼。
    private void performDraw() {
        ......
        //1簇抵、fullRedrawNeeded這個(gè)變量標(biāo)識(shí)了本次繪制是否需要完全重新繪制
        final boolean fullRedrawNeeded = mFullRedrawNeeded;
        try {
            //2庆杜、此處調(diào)用了ViewRootImpl的draw方法
            draw(fullRedrawNeeded);
        } finally {
            mIsDrawing = false;
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }

        ......
    }
  • 看1處,既然有完全繪制碟摆,當(dāng)然也會(huì)有局部繪制了,這樣做是為了提高性能
  • OK叨橱,我們看下draw這個(gè)方法里面的代碼
    private void draw(boolean fullRedrawNeeded) {
        ......
        //1典蜕、獲得dirty,也就是我們要繪制的區(qū)域
        final Rect dirty = mDirty;
        if (mSurfaceHolder != null) {
            // The app owns the surface, we won't draw.
            dirty.setEmpty();
            if (animating) {
                if (mScroller != null) {
                    mScroller.abortAnimation();
                }
                disposeResizeBuffer();
            }
            return;
        }

        //2罗洗、判斷是否需要完全繪制
        if (fullRedrawNeeded) {
            mAttachInfo.mIgnoreDirtyState = true;
            //3愉舔、需要完全繪制時(shí),將dirty的值設(shè)置為神歌屏幕的大小
            dirty.set(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
        }
        ......
        
         //3伙菜、調(diào)用drawSoftware進(jìn)行繪制
        if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
                    return;
            }
    }
  • 從上面的代碼分析中轩缤,我們看到,最后時(shí)通過調(diào)用drawSoftware進(jìn)行繪制贩绕,那么我們看下drawSoftware方法的代碼

      private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
                  boolean scalingRequired, Rect dirty) {
    
          //1火的、哈哈,看到了canvas淑倾,是不是感覺里繪制越來越近了
          final Canvas canvas;
          try {
              //2馏鹤、取出繪制區(qū)域的四個(gè)位置的值
              final int left = dirty.left;
              final int top = dirty.top;
              final int right = dirty.right;
              final int bottom = dirty.bottom;
    
              //3、傳入我們的繪制區(qū)域娇哆,創(chuàng)建一個(gè)被鎖定了繪制區(qū)域的canvas
              canvas = mSurface.lockCanvas(dirty);
    
              // The dirty rectangle can be modified by Surface.lockCanvas()
              //noinspection ConstantConditions
              if (left != dirty.left || top != dirty.top || right != dirty.right
                      || bottom != dirty.bottom) {
                  attachInfo.mIgnoreDirtyState = true;
              }
              //4湃累、設(shè)置畫布的密度
              canvas.setDensity(mDensity);
          } 
    
          try {
    
              if (!canvas.isOpaque() || yoff != 0 || xoff != 0) {
                  //5勃救、清除畫布的顏色
                  canvas.drawColor(0, PorterDuff.Mode.CLEAR);
              }
              
              dirty.setEmpty();
              mIsAnimating = false;
              attachInfo.mDrawingTime = SystemClock.uptimeMillis();
              mView.mPrivateFlags |= View.PFLAG_DRAWN;
    
              try {
                  //7、設(shè)置畫布的偏離值
                  canvas.translate(-xoff, -yoff);
                  if (mTranslator != null) {
                      mTranslator.translateCanvas(canvas);
                  }
                  canvas.setScreenDensity(scalingRequired ? mNoncompatDensity : 0);
                  attachInfo.mSetIgnoreDirtyState = false;
    
                  //8治力、調(diào)用mView大的draw方法開始繪制
                  mView.draw(canvas);
    
              }
          } 
          return true;
      }
    
  • Ok蒙秒,我們分析到第8步知道,最終調(diào)用了mView的draw開始繪制了宵统,而mView也就是DecorView晕讲,我們前面分析過DecorView是一個(gè)FrameLayout,而FrameLayout并沒有重現(xiàn)draw方法榜田,ViewGroup也沒有重寫益兄,所以,我們直接看View的draw方法箭券,代碼有點(diǎn)長(zhǎng)净捅,但是思路分清晰,官方給出的解釋也是非常清晰的

      @CallSuper
      public void draw(Canvas canvas) {
          final int privateFlags = mPrivateFlags;
           //(1)辩块、dirtyOpaque標(biāo)識(shí)了當(dāng)前View是否時(shí)透明的
          final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
                  (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
          mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
    
          /*
       * 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 *      4. Draw children *      5. If necessary, draw the fading edges and restore layers *      6. Draw decorations (scrollbars for instance) */
       //上面的解釋大知識(shí)蛔六,繪制過程中有一系列的步驟,但是有幾個(gè)是必須要執(zhí)行的
       //1废亭、繪制背景2国章、如果有需要,在可以先保存當(dāng)前canvas的層級(jí)數(shù)據(jù)豆村,3液兽、繪制View的內(nèi)容4、繪制View的子類5掌动、如果又需要四啰,在退出此次繪制時(shí)恢復(fù)之前的canvas的層級(jí)數(shù)據(jù)
       //6、繪制一些裝飾的效果
       // Step 1, draw the background, if needed  int saveCount;
          //(2)粗恢、透明時(shí)不需要繪制背景
          if (!dirtyOpaque) {
            //(3)柑晒、不透明時(shí),繪制背景
              drawBackground(canvas);
          }
          //他說可以跳過第2步和第5步眷射,說明第2和第5步時(shí)很重要
          // 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
              //(4)匙赞、如果不透明,繪制View的內(nèi)容
        if (!dirtyOpaque) onDraw(canvas);
    
              // Step 4, draw the children
          //(5)將canvas傳遞給childView妖碉,將繪制事件傳遞下去
        dispatchDraw(canvas);
    
              // Overlay is part of the content and draws beneath Foreground
        if (mOverlay != null && !mOverlay.isEmpty()) {
                  mOverlay.getOverlayView().dispatchDraw(canvas);
              }
    
              // Step 6, draw decorations (foreground, scrollbars)
        onDrawForeground(canvas);
    
              // we're done...
        return;
          }
    
      ......
      }
    
  • OK,第2步和第5步是保存canves狀態(tài)和恢復(fù)的操作涌庭,我們這次就分析其他步驟就好

  • 首先,我們看第1步嗅绸,在View非透明情況下脾猛,執(zhí)行背景的繪制操作

      private void drawBackground(Canvas canvas) {
          final Drawable background = mBackground;
          //1、背景為null當(dāng)然是直接返回了
          if (background == null) {
              return;
          }
          //2鱼鸠、確認(rèn)背景的邊界值
          setBackgroundBounds();
    
          // Attempt to use a display list if requested.
        if (canvas.isHardwareAccelerated() && mAttachInfo != null
        && mAttachInfo.mHardwareRenderer != null) {
              mBackgroundRenderNode = getDrawableRenderNode(background, mBackgroundRenderNode);
    
              final RenderNode renderNode = mBackgroundRenderNode;
              if (renderNode != null && renderNode.isValid()) {
                  setBackgroundRenderNodeProperties(renderNode);
                  ((DisplayListCanvas) canvas).drawRenderNode(renderNode);
                  return;
              }
          }
          //3猛拴、獲取當(dāng)前的scrollX和scrollY的值
          final int scrollX = mScrollX;
          final int scrollY = mScrollY;
          if ((scrollX | scrollY) == 0) {
              //此時(shí)沒有滾動(dòng)羹铅,開始繪制背景
              background.draw(canvas);
          } else {
              //正在滾動(dòng),移動(dòng)canvas后繪制
              canvas.translate(scrollX, scrollY);
              background.draw(canvas);
              canvas.translate(-scrollX, -scrollY);
          }
      }
    
  • 從上面的分析愉昆,我有又個(gè)意外的發(fā)現(xiàn)职员,當(dāng)scrllX或者scrollY的值不為0時(shí),先使canvas偏移后在繪制跛溉,這就是為什么如果我們是使用Scoller來實(shí)現(xiàn)View的滑動(dòng)時(shí)焊切,實(shí)際上移動(dòng)的是View的可視區(qū)域,而不是View本身

  • 我們看下setBackgroundBounds里面是如何確認(rèn)背景邊界的

      void setBackgroundBounds() {
          if (mBackgroundSizeChanged && mBackground != null) {
          //1芳室、直接根據(jù)layout中獲得的四個(gè)位置的值直接確定
              mBackground.setBounds(0, 0, mRight - mLeft, mBottom - mTop);
              mBackgroundSizeChanged = false;
              rebuildOutline();
          }
      }
    
  • 介紹完繪制背景专肪,我們接下來分析繪制內(nèi)容部分,我們看onDraw方法堪侯,沒錯(cuò)嚎尤,又是空的,因?yàn)檫@是我們?cè)谧远xView的時(shí)候需要自己去實(shí)現(xiàn)的

      protected void onDraw(Canvas canvas) {
      }
    
  • OK伍宦,那我們看下一步芽死,傳遞繪制事件給child們

  • 我們先看View的dispatchDraw,沒錯(cuò)次洼,還是空的关贵,View就是最原始的了,哪里有child嘛卖毁。

      protected void dispatchDraw(Canvas canvas) {
    
      }
    
  • 那么我們來看ViewGroup中的吧,源碼優(yōu)點(diǎn)長(zhǎng)揖曾,我保留于本次分析相關(guān)就好

      @Override
      protected void dispatchDraw(Canvas canvas) {
          boolean usingRenderNodeProperties = canvas.isRecordingFor(mRenderNode);
          
          //1、獲取child的數(shù)據(jù)
          final int childrenCount = mChildrenCount;
          final View[] children = mChildren;
          int flags = mGroupFlags;
    
          ......
          for (int i = 0; i < childrenCount; i++) {
              ......
    
              final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
              final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
              if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
              
                  //2亥啦、調(diào)用drawChild傳遞canvas翩肌、child進(jìn)去繪制child
                  more |= drawChild(canvas, child, drawingTime);
              }
          }
          ......
      }
    
  • ok,重點(diǎn)是drawChild這個(gè)方法禁悠,我們看下drawChild里面做什么操作

      protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
          return child.draw(canvas, this, drawingTime);
      }
    
  • 里面直接調(diào)用了child的draw方法。不過這個(gè)方法跟我們前面的分析的draw有點(diǎn)區(qū)別哦兑宇,沒錯(cuò)碍侦,參數(shù)個(gè)數(shù)不同,那么我們看下到底卻別在哪呢,這個(gè)方法的代碼很長(zhǎng)隶糕,我截取關(guān)鍵代碼

      boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
    
          ......
          if (!drawingWithDrawingCache) {
              if (drawingWithRenderNode) {
                  mPrivateFlags &= ~PFLAG_DIRTY_MASK;
                  ((DisplayListCanvas) canvas).drawRenderNode(renderNode);
              } else {
                  if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
                      mPrivateFlags &= ~PFLAG_DIRTY_MASK;
                      dispatchDraw(canvas);
                  } else {
                  // 1瓷产、這里調(diào)用子View的draw方法,并將調(diào)整好的canvas傳進(jìn)去
                      draw(canvas);
                  }
              }
          } else if (cache != null) 
              // 2枚驻、如果是cache模式濒旦,則利用cache
              mPrivateFlags &= ~PFLAG_DIRTY_MASK;
              if (layerType == LAYER_TYPE_NONE) {
                  Paint cachePaint = parent.mCachePaint;
                  if (cachePaint == null) {
                      cachePaint = new Paint();
                      cachePaint.setDither(false);
                      parent.mCachePaint = cachePaint;
                  }
                  cachePaint.setAlpha((int) (alpha * 255));
                  canvas.drawBitmap(cache, 0.0f, 0.0f, cachePaint);
              } else {
                  int layerPaintAlpha = mLayerPaint.getAlpha();
                  mLayerPaint.setAlpha((int) (alpha * layerPaintAlpha));
                  canvas.drawBitmap(cache, 0.0f, 0.0f, mLayerPaint);
                  mLayerPaint.setAlpha(layerPaintAlpha);
              }
          }
          ......
    
      }
    
  • 上面主要做的事情就是,如果有cache再登,就利用cache進(jìn)行繪制尔邓,沒有則直接調(diào)用View的draw方法晾剖。然后根據(jù)前面的分析,最終會(huì)調(diào)用個(gè)個(gè)View的onDraw進(jìn)行繪制操作

  • 接下來到了第六部梯嗽,繪制裝飾物(例如recyclerView的滾動(dòng)條)齿尽,OK,我們來看下onDrawForeground方法

      public void onDrawForeground(Canvas canvas) {
          //1灯节、繪制滾動(dòng)指示器
          onDrawScrollIndicators(canvas);
          //2循头、繪制滾動(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);
              }
              //繪制foreground
              foreground.draw(canvas);
          }
      }
    
  • 通過以上的分析,我們就把View的Draw分析完了炎疆,


總結(jié):好吧卡骂,直接上一個(gè)收集的時(shí)序圖

123.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市形入,隨后出現(xiàn)的幾起案子全跨,更是在濱河造成了極大的恐慌,老刑警劉巖唯笙,帶你破解...
    沈念sama閱讀 212,383評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件螟蒸,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡崩掘,警方通過查閱死者的電腦和手機(jī)七嫌,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來苞慢,“玉大人诵原,你說我怎么就攤上這事⊥旆牛” “怎么了绍赛?”我有些...
    開封第一講書人閱讀 157,852評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)辑畦。 經(jīng)常有香客問我吗蚌,道長(zhǎng),這世上最難降的妖魔是什么纯出? 我笑而不...
    開封第一講書人閱讀 56,621評(píng)論 1 284
  • 正文 為了忘掉前任蚯妇,我火速辦了婚禮,結(jié)果婚禮上暂筝,老公的妹妹穿的比我還像新娘箩言。我一直安慰自己,他們只是感情好焕襟,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,741評(píng)論 6 386
  • 文/花漫 我一把揭開白布陨收。 她就那樣靜靜地躺著,像睡著了一般鸵赖。 火紅的嫁衣襯著肌膚如雪务漩。 梳的紋絲不亂的頭發(fā)上拄衰,一...
    開封第一講書人閱讀 49,929評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音菲饼,去河邊找鬼肾砂。 笑死,一個(gè)胖子當(dāng)著我的面吹牛宏悦,可吹牛的內(nèi)容都是我干的镐确。 我是一名探鬼主播,決...
    沈念sama閱讀 39,076評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼饼煞,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼源葫!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起砖瞧,我...
    開封第一講書人閱讀 37,803評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤息堂,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后块促,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體荣堰,經(jīng)...
    沈念sama閱讀 44,265評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,582評(píng)論 2 327
  • 正文 我和宋清朗相戀三年竭翠,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了振坚。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,716評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡斋扰,死狀恐怖渡八,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情传货,我是刑警寧澤屎鳍,帶...
    沈念sama閱讀 34,395評(píng)論 4 333
  • 正文 年R本政府宣布,位于F島的核電站问裕,受9級(jí)特大地震影響逮壁,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜粮宛,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,039評(píng)論 3 316
  • 文/蒙蒙 一貌踏、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧窟勃,春花似錦、人聲如沸逗堵。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)蜒秤。三九已至汁咏,卻和暖如春亚斋,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背攘滩。 一陣腳步聲響...
    開封第一講書人閱讀 32,027評(píng)論 1 266
  • 我被黑心中介騙來泰國(guó)打工帅刊, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人漂问。 一個(gè)月前我還...
    沈念sama閱讀 46,488評(píng)論 2 361
  • 正文 我出身青樓赖瞒,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親蚤假。 傳聞我的和親對(duì)象是個(gè)殘疾皇子栏饮,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,612評(píng)論 2 350

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