美文网首页
View工作原理六:draw流程

View工作原理六:draw流程

作者: 水言 | 来源:发表于2018-08-29 15:19 被阅读18次

    View框架在经过了measure过程和layout过程之后,就已经确定了每一个View的尺寸和位置,那么接下来的工作就是将具体的View绘制到屏幕上,这就是draw的主要工作。

    View的draw流程:

    • 绘制 backgroud(drawBackground)
    • 如果需要的话,保存canvas的layer,来准备fading(边缘渐变效果)
    • 绘制view的content(onDraw方法)
    • 绘制children(dispatchDraw方法)
    • 如果需要的话,绘制fading edges,然后还原layer
    • 绘制装饰器、比如scrollBar(onDrawForeground)

    fading edge使用可参考https://blog.csdn.net/qq1263292336/article/details/78675635

    draw的入口也是在ViewRootImpl中,执行ViewRootImpl#performTraversals中会执行ViewRootIml#performDraw:

    private void performDraw() {
    ...
    //fullRedrawNeeded,它的作用是判断是否需要重新绘制全部视图
    draw(fullRedrawNeeded);
    ...
    }
    

    然后会执行到ViewRootImpl#draw:

    private void draw(boolean fullRedrawNeeded) {
     ...
     //获取mDirty,该值表示需要重绘的区域
     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;
     }
    
     //如果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;
      }
    }
    

    接着会执行到ViewRootIml#drawSoftware,然后在ViewRootIml#drawSoftware会执行到 mView.draw(canvas)。

    private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
       boolean scalingRequired, Rect dirty) {
     final Canvas canvas;
      //锁定canvas区域,由dirty区域决定
      //这个canvas就是我们想在上面绘制东西的画布
      canvas = mSurface.lockCanvas(dirty);
      ...
     //画布支持位图的密度,和手机分辨率相关
      canvas.setDensity(mDensity);
     ...
       if (!canvas.isOpaque() || yoff != 0 || xoff != 0) {
                    canvas.drawColor(0, PorterDuff.Mode.CLEAR);
                }
       ...
          canvas.translate(-xoff, -yoff);
       ...
       //正式开始绘制
       mView.draw(canvas);
      ...
     //提交需要绘制的东西
      surface.unlockCanvasAndPost(canvas);
    }
    

    mView.draw(canvas)开始真正的绘制。
    View#draw:

        public void draw(Canvas canvas) {
            final int privateFlags = mPrivateFlags;
            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)
             */
    
            // Step 1, draw the background, if needed
            int saveCount;
    
            //绘制背景
            if (!dirtyOpaque) {
                drawBackground(canvas);
            }
    
            // 如果可以跳过2和5步
            final int viewFlags = mViewFlags;
          //判断是否有绘制衰退边缘的标示
            boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
            boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
         // 如果没有绘制衰退边缘只需要3,4,6步
            if (!verticalEdges && !horizontalEdges) {
                // Step 3, draw the content
                if (!dirtyOpaque) onDraw(canvas);
    
                // Step 4, draw the children
                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;
            }
    
            /*
             * Here we do the full fledged routine...
             * (this is an uncommon case where speed matters less,
             * this is why we repeat some of the tests that have been
             * done above)
             */
    
            boolean drawTop = false;
            boolean drawBottom = false;
            boolean drawLeft = false;
            boolean drawRight = false;
    
            float topFadeStrength = 0.0f;
            float bottomFadeStrength = 0.0f;
            float leftFadeStrength = 0.0f;
            float rightFadeStrength = 0.0f;
    
            // Step 2, save the canvas' layers
            int paddingLeft = mPaddingLeft;
    
            final boolean offsetRequired = isPaddingOffsetRequired();
            if (offsetRequired) {
                paddingLeft += getLeftPaddingOffset();
            }
    
            int left = mScrollX + paddingLeft;
            int right = left + mRight - mLeft - mPaddingRight - paddingLeft;
            int top = mScrollY + getFadeTop(offsetRequired);
            int bottom = top + getFadeHeight(offsetRequired);
    
            if (offsetRequired) {
                right += getRightPaddingOffset();
                bottom += getBottomPaddingOffset();
            }
    
            final ScrollabilityCache scrollabilityCache = mScrollCache;
            final float fadeHeight = scrollabilityCache.fadingEdgeLength;
            int length = (int) fadeHeight;
    
            // clip the fade length if top and bottom fades overlap
            // overlapping fades produce odd-looking artifacts
            if (verticalEdges && (top + length > bottom - length)) {
                length = (bottom - top) / 2;
            }
    
            // also clip horizontal fades if necessary
            if (horizontalEdges && (left + length > right - length)) {
                length = (right - left) / 2;
            }
    
            if (verticalEdges) {
                topFadeStrength = Math.max(0.0f, Math.min(1.0f, getTopFadingEdgeStrength()));
                drawTop = topFadeStrength * fadeHeight > 1.0f;
                bottomFadeStrength = Math.max(0.0f, Math.min(1.0f, getBottomFadingEdgeStrength()));
                drawBottom = bottomFadeStrength * fadeHeight > 1.0f;
            }
    
            if (horizontalEdges) {
                leftFadeStrength = Math.max(0.0f, Math.min(1.0f, getLeftFadingEdgeStrength()));
                drawLeft = leftFadeStrength * fadeHeight > 1.0f;
                rightFadeStrength = Math.max(0.0f, Math.min(1.0f, getRightFadingEdgeStrength()));
                drawRight = rightFadeStrength * fadeHeight > 1.0f;
            }
    
            saveCount = canvas.getSaveCount();
    
            int solidColor = getSolidColor();
            if (solidColor == 0) {
                final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;
    
                if (drawTop) {
                    canvas.saveLayer(left, top, right, top + length, null, flags);
                }
    
                if (drawBottom) {
                    canvas.saveLayer(left, bottom - length, right, bottom, null, flags);
                }
    
                if (drawLeft) {
                    canvas.saveLayer(left, top, left + length, bottom, null, flags);
                }
    
                if (drawRight) {
                    canvas.saveLayer(right - length, top, right, bottom, null, flags);
                }
            } else {
                scrollabilityCache.setFadeColor(solidColor);
            }
    
            // Step 3, draw the content
            if (!dirtyOpaque) onDraw(canvas);
    
            // Step 4, draw the children
            dispatchDraw(canvas);
    
            // Step 5, draw the fade effect and restore layers
            final Paint p = scrollabilityCache.paint;
            final Matrix matrix = scrollabilityCache.matrix;
            final Shader fade = scrollabilityCache.shader;
    
            if (drawTop) {
                matrix.setScale(1, fadeHeight * topFadeStrength);
                matrix.postTranslate(left, top);
                fade.setLocalMatrix(matrix);
                p.setShader(fade);
                canvas.drawRect(left, top, right, top + length, p);
            }
    
            if (drawBottom) {
                matrix.setScale(1, fadeHeight * bottomFadeStrength);
                matrix.postRotate(180);
                matrix.postTranslate(left, bottom);
                fade.setLocalMatrix(matrix);
                p.setShader(fade);
                canvas.drawRect(left, bottom - length, right, bottom, p);
            }
    
            if (drawLeft) {
                matrix.setScale(1, fadeHeight * leftFadeStrength);
                matrix.postRotate(-90);
                matrix.postTranslate(left, top);
                fade.setLocalMatrix(matrix);
                p.setShader(fade);
                canvas.drawRect(left, top, left + length, bottom, p);
            }
    
            if (drawRight) {
                matrix.setScale(1, fadeHeight * rightFadeStrength);
                matrix.postRotate(90);
                matrix.postTranslate(right, top);
                fade.setLocalMatrix(matrix);
                p.setShader(fade);
                canvas.drawRect(right - length, top, right, bottom, p);
            }
    
            canvas.restoreToCount(saveCount);
    
            // 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);
        }
    

    1.绘制背景

    View#drawBackground

        private void drawBackground(Canvas canvas) {
           //获取背景的Drawable,没有就不需要绘制
            final Drawable background = mBackground;
            if (background == null) {
                return;
            }
           //确定背景Drawable边界
            setBackgroundBounds();
            ...
    
           //如果有偏移量先偏移画布再将drawable绘制上去
            final int scrollX = mScrollX;
            final int scrollY = mScrollY;
            if ((scrollX | scrollY) == 0) {
                background.draw(canvas);
            } else {
                canvas.translate(scrollX, scrollY);
                //此处会执行各种Drawable对应的draw方法
                background.draw(canvas);
                //把画布的原点移回去,drawable在屏幕上的位置不动
                canvas.translate(-scrollX, -scrollY);
            }
        }
    

    3.绘制View的内容

    先跳过第2步,是因为不是所有的View都需绘制褪色边缘。
    View#onDraw:

      protected void onDraw(Canvas canvas) {
        }
    

    没有具体的实现,不同的View需要不同的绘制,自定义View需要自己去实现。
    看看ImageView的ImageVIew#onDraw

    @Override
    protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            //mDrawable空退出绘制
            if (mDrawable == null) {
                return; 
            }
            if (mDrawableWidth == 0 || mDrawableHeight == 0) {
                return;    
            }
           //没有Pad和Matrix时候直接绘制
            if (mDrawMatrix == null && mPaddingTop == 0 && mPaddingLeft == 0) {
                mDrawable.draw(canvas);
            } else {
           // 返回栈中 matrix/clip 状态的数量,值等于 save()调用次数减去 restore()调用次数 
                int saveCount = canvas.getSaveCount();
                canvas.save();
               // android:cropToPadding="true"
                if (mCropToPadding) {
                    final int scrollX = mScrollX;
                    final int scrollY = mScrollY;
                    canvas.clipRect(scrollX + mPaddingLeft, scrollY + mPaddingTop,
                            scrollX + mRight - mLeft - mPaddingRight,
                            scrollY + mBottom - mTop - mPaddingBottom);
                }
                //设置pad偏移
                canvas.translate(mPaddingLeft, mPaddingTop);
                //设置矩阵
                if (mDrawMatrix != null) {
                    canvas.concat(mDrawMatrix);
                }
                mDrawable.draw(canvas);
               //回到上一个save()方法调用之前的状态 
                canvas.restoreToCount(saveCount);
            }
    }
    

    4.绘制子View

    View没有子View,所以在View中该方法是空的。ViewGroup中可以放入子View,所以ViewGroup的绘制需要绘制子View,直接看看ViewGroup#dispatchDraw:

    protected void dispatchDraw(Canvas canvas) {
           boolean usingRenderNodeProperties = canvas.isRecordingFor(mRenderNode);
           final int childrenCount = mChildrenCount;
           final View[] children = mChildren;
           int flags = mGroupFlags;
    
    //ViewGroup是否有设置子View入场动画,如果有绑定到View
    // 启动动画控制器
          ...
    
    //指定修改区域
           int clipSaveCount = 0;
           final boolean clipToPadding = (flags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK;
           if (clipToPadding) {
               clipSaveCount = canvas.save();
               canvas.clipRect(mScrollX + mPaddingLeft, mScrollY + mPaddingTop,
                       mScrollX + mRight - mLeft - mPaddingRight,
                       mScrollY + mBottom - mTop - mPaddingBottom);
           }
    
         ...
    
           for (int i = 0; i < childrenCount; i++) {
    //先取mTransientViews中的View,mTransientViews中的View通过addTransientView添加,它们只是容器渲染的一个item
               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;
                   }
               }
               int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i;
               final View child = (preorderedList == null)
                       ? children[childIndex] : preorderedList.get(childIndex);
               if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
                   more |= drawChild(canvas, child, drawingTime);
               }
           }
        ...
       }
    
    

    ViewGroup#dispatchDraw的流程是先启动第一次加到布局中的动画,然后确定绘制区域,遍历绘制View,遍历View的时候优先绘制渲染的mTransientViews,绘制View调用到ViewGroup#drawChild:

    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
           //View中有两个draw方法
          //这个多参数的draw用于view绘制自身内容
            return child.draw(canvas, this, drawingTime);
        }
    

    View#draw(canvas, this, drawingTime)

    boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
     
           boolean drawingWithRenderNode = mAttachInfo != null
                    && mAttachInfo.mHardwareAccelerated
                    && hardwareAcceleratedCanvas;
          ...
    
    //主要判断是否有绘制缓存,如果有,直接使用缓存,如果没有,调用 draw(canvas)方法
            if (!drawingWithDrawingCache) {
                if (drawingWithRenderNode) {
                    mPrivateFlags &= ~PFLAG_DIRTY_MASK;
                    ((DisplayListCanvas) canvas).drawRenderNode(renderNode);
                } else {
                    // Fast path for layouts with no backgrounds
                    if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
                        mPrivateFlags &= ~PFLAG_DIRTY_MASK;
                        dispatchDraw(canvas);
                    } else {
                        draw(canvas);
                    }
                }
            } else if (cache != null) {
                mPrivateFlags &= ~PFLAG_DIRTY_MASK;
                if (layerType == LAYER_TYPE_NONE) {
                    // no layer paint, use temporary paint to draw bitmap
                    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 {
                    // use layer paint to draw the bitmap, merging the two alphas, but also restore
                    int layerPaintAlpha = mLayerPaint.getAlpha();
                    mLayerPaint.setAlpha((int) (alpha * layerPaintAlpha));
                    canvas.drawBitmap(cache, 0.0f, 0.0f, mLayerPaint);
                    mLayerPaint.setAlpha(layerPaintAlpha);
                }
          }
    }
    

    6.绘制装饰

        public void onDrawForeground(Canvas canvas) {
    //绘制滑动指示
            onDrawScrollIndicators(canvas);
    //绘制ScrollBar
            onDrawScrollBars(canvas);
    //获取前景色的Drawable,绘制到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.draw(canvas);
            }
        }
    

    2和5.绘制View的褪色边缘

    当horizontalEdges或者verticalEdges有一个true的时候,表示需要绘制View的褪色边缘:

         boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
         boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
    

    这时候先计算出是否需要绘制上下左右的褪色边缘和它的参数,然后保存视图层:

            int paddingLeft = mPaddingLeft;
            final boolean offsetRequired = isPaddingOffsetRequired();
            if (offsetRequired) {
                paddingLeft += getLeftPaddingOffset();
            }
            int left = mScrollX + paddingLeft;
            int right = left + mRight - mLeft - mPaddingRight - paddingLeft;
            int top = mScrollY + getFadeTop(offsetRequired);
            int bottom = top + getFadeHeight(offsetRequired);
            if (offsetRequired) {
                right += getRightPaddingOffset();
                bottom += getBottomPaddingOffset();
            }
            final ScrollabilityCache scrollabilityCache = mScrollCache;
            final float fadeHeight = scrollabilityCache.fadingEdgeLength;
            int length = (int) fadeHeight;
            if (verticalEdges && (top + length > bottom - length)) {
                length = (bottom - top) / 2;
            }
            if (horizontalEdges && (left + length > right - length)) {
                length = (right - left) / 2;
            }
            if (verticalEdges) {
                topFadeStrength = Math.max(0.0f, Math.min(1.0f, getTopFadingEdgeStrength()));
                drawTop = topFadeStrength * fadeHeight > 1.0f;
                bottomFadeStrength = Math.max(0.0f, Math.min(1.0f, getBottomFadingEdgeStrength()));
                drawBottom = bottomFadeStrength * fadeHeight > 1.0f;
            }
            if (horizontalEdges) {
                leftFadeStrength = Math.max(0.0f, Math.min(1.0f, getLeftFadingEdgeStrength()));
                drawLeft = leftFadeStrength * fadeHeight > 1.0f;
                rightFadeStrength = Math.max(0.0f, Math.min(1.0f, getRightFadingEdgeStrength()));
                drawRight = rightFadeStrength * fadeHeight > 1.0f;
            }
            saveCount = canvas.getSaveCount();
            int solidColor = getSolidColor();
            if (solidColor == 0) {
                final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;
                if (drawTop) {
                    canvas.saveLayer(left, top, right, top + length, null, flags);
                }
                if (drawBottom) {
                    canvas.saveLayer(left, bottom - length, right, bottom, null, flags);
                }
                if (drawLeft) {
                    canvas.saveLayer(left, top, left + length, bottom, null, flags);
                }
                if (drawRight) {
                    canvas.saveLayer(right - length, top, right, bottom, null, flags);
                }
            } else {
                scrollabilityCache.setFadeColor(solidColor);
            }
    

    绘制褪色边缘,恢复视图层

            final Paint p = scrollabilityCache.paint;
            final Matrix matrix = scrollabilityCache.matrix;
            final Shader fade = scrollabilityCache.shader;
            if (drawTop) {
                matrix.setScale(1, fadeHeight * topFadeStrength);
                matrix.postTranslate(left, top);
                fade.setLocalMatrix(matrix);
                p.setShader(fade);
                canvas.drawRect(left, top, right, top + length, p);
            }
            if (drawBottom) {
                matrix.setScale(1, fadeHeight * bottomFadeStrength);
                matrix.postRotate(180);
                matrix.postTranslate(left, bottom);
                fade.setLocalMatrix(matrix);
                p.setShader(fade);
                canvas.drawRect(left, bottom - length, right, bottom, p);
            }
            if (drawLeft) {
                matrix.setScale(1, fadeHeight * leftFadeStrength);
                matrix.postRotate(-90);
                matrix.postTranslate(left, top);
                fade.setLocalMatrix(matrix);
                p.setShader(fade);
                canvas.drawRect(left, top, left + length, bottom, p);
            }
            if (drawRight) {
                matrix.setScale(1, fadeHeight * rightFadeStrength);
                matrix.postRotate(90);
                matrix.postTranslate(right, top);
                fade.setLocalMatrix(matrix);
                p.setShader(fade);
                canvas.drawRect(right - length, top, right, bottom, p);
            }
            canvas.restoreToCount(saveCount);
    

    参考:《Android 进阶之光》

    相关文章

      网友评论

          本文标题:View工作原理六:draw流程

          本文链接:https://www.haomeiwen.com/subject/jthjmxtx.html