• 文章独家授权公众号:码个蛋
  • 更多分享:http://www.cherylgood.cn

前言

  • 大家好!本次我们将继续学习Android之自定义View的死亡三部曲中的最后一部(Draw):画出最真实的自己
  • 在此之前,我们在Android之自定义View的死亡三部曲之(Measure) 中分析了View测测量过程,获得了View的三围数据-测量后获得高和宽,在Android之自定义View的死亡三部曲之(Layout) 中分析了View的测量过程,经过测量后,我们就能拿到View的left、top、right、bottom四个点的值。那么我们剩下最后一步,将我的的View绘制出来。
  • Ok,这次我们依然是以ViewRootImpl的performTraversals方法起点。
    private void performTraversals() {          ...            if (!mStopped) {          //1、获取顶层布局的childWidthMeasureSpec            int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);            //2、获取顶层布局的childHeightMeasureSpec            int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);            //3、测量开始测量            performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);                   }          }           if (didLayout) {          //4、执行布局方法            performLayout(lp, desiredWindowWidth, desiredWindowHeight);            ...          }          if (!cancelDraw && !newSurface) {           ...          //5、开始绘制了哦                performDraw();            }          }         ...      }
  • 这次我们分析到performDraw方法了。好的,我们一起来看下performDraw里面的代码吧,我只保留来于本次分析相关的关键代码。
    private void performDraw() {        ......        //1、fullRedrawNeeded这个变量标识了本次绘制是否需要完全重新绘制        final boolean fullRedrawNeeded = mFullRedrawNeeded;        try {            //2、此处调用了ViewRootImpl的draw方法            draw(fullRedrawNeeded);        } finally {            mIsDrawing = false;            Trace.traceEnd(Trace.TRACE_TAG_VIEW);        }        ......    }
  • 看1处,既然有完全绘制,当然也会有局部绘制了,这样做是为了提高性能
  • OK,我们看下draw这个方法里面的代码
    private void draw(boolean fullRedrawNeeded) {        ......        //1、获得dirty,也就是我们要绘制的区域        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、需要完全绘制时,将dirty的值设置为神歌屏幕的大小            dirty.set(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));        }        ......                 //3、调用drawSoftware进行绘制        if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {                    return;            }    }
  • 从上面的代码分析中,我们看到,最后时通过调用drawSoftware进行绘制,那么我们看下drawSoftware方法的代码

      private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,              boolean scalingRequired, Rect dirty) {      //1、哈哈,看到了canvas,是不是感觉里绘制越来越近了      final Canvas canvas;      try {          //2、取出绘制区域的四个位置的值          final int left = dirty.left;          final int top = dirty.top;          final int right = dirty.right;          final int bottom = dirty.bottom;          //3、传入我们的绘制区域,创建一个被锁定了绘制区域的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、设置画布的密度          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、设置画布的偏离值              canvas.translate(-xoff, -yoff);              if (mTranslator != null) {                  mTranslator.translateCanvas(canvas);              }              canvas.setScreenDensity(scalingRequired ? mNoncompatDensity : 0);              attachInfo.mSetIgnoreDirtyState = false;              //8、调用mView大的draw方法开始绘制              mView.draw(canvas);          }      }       return true;  }
  • Ok,我们分析到第8步知道,最终调用了mView的draw开始绘制了,而mView也就是DecorView,我们前面分析过DecorView是一个FrameLayout,而FrameLayout并没有重现draw方法,ViewGroup也没有重写,所以,我们直接看View的draw方法,代码有点长,但是思路分清晰,官方给出的解释也是非常清晰的

      @CallSuper  public void draw(Canvas canvas) {      final int privateFlags = mPrivateFlags;       //(1)、dirtyOpaque标识了当前View是否时透明的      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) */   //上面的解释大知识,绘制过程中有一系列的步骤,但是有几个是必须要执行的   //1、绘制背景2、如果有需要,在可以先保存当前canvas的层级数据,3、绘制View的内容4、绘制View的子类5、如果又需要,在退出此次绘制时恢复之前的canvas的层级数据   //6、绘制一些装饰的效果   // Step 1, draw the background, if needed  int saveCount;      //(2)、透明时不需要绘制背景      if (!dirtyOpaque) {        //(3)、不透明时,绘制背景          drawBackground(canvas);      }      //他说可以跳过第2步和第5步,说明第2和第5步时很重要      // 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的内容    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状态和恢复的操作,我们这次就分析其他步骤就好

  • 首先,我们看第1步,在View非透明情况下,执行背景的绘制操作

      private void drawBackground(Canvas canvas) {      final Drawable background = mBackground;      //1、背景为null当然是直接返回了      if (background == null) {          return;      }      //2、确认背景的边界值      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、获取当前的scrollX和scrollY的值      final int scrollX = mScrollX;      final int scrollY = mScrollY;      if ((scrollX | scrollY) == 0) {          //此时没有滚动,开始绘制背景          background.draw(canvas);      } else {          //正在滚动,移动canvas后绘制          canvas.translate(scrollX, scrollY);          background.draw(canvas);          canvas.translate(-scrollX, -scrollY);      }  }
  • 从上面的分析,我有又个意外的发现,当scrllX或者scrollY的值不为0时,先使canvas偏移后在绘制,这就是为什么如果我们是使用Scoller来实现View的滑动时,实际上移动的是View的可视区域,而不是View本身

  • 我们看下setBackgroundBounds里面是如何确认背景边界的

      void setBackgroundBounds() {      if (mBackgroundSizeChanged && mBackground != null) {      //1、直接根据layout中获得的四个位置的值直接确定          mBackground.setBounds(0, 0, mRight - mLeft, mBottom - mTop);          mBackgroundSizeChanged = false;          rebuildOutline();      }  }
  • 介绍完绘制背景,我们接下来分析绘制内容部分,我们看onDraw方法,没错,又是空的,因为这是我们在自定义View的时候需要自己去实现的

      protected void onDraw(Canvas canvas) {  }
  • OK,那我们看下一步,传递绘制事件给child们

  • 我们先看View的dispatchDraw,没错,还是空的,View就是最原始的了,哪里有child嘛。

      protected void dispatchDraw(Canvas canvas) {  }
  • 那么我们来看ViewGroup中的吧,源码优点长,我保留于本次分析相关就好

      @Override  protected void dispatchDraw(Canvas canvas) {      boolean usingRenderNodeProperties = canvas.isRecordingFor(mRenderNode);            //1、获取child的数据      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、调用drawChild传递canvas、child进去绘制child              more |= drawChild(canvas, child, drawingTime);          }      }      ......  }
  • ok,重点是drawChild这个方法,我们看下drawChild里面做什么操作

      protected boolean drawChild(Canvas canvas, View child, long drawingTime) {      return child.draw(canvas, this, drawingTime);  }
  • 里面直接调用了child的draw方法。不过这个方法跟我们前面的分析的draw有点区别哦,没错,参数个数不同,那么我们看下到底却别在哪呢,这个方法的代码很长,我截取关键代码

      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、这里调用子View的draw方法,并将调整好的canvas传进去                  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进行绘制,没有则直接调用View的draw方法。然后根据前面的分析,最终会调用个个View的onDraw进行绘制操作

  • 接下来到了第六部,绘制装饰物(例如recyclerView的滚动条),OK,我们来看下onDrawForeground方法

      public void onDrawForeground(Canvas canvas) {      //1、绘制滚动指示器      onDrawScrollIndicators(canvas);      //2、绘制滚动条      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分析完了,


总结:好吧,直接上一个收集的时序图

123.png

更多相关文章

  1. 浅谈Java中Collections.sort对List排序的两种方法
  2. Python list sort方法的具体使用
  3. python list.sort()根据多个关键字排序的方法实现
  4. android上一些方法的区别和用法的注意事项
  5. android实现字体闪烁动画的方法
  6. Android(安卓)matrix 控制图片的旋转、缩放、移动
  7. Android(安卓)Wifi模块分析(三)
  8. Android中dispatchDraw分析
  9. 锁屏界面

随机推荐

  1. Android期末基础复习
  2. android中handler中 obtainmessge与New m
  3. Android历次主要版本更新说明书
  4. Android 判断用户2G/3G/4G移动数据网络
  5. Android的startActivity启动出错
  6. conversion to dalvik format failed wit
  7. Android USB HID设备通信controlTransfer
  8. Android的frameworks层音量控制原理分析
  9. adb (Android Debug Bridge) Android 调
  10. 共享全局数据 android