系统运维

Android源码进阶之深入理解View的绘制流程(Draw)机制

时间:2010-12-5 17:23:32  作者:应用开发   来源:IT科技类资讯  查看:  评论:0
内容摘要:前言前几篇文章,讲述了measure,layout流程等,接下来将详细分析绘制流程。测量流程决定了View的大小,布局流程决定了View的位置,那么绘制流程将决定View的样子,一个View该显示什么

前言

前几篇文章,码进讲述了measure,阶之w机layout流程等,深入接下来将详细分析绘制流程。理解

测量流程决定了View的制流制大小,布局流程决定了View的码进位置,那么绘制流程将决定View的阶之w机样子,一个View该显示什么由绘制流程完成;

那我们就开始开车了;

一、深入performDraw

三大工作流程始于ViewRootImpl#performTraversals,理解在这个方法内部会分别调用performMeasure,performLayout,制流制performDraw三个方法来分别完成测量,码进布局,阶之w机绘制流程。深入那么我们现在先从performDraw方法看起;

performDraw

private void performDraw() {      //...     final boolean fullRedrawNeeded = mFullRedrawNeeded;     try {          draw(fullRedrawNeeded);     } finally {          mIsDrawing = false;         Trace.traceEnd(Trace.TRACE_TAG_VIEW);     } } 

里面又调用了ViewRootImpl#draw方法,理解我们来看看ViewRootImpl#draw:

private void draw(boolean fullRedrawNeeded) {      ...     //获取mDirty,制流制该值表示需要重绘的区域     final Rect dirty = mDirty;     if (mSurfaceHolder != null) {          // The app owns the surface, we wont draw.         dirty.setEmpty();         if (animating) {              if (mScroller != null) {                  mScroller.abortAnimation();             }             disposeResizeBuffer();         }         return;     }     //如果fullRedrawNeeded为真,则把dirty区域置为整个屏幕,表示整个视图都需要绘制     //第一次绘制流程,需要绘制所有视图     if (fullRedrawNeeded) {          mAttachInfo.mIgnoreDirtyState = true;         dirty.set(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));     }     //...     if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {                  return;         } } 

根据fullRedrawNeeded来判断是否需要重置dirty区域,最后调用了ViewRootImpl#drawSoftware方法,并把相关参数传递进去,包括dirty区域,我们接着看该方法的源码;

private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,             boolean scalingRequired, Rect dirty) {      // Draw with software renderer.     final Canvas canvas;     try {          final int left = dirty.left;         final int top = dirty.top;         final int right = dirty.right;         final int bottom = dirty.bottom;         //锁定canvas区域,由dirty区域决定         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;         }         canvas.setDensity(mDensity);     }      try {          if (!canvas.isOpaque() || yoff != 0 || xoff != 0) {              canvas.drawColor(0, PorterDuff.Mode.CLEAR);         }         dirty.setEmpty();         mIsAnimating = false;         attachInfo.mDrawingTime = SystemClock.uptimeMillis();         mView.mPrivateFlags |= View.PFLAG_DRAWN;         try {              canvas.translate(-xoff, -yoff);             if (mTranslator != null) {                  mTranslator.translateCanvas(canvas);             }             canvas.setScreenDensity(scalingRequired ? mNoncompatDensity : 0);             attachInfo.mSetIgnoreDirtyState = false;             //正式开始绘制             mView.draw(canvas);         }     }      return true; } 

实例化了Canvas对象,然后锁定该canvas的区域,由dirty区域决定,接着对canvas进行一系列的高防服务器属性赋值,最后调用了mView.draw(canvas)方法,

mView就是DecorView,也就是说从DecorView开始绘制;

二、draw源码详解

由于ViewGroup没有重写draw方法,因此所有的View都是调用View#draw方法,因此,我们直接看它的源码

public void draw(Canvas canvas) {      ....      // 1. 绘制本身View背景     if (!dirtyOpaque) {          drawBackground(canvas);     }     if (!verticalEdges && !horizontalEdges) {          // Step 3, draw the content         // 2. 绘制内容,默认空实现 需复写         if (!dirtyOpaque) onDraw(canvas);         // 3. 绘制 children          dispatchDraw(canvas);         drawAutofilledHighlight(canvas);         // 4. 分发Draw (单一View空实现,ViewGroup见下面分析)         if (mOverlay != null && !mOverlay.isEmpty()) {              mOverlay.getOverlayView().dispatchDraw(canvas);         }         // 5. 绘制装饰 (前景色,滚动条)         onDrawForeground(canvas);         return;     }     .... } 

可以看到,draw过程比较复杂,但是逻辑十分清晰。首先来看一开始的标记位dirtyOpaque,

该标记位的作用是判断当前View是否是透明的,如果View是透明的,那么根据下面的逻辑可以看出,将不会执行一些步骤,服务器租用比如绘制背景、绘制内容等;

绘制流程的五个步骤:

对View的背景进行绘制; 绘制View的内容; 对View的子View进行绘制(如果有子View); 分发Draw;

绘制View的装饰(例如:前景色,滚动条);

1、绘制背景

//绘制背景 private void drawBackground(Canvas canvas) {      final Drawable background = mBackground;     if (background == null) {          return;     }     // 根据在 layout 过程中获取的 View 的位置参数,来设置背景的边界     setBackgroundBounds();     // 先尝试用HWUI绘制     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 {          // 若 mScrollX 和 mScrollY 有值,则对 canvas 的坐标进行偏移平移画布         canvas.translate(scrollX, scrollY);         //调用 Drawable 的 draw 方法来进行背景的绘制         background.draw(canvas);         canvas.translate(-scrollX, -scrollY);     } } 

2、绘制View的内容

// 绘制View本身内容,空实现,子类必须复写 protected void onDraw(Canvas canvas) {  } 

这里调用了View#onDraw方法,View中该方法是一个空实现,因为不同的View有着不同的内容,这需要我们自己去实现,即在自定义View中重写该方法来实现;

3、子View进行绘制

当前的View是一个ViewGroup类型,站群服务器那么就需要绘制它的子View,这里调用了dispatchDraw,而View中该方法是空实现,实际是ViewGroup重写了这个方法,那么我们来看看;

@Override protected void dispatchDraw(Canvas canvas) {      ...     //  遍历子View     final int childrenCount = mChildrenCount;     ...     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);         }     }     .... } 

4、分发Draw

@Override protected void dispatchDraw(Canvas canvas) {      ...     // 1. 遍历子View     final int childrenCount = mChildrenCount;     ...     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);         }     }     .... } 

5、绘制View

所谓的绘制装饰,就是指View除了背景、内容、子View的其余部分,例如滚动条等,我们看View#onDrawForeground

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的绘制流程也讲述完毕了;

总结

其实绘制这块还是很重要的,下次还是要继续讲解下;

学如逆水行舟,不进则退;心似平原走马,易放难收;

一起加油老铁们

本文转载自微信公众号「Android开发编程」

【编辑推荐】

任何Ubuntu用户都应安装的四大Linux应用程序 工信部:5G手机终端连接数达4.19亿户 只需两步,教会你正确处理旧手机,变废为宝 MySQL的三条JOIN子句使用指南 苹果iOS 15再次迎来更新,除了实况文本外,还有五个新发现
copyright © 2025 powered by 益强资讯全景  滇ICP备2023006006号-31sitemap