美文网首页Android进阶Android自定义View
Android高级进阶——View的工作原理(三)Draw过程

Android高级进阶——View的工作原理(三)Draw过程

作者: aKaiC | 来源:发表于2018-04-18 19:50 被阅读18次

    开篇:

    前两篇已经详细的介绍了 Measure 以及 Layout 过程,就剩下一个 Draw 绘制过程了,Draw 其实也不是很复杂,但是想要彻底掌握绘制的技巧就需要了解 Canvas 的使用了,后续会再开几篇详细介绍 Canvas 的具体使用

    老规矩,还是先给出 ViewRootImpl#performTraversals 方法

    ViewRootImpl#performTraversals 方法

    private void performTraversals() {
             ...
            if (!mStopped || mReportNextDraw) {
                boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
                            (relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
                if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
                            || mHeight != host.getMeasuredHeight() || contentInsetsChanged ||
                            updatedConfiguration) {
                    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
                    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
    
                    // Ask host how big it wants to be
                    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                    ...
    
            if (didLayout) {
                performLayout(lp, mWidth, mHeight);
                ...
    
            if (!cancelDraw && !newSurface) {
                if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
                    for (int i = 0; i < mPendingTransitions.size(); ++i) {
                        mPendingTransitions.get(i).startChangingAnimations();
                    }
                    mPendingTransitions.clear();
                }
    
                performDraw();
                ...
    

    不废话,直接看 performDraw 方法

    ViewRootImpl #performDraw 方法

        private void performDraw() {
            ...省略部分代码
            try {
                draw(fullRedrawNeeded);
            } finally {
                mIsDrawing = false;
                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
            }
            ...省略部分代码
    

    ViewRootImpl#draw() 方法

        private void draw(boolean fullRedrawNeeded) {
            ...省略部分代码
            scrollToRectOrFocus(null, false);
    
            if (mAttachInfo.mViewScrollChanged) {
                mAttachInfo.mViewScrollChanged = false;
                mAttachInfo.mTreeObserver.dispatchOnScrollChanged();
            }
            ...省略部分代码
            mAttachInfo.mTreeObserver.dispatchOnDraw();
            ...省略部分代码
            //根据是否开启了硬件加速,是否开启硬件加速,View 的绘制流程都是一样的,区别就是 Canvas 不同
                if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) {
                    //开启了硬件加速,则执行该方法,内部最终还是会执行到view 的 draw 方法
                    mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
                } else {
                    //未开启硬件加速,则执行该方法,直接调用 view 的 draw 方法
                    if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
                        return;
                    }
                }
    }
    

    measure 和 layout 过程直接调用的就是 ViewRootImpl的performMeasure和performLayout方法,而draw调用的是ViewRootImpl的performDraw()方法,再由performDraw中的draw(boolean fullRedrawNeeded)方法来调用ViewTreeObserver中的dispatchOnDraw()方法,进行通知所有挂在view树上的view开始draw,随后通过 drawSoftware 方法调用 view 的 draw 方法开始绘制

    ViewTreeObserver.dispatchOnDraw()

        public final void dispatchOnDraw() {
            if (mOnDrawListeners != null) {
                mInDispatchOnDraw = true;
                final ArrayList<OnDrawListener> listeners = mOnDrawListeners;
                int numListeners = listeners.size();
                for (int i = 0; i < numListeners; ++i) {
                    listeners.get(i).onDraw();
                }
                mInDispatchOnDraw = false;
            }
        }
    

    ViewRootImpl#drawSoftware 方法

        private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
                boolean scalingRequired, Rect dirty) {
            final Canvas canvas;
                canvas = mSurface.lockCanvas(dirty);
                    //...省略部分代码
                    mView.draw(canvas);
                    //...省略部分代码
    }
    
    

    最终终于执行到了 View 的 draw() 方法了,这个方法很重要,我们要显示的内容都是在这个方法中实现的,没有实现这个方法的逻辑,就是前面的 Measure 和 Layout 逻辑处理的在漂亮,也不能呈现。

    View #draw(Canvas canvas)

        public void draw(Canvas canvas) {
            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    
             *      //绘制 View 的内容
             *      3. Draw view's content
             *      //绘制子 View,子 View 的绘制也是按照这个流程进行
             *      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
            // Step 1, 绘制背景
            int saveCount;
    
            if (!dirtyOpaque) {
                drawBackground(canvas);
            }
    
            // 通常情况下,会跳过第 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) {
                //绘制 View 的内容,需要子类具体实现,View 的 onDraw 是一个空实现,因为 View 并不是一个具体的 View ,不知道要绘制的内容,所以要绘制的内容留给具体的子类去具体实现
                // Step 3, draw the content
                if (!dirtyOpaque) onDraw(canvas);
    
                //绘制内部包含的子 View,这个方法 View 也没有实现,具体的实现是在 ViewGroup 中,后面会具体分析该方法
                // Step 4, draw the children
                dispatchDraw(canvas);
    
                drawAutofilledHighlight(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);
    
                // Step 7, draw the default focus highlight
                drawDefaultFocusHighlight(canvas);
    
                if (debugDraw()) {
                    debugDrawFocus(canvas);
                }
    
                // we're done...
                return;
            }
              ...省略部分代码
    
    

    View #drawBackground(Canvas)

    该方法用于绘制 View 的背景,这个背景是我们在创建 View 时设定的

        private void drawBackground(Canvas canvas) {
            final Drawable background = mBackground;
            if (background == null) {
                return;
            }
            mBackground.setBounds(0, 0, mRight - mLeft, mBottom - mTop);
    
            ...省略硬件加速相关代码
            //判断 View 是否设置了 scrollX 和 mScrollY,并平移滑动,绘制完背景后,在平移到原来的位置
            final int scrollX = mScrollX;
            final int scrollY = mScrollY;
            if ((scrollX | scrollY) == 0) {
                background.draw(canvas);
            } else {
                canvas.translate(scrollX, scrollY);
                background.draw(canvas);
                canvas.translate(-scrollX, -scrollY);
            }
        }
    

    View 的 onDraw(Canvas) 方法

        protected void onDraw(Canvas canvas) {
        }
    

    onDraw 是用来绘制 View 的显示内容的,但是 View 并没有实现 onDraw ,具体的实现逻辑需要派生类去给根据自身情况去绘制具体的内容,可以参看 TextView 的 onDraw 方法

    View #dispatchDraw

        protected void dispatchDraw(Canvas canvas) {
    
        }
    

    dispatchDraw( )方法用于通知子View自己绘制,View未实现该方法,由ViewGroup来实现该方法。我们来看一下吧;

    ViewGroup#dispatchDraw(Canvas) 方法

    protected void dispatchDraw(Canvas canvas) {
    
        。。。(省略)
    
        int clipSaveCount = 0;
        final boolean clipToPadding = (flags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK;
        if (clipToPadding) {
            // 这里会对画布进行剪切,切掉Padding值
            clipSaveCount = canvas.save();
            canvas.clipRect(mScrollX + mPaddingLeft, mScrollY + mPaddingTop,
                    mScrollX + mRight - mLeft - mPaddingRight,
                    mScrollY + mBottom - mTop - mPaddingBottom);
        }
    
        。。。(省略)
    
        // 遍历子View
        for (int i = 0; i < childrenCount; i++) {
    
            。。。(动画相关操作 省略)
    
            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) {
                // 绘制子View
                drawChild(canvas, child, drawingTime);
            }
        }
    
        。。。(省略)
    
        if (clipToPadding) {
            canvas.restoreToCount(clipSaveCount);
        }
        。。。(省略)
    }
    

    ViewGroup#drawChild 方法

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

    这里调用的是 View 这个类的重载方法,来看一下

    View draw(Canvas canvas,ViewGroup parent,long drawingTime) 方法

    // 画布canvas的大小是
    boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
        // 是否开启硬件加速标识
        boolean drawingWithRenderNode = mAttachInfo != null
            && mAttachInfo.mHardwareAccelerated
            && hardwareAcceleratedCanvas;
        。。。省略
        int sx = 0;
        int sy = 0;
        if (!drawingWithRenderNode) {
            // 我们在自定义滑动控件时,一般会重写该方法,并设置mScrollX和mScrollY
            computeScroll();
            sx = mScrollX;
            sy = mScrollY;
        }
    
        final boolean drawingWithDrawingCache = cache != null && !drawingWithRenderNode;
        final boolean offsetForScroll = cache == null && !drawingWithRenderNode;
    
        int restoreTo = -1;
        restoreTo = canvas.save();
    
        // 根据mScrollX和mScrollY移动画布的坐标系
        canvas.translate(mLeft - sx, mTop - sy);
    
        。。。(设置画布透明度的操作 省略)
    
        if (!drawingWithRenderNode) {
            // apply clips directly, since RenderNode won't do it for this draw
            if ((parentFlags & ViewGroup.FLAG_CLIP_CHILDREN) != 0 && cache == null) {
                canvas.clipRect(sx, sy, sx + getWidth(), sy + getHeight());
            }
        }
    
        if (!drawingWithDrawingCache) {
            if (drawingWithRenderNode) {
                // 开启硬件加速时走该分支
                ((DisplayListCanvas) canvas).drawRenderNode(renderNode);
            } else {
                // 调用View.draw()进行绘制
                draw(canvas);
            }
        }
    
        if (restoreTo >= 0) {
            canvas.restoreToCount(restoreTo);
        }
        。。。省略
        return more;
    }
    

    到这里关于 View 的三个工作流程就介绍完了,后面会详细的介绍一下 View 的绘制技巧 —— Canvas 的使用。

    相关文章

      网友评论

        本文标题:Android高级进阶——View的工作原理(三)Draw过程

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