美文网首页
Draw过程

Draw过程

作者: Utte | 来源:发表于2018-08-07 22:00 被阅读58次

    一、调用流程

    1. ViewRootImpl # performTraversals()

    performTraversals()调用了performDraw(),三大流程的起点都是performTraversals()。

    // 没有取消draw也没有创建新的平面 第一次traversals时newSurface为true
    if (!cancelDraw && !newSurface) {
        // ...
        // 开始draw流程
        performDraw();
    } else {
        if (isViewVisible) {
            // Try again
            // 如果是可见的, 就再调用一次traversals
            scheduleTraversals();
            // ...
        }
        // ...
    }
    

    2. ViewRootImpl # performDraw()

    调用到ViewRootImpl的draw()。

    private void performDraw() {
        // ...
        // mFullRedrawNeeded表示是否需要完全重绘
        final boolean fullRedrawNeeded = mFullRedrawNeeded;
        mFullRedrawNeeded = false;
        mIsDrawing = true;
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "draw");
        try {
            // 调用draw()
            draw(fullRedrawNeeded);
        } finally {
            mIsDrawing = false;
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
        // ...
    }
    

    3. ViewRootImpl # draw()

    这个方法非常长,主要是去处理绘制区域、坐标等准备工作,之后调用drawSoftware()。

    private void draw(boolean fullRedrawNeeded) {
        // ...
        // 获取需要绘制的区域
        final Rect dirty = mDirty;
        // ...
        // 判断是否需要完全绘制
        if (fullRedrawNeeded) {
            mAttachInfo.mIgnoreDirtyState = true;
            // 如果需要就将区域设置为屏幕的所有区域
            dirty.set(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
        }
        // ...
        if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
            if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) {
                // ...
            } else {
                // ...
                // 调用drawSoftware()绘制
                if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
                    return;
                }
            }
        }
        // ...
    }
    

    4. ViewRootImpl # drawSoftware()

    • 初始canvas对象
    • 调用DecorView的draw()
    private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
            boolean scalingRequired, Rect dirty) {
        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对象
            canvas = mSurface.lockCanvas(dirty);
            
            // 判断lockCanvas有没有改变dirty的顶点值
            if (left != dirty.left || top != dirty.top || right != dirty.right
                    || bottom != dirty.bottom) {
                attachInfo.mIgnoreDirtyState = true;
            }
            // 设置画布密度
            canvas.setDensity(mDensity);
        } catch (Surface.OutOfResourcesException e) {
           // ...
        }
        try {
            // ...
            // 先清空画布
            if (!canvas.isOpaque() || yoff != 0 || xoff != 0) {
                canvas.drawColor(0, PorterDuff.Mode.CLEAR);
            }
            dirty.setEmpty();
            mIsAnimating = false;
            mView.mPrivateFlags |= View.PFLAG_DRAWN;
            
            try {
                // 设置画布偏移值
                canvas.translate(-xoff, -yoff);
                if (mTranslator != null) {
                    mTranslator.translateCanvas(canvas);
                }
                canvas.setScreenDensity(scalingRequired ? mNoncompatDensity : 0);
                attachInfo.mSetIgnoreDirtyState = false;
                // 调用draw() 从DecorView开始绘制流程
                mView.draw(canvas);
                // ...
            }
            // ...
        } 
        // ...
        return true;
    }
    

    5. DecorView # draw()

    • 先调用父类的draw()去绘制。
    • 如果有菜单背景的drawable的画就画上。
    @Override
    public void draw(Canvas canvas) {
        super.draw(canvas);
        if (mMenuBackground != null) {
            mMenuBackground.draw(canvas);
        }
    }
    

    6. View # draw()

    FrameLayout和ViewGroup都没有重写draw(),所以就调用到了View的draw()。View的draw()也很长,但是注释写的分步很清楚。

    /*
     * 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)
     */
    

    绘制背景、绘制自身、绘制子View、绘制阴影、绘制其它的装饰。到这里就调用到Draw向子View分发的过程了。下面就仔细的看看这个方法。

    二、View的draw()的实现

    根据注释可以将draw()分成六个阶段。

    • 绘制背景
    • 存储图层
    • 绘制自身
    • 绘制子View
    • 绘制阴影并恢复图层
    • 绘制滚动条等效果

    1. 绘制背景

    View # draw()

    如果不为透明,就调用drawBackground()去绘制背景。

    final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
            (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);
    
    // 判断是否为透明
    if (!dirtyOpaque) {
        // 调用drawBackgroud()去绘制背景
        drawBackground(canvas);
    }
    
    View # drawBackground()
    • 设置背景图片边界
    • 判断View是否有移动
      • N:直接绘制背景图片
      • Y:移动到View的偏移位置,绘制背景,再移动回来。
    private void drawBackground(Canvas canvas) {
        final Drawable background = mBackground;
        if (background == null) {
            return;
        }
        // 设置背景图片的边界位置
        setBackgroundBounds();
        
        // 硬件加速相关......
        
        final int scrollX = mScrollX;
        final int scrollY = mScrollY;
        // 判断View是否滑动
        if ((scrollX | scrollY) == 0) {
            // 没有滑动就绘制背景图片
            background.draw(canvas);
        } else {
            // 如果移动了,就先移动canvas,绘制背景,canvas再移回
            canvas.translate(scrollX, scrollY);
            background.draw(canvas);
            canvas.translate(-scrollX, -scrollY);
        }
    }
    
    View # setBackgroundBounds()

    调用drawable的setBounds()设置边界,传参是由layout过程中生成的顶点值组合的,就相当于是View的顶点。

    void setBackgroundBounds() {
        if (mBackgroundSizeChanged && mBackground != null) {
            // 设置背景图的四个坐标,这四个值就是View的四个顶点值
            mBackground.setBounds(0, 0, mRight - mLeft, mBottom - mTop);
            mBackgroundSizeChanged = false;
            rebuildOutline();
        }
    }
    

    2. 绘制自身

    View # draw()
    • 如果不需要绘制阴影,就直接进入绘制自身的步骤。
    • 如果View不是透明的,就调用onDraw()去绘制自身。
    boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
    boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
    // 如果不需要去绘制阴影,就可以跳过存储图层和绘制阴影的步骤
    if (!verticalEdges && !horizontalEdges) {
        // Step 3, draw the content
        // 绘制自身
        if (!dirtyOpaque) onDraw(canvas);
        // ...
    }
    
    View # onDraw()

    空实现,没有做统一实现,自定义View时需要自己去实现。

    protected void onDraw(Canvas canvas) {
    }
    

    3. 绘制子View分发过程

    View # draw()

    在绘制自己完成后就去分发调用子View的绘制过程。

    if (!verticalEdges && !horizontalEdges) {
        // ...
        dispatchDraw(canvas);
        // ...
    }
    
    ViewGroup # dispatchDraw()

    在View中是空实现,所以来看ViewGroup中的实现。会遍历所有的子View,如果子View可见或是存在动画,就调用drawChld()。

    @Override
    protected void dispatchDraw(Canvas canvas) {
        boolean usingRenderNodeProperties = canvas.isRecordingFor(mRenderNode);
        final int childrenCount = mChildrenCount;
        final View[] children = mChildren;
        int flags = mGroupFlags;
        // ...
        // 遍历子View
        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) {
                // 调用drawChild去传递
                more |= drawChild(canvas, child, drawingTime);
            }
        }
        // ...
    }
    
    ViewGroup # drawChild()

    直接调用了子View的draw(),实现了传递。

    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
        return child.draw(canvas, this, drawingTime);
    }
    
    View # draw()

    这个draw()有三个参数,和之前分析的一个参数的draw()不同,注释上说得很清楚,这个draw()是ViewGroup.drawChild()调用去绘制子View的。和一个参数最大不同是,这个draw()判断了是绘制缓存内容还是去调用一个参数的draw()绘制。

    boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
        // ...
        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;
                    // 如果需要跳过,就直接去调用分发给这个View的子View的方法
                    dispatchDraw(canvas);
                } else {
                    // 调用普通的draw()
                    draw(canvas);
                }
            }
        } else if (cache != null) {
            // 如果使用缓存并且缓存不为空
            mPrivateFlags &= ~PFLAG_DIRTY_MASK;
            if (layerType == LAYER_TYPE_NONE || mLayerPaint == null) {
                // 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();
                if (alpha < 1) {
                    mLayerPaint.setAlpha((int) (alpha * layerPaintAlpha));
                }
                // 使用图层的Paint去绘制缓存内容
                canvas.drawBitmap(cache, 0.0f, 0.0f, mLayerPaint);
                if (alpha < 1) {
                    mLayerPaint.setAlpha(layerPaintAlpha);
                }
            }
        }
        // ...
    }
    

    4. 绘制装饰物

    View # draw()
    if (!verticalEdges && !horizontalEdges) {
        // ...
        onDrawForeground(canvas);
        // ...
    }
    
    View # onDrawForeground()
    public void onDrawForeground(Canvas canvas) {
        // 绘制滚动指示器
        onDrawScrollIndicators(canvas);
        // 绘制滚动条
        onDrawScrollBars(canvas);
        final Drawable foreground = mForegroundInfo != null ? mForegroundInfo.mDrawable : null;
        if (foreground != null) {
            // ...
            // 如果有前景图片,就绘制它
            foreground.draw(canvas);
        }
    }
    

    5. 如果需要绘制边框阴影

    前面那些步骤走完后就能直接return了。如果需要绘制阴影的话,则不会进入前面分析的方法块。而是执行下面的步骤。注释上说这种情况不常见。

    View # draw()
    • 同样地绘制背景
    • 去计算阴影带来的变量影响,对阴影分类去保存图层
    • 同样地绘制自身
    • 同样地分发绘制流程
    • 分类去绘制阴影
    • 加载保存的图层
    • 同样地绘制装饰
    // 共同的步骤,绘制背景图片
    if (!dirtyOpaque) {
        drawBackground(canvas);
    }
    // 不需要绘制阴影的情况
    if (!verticalEdges && !horizontalEdges) {
        // ...
        return;
    }
    
    // 如果需要绘制阴影
    
    // 根据阴影情况去改变参数......
    
    if (solidColor == 0) {
        final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;
        // 保存图层
        if (drawTop) {
            canvas.saveLayer(left, top, right, top + length, null, flags);
        }
        // ...
    } else {
        scrollabilityCache.setFadeColor(solidColor);
    }
    // 绘制自身
    if (!dirtyOpaque) onDraw(canvas);
    // 绘制子View
    dispatchDraw(canvas);
    // ...
    // 绘制阴影
    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);
    }
    // ...
    // 取出保存的图层
    canvas.restoreToCount(saveCount);
    // ...
    // 绘制装饰
    onDrawForeground(canvas);
    

    相关文章

      网友评论

          本文标题:Draw过程

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