view -draw过程

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坐标远点是起内容的左上角,既数字②的位置.

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容