美文网首页
view -draw过程

view -draw过程

作者: 刘佳阔 | 来源:发表于2018-03-09 17:52 被阅读0次

    1.view中包含的绘制元素.

    view背景,任何view都有背景,可以是图片.色值或者drawable资源
    视图本身内容. 在ondraw中绘制
    渐变边框,本质是一个shader对象
    滚动条,紧紧显示滚动的状态,任何的view都有滚动条.
    

    2.设置思路

    2.png

    和之前的layout,measure过程都是类似的.draw方法不建议子视图重写. 首先是mView的draw函数被调用,一般来说mView就是窗户的根视图,对于Activity就是PhoneWindow.DecorView对象. 会调用View的draw方法,然后在onDraw中完成界面内容的绘制,如果还有子视图.就调用dispatchDraw函数,dispatchDraw由viewgroup实现了.也不建议我们来重写他.该方法内部循环调用drawchild()方法,其实就是调用子视图的draw方法完成绘制. 其实draw的过程就是把canvas从根部局不断剪裁,然后传给所有子视图利用canvas进行绘制的过程.

    3.ViewRootImpl 中draw 的主要过程就是得到绘图用的Canvas.设置屏幕密度.调用mView.draw()函数.

    4.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) {//1.dirtyOpaque表示dirty区是否透明,只有透明才需要绘制背景.
                drawBackground(canvas);//绘制背景时会对canvas的左边进行调整,需要调整到该视图内部的原点.
            }
    
            // 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) {//2表示没有渐变.跳过第二步
                // Step 3, draw the content
                if (!dirtyOpaque) onDraw(canvas);//3视图在这里实现自己内容的绘制
    
                // Step 4, draw the children
                dispatchDraw(canvas); //4viewgroup实现这个方法来绘制子视图
    
                // Step 6, draw decorations (scrollbars)
                onDrawScrollBars(canvas);//5绘制滚动条等装饰内容
    
                if (mOverlay != null && !mOverlay.isEmpty()) {
                    mOverlay.getOverlayView().dispatchDraw(canvas);
                }
    
                // we're done...
                return;
            }
    
          // Step 2,这里是对渐变框计算的数值的处理,代码不看了
    
            // Step 3, draw the content
            if (!dirtyOpaque) onDraw(canvas); //绘制视图
    
            // Step 4, draw the children
            dispatchDraw(canvas);//绘制内容
    
            // Step 5,  //利用step2步骤的值绘制渐变框,不看了
            final Paint p = scrollabilityCache.paint;
            final Matrix matrix = scrollabilityCache.matrix;
            final Shader fade = scrollabilityCache.shader;
     
    
            // Step 6, draw decorations (scrollbars)
            onDrawScrollBars(canvas); //绘制滚动条
    
            if (mOverlay != null && !mOverlay.isEmpty()) {
                mOverlay.getOverlayView().dispatchDraw(canvas);
            }
        }
    

    5.ViewGroup 绘制子视图 dispatchDraw 内部流程.

         protected void dispatchDraw(Canvas canvas) {
     
            if ((flags & FLAG_RUN_ANIMATION) != 0 && canAnimate()) {//1.判断是否有布局动画标识,有的话就处理.该参数设置在viewgroup中.
    layoutAnimation属性,是ViewGroup为所有子视图设置的动画
                final boolean cache = (mGroupFlags & FLAG_ANIMATION_CACHE) == FLAG_ANIMATION_CACHE;
    
                for (int i = 0; i < childrenCount; i++) {
                    final View child = children[i];
                    if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
                        final LayoutParams params = child.getLayoutParams();
                        attachLayoutAnimationParameters(child, params, i, childrenCount);
                        bindLayoutAnimation(child);
                    }
                }
                final LayoutAnimationController controller = mLayoutAnimationController;
                controller.start();
            }
    
            int clipSaveCount = 0;//2.处理padding属性,剪切该视图的canvas.
            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++) {//3.遍历子视图,调用它的draw方法,此时的canvas的范围经过上一部的剪裁后的
          //就是该视图内可供子视图绘制的区域.
                final View child = (preorderedList == null)
                        ? children[childIndex] : preorderedList.get(childIndex);
                if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
                    more |= drawChild(canvas, child, drawingTime);//此时还会调用到view的draw方法,但是是三个参数的.和之前的不同.
                }
            }
          
    
          //4.绘制被该视图remove但是扔在执行移除动画的子视图.
            if (mDisappearingChildren != null) {
                final ArrayList<View> disappearingChildren = mDisappearingChildren;
                final int disappearingCount = disappearingChildren.size() - 1;
              
                for (int i = disappearingCount; i >= 0; i--) {
                    final View child = disappearingChildren.get(i);
                    more |= drawChild(canvas, child, drawingTime);
                }
            }
     
          //5.通知重绘界面
            if ((flags & FLAG_INVALIDATE_REQUIRED) == FLAG_INVALIDATE_REQUIRED) {
                invalidate(true);
            }
            //6.通知布局动画绘制完成
            if ((flags & FLAG_ANIMATION_DONE) == 0 && (flags & FLAG_NOTIFY_ANIMATION_LISTENER) == 0 &&
                    mLayoutAnimationController.isDone() && !more) {
                final Runnable end = new Runnable() {
                   public void run() {
                       notifyAnimationListener();
                   }
                };
                post(end);
            }
        }
    

    6. View.draw(Canvas canvas, ViewGroup parent, long drawingTime)

    这个方法是通过ViewGroup.drawChild()调用的,不建议被重写.核心过程就是为该视图分配合理的canvas剪裁区

        boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
     
    
            final Animation a = getAnimation();//1.有动画就执行动画.没动画清除掉view变化的值.
            if (a != null) {
                more = drawAnimation(parent, drawingTime, a, scalingRequired);
                concatMatrix = a.willChangeTransformationMatrix();
                if (concatMatrix) {
                    mPrivateFlags3 |= PFLAG3_VIEW_IS_ANIMATING_TRANSFORM;
                }
                transformToApply = parent.getChildTransformation();
            }  
    
            // 2.判断子视图的canvas剪裁区是否超出父视图的剪切区,如果超过.就不绘制.
            if (!concatMatrix &&
                    (flags & (ViewGroup.FLAG_SUPPORT_STATIC_TRANSFORMATIONS |
                            ViewGroup.FLAG_CLIP_CHILDREN)) == ViewGroup.FLAG_CLIP_CHILDREN &&
                    canvas.quickReject(mLeft, mTop, mRight, mBottom, Canvas.EdgeType.BW) &&
                    (mPrivateFlags & PFLAG_DRAW_ANIMATION) == 0) {
                mPrivateFlags2 |= PFLAG2_VIEW_QUICK_REJECTED;
                return more;
            }
     
            if (!hasDisplayList) {
                computeScroll(); //3.计算视图的当前滚动值
                sx = mScrollX;
                sy = mScrollY;
            }
    
     
            if (offsetForScroll) {//3.通过计算的滚动值,将canvas的坐标原点由父视图的坐标原点改为子视图的左边原点
          //注意.一个视图的坐标远点不总是他的左上角.因为如果视图的内容可以滚动,导致部分内容不可见.这种视图的坐标远点其实是他内容的左上角,
                canvas.translate(mLeft - sx, mTop - sy);
            } else {
                if (!usingRenderNodeProperties) {
                    canvas.translate(mLeft, mTop);
                }
                if (scalingRequired) {
                    if (usingRenderNodeProperties) {
                        restoreTo = canvas.save();
                    }
                    // mAttachInfo cannot be null, otherwise scalingRequired == false
                    final float scale = 1.0f / mAttachInfo.mApplicationScale;
                    canvas.scale(scale, scale);
                }
            }
         
            //4.剪裁canvas.使他匹配当前视图的.
            if (!usingRenderNodeProperties) {
                if ((flags & ViewGroup.FLAG_CLIP_CHILDREN) == ViewGroup.FLAG_CLIP_CHILDREN
                        && cache == null) {
                    if (offsetForScroll) {
                        canvas.clipRect(sx, sy, sx + (mRight - mLeft), sy + (mBottom - mTop));
                    } else {
                        if (!scalingRequired || cache == null) {
                            canvas.clipRect(0, 0, mRight - mLeft, mBottom - mTop);
                        } else {
                            canvas.clipRect(0, 0, cache.getWidth(), cache.getHeight());
                        }
                    }
                }
     
            }
    
              //5.绘制视图.这里分有缓存和没缓存的处理, -代码不看了.
                if (!layerRendered) {
                    if (!hasDisplayList) {
        
                        if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
                            mPrivateFlags &= ~PFLAG_DIRTY_MASK;
                            dispatchDraw(canvas);
                            if (mOverlay != null && !mOverlay.isEmpty()) {
                                mOverlay.getOverlayView().draw(canvas);
                            }
                        } else {
                            draw(canvas);
                        }
                        drawAccessibilityFocus(canvas);
                    } else {
                        mPrivateFlags &= ~PFLAG_DIRTY_MASK;
                        ((HardwareCanvas) canvas).drawRenderNode(renderNode, null, flags);
                    }
                }
            }   
            return more;
        }
    
    3.png

    7.关于canvas的剪切.简单来讲就是理解这个图, ①是父视图的布局大小以及父视图具有的Canvas剪切大小. 子视图1表示子视图1的布局大小. 而②标识子视图1的内容大小.剪裁的过程就是把canvas从①剪裁到②,然后交给子视图1.而此时对于子视图1,他的canvas坐标远点是起内容的左上角,既数字②的位置.

    相关文章

      网友评论

          本文标题:view -draw过程

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