博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
View绘制流程第三步:递归draw源码分析
阅读量:6429 次
发布时间:2019-06-23

本文共 5995 字,大约阅读时间需要 19 分钟。

4 View绘制流程第三步:递归draw源码分析

在上面的背景介绍就说过,当ViewRootImpl的performTraversals中measure和layout执行完成以后会接着执行mView.layout,具体如下:

private void performTraversals() {    ......    final Rect dirty = mDirty;    ......    canvas = mSurface.lockCanvas(dirty);    ......    mView.draw(canvas);    ......}复制代码

draw过程也是在ViewRootImpl的performTraversals()内部调运的,其调用顺序在measure()和layout()之后,这里的mView对于Actiity来说就是PhoneWindow.DecorView,ViewRootImpl中的代码会创建一个Canvas对象,然后调用View的draw()方法来执行具体的绘制工。所以又回归到了ViewGroup与View的树状递归draw过程。

先来看下View树的递归draw流程图,如下:

如下我们详细分析这一过程。

4-1 draw源码分析

由于ViewGroup没有重写View的draw方法,所以如下直接从View的draw方法开始分析:

public void draw(Canvas canvas) {        ......        /*         * 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        ......        if (!dirtyOpaque) {            drawBackground(canvas);        }        // skip step 2 & 5 if possible (common case)        ......        // Step 2, save the canvas' layers        ......            if (drawTop) {                canvas.saveLayer(left, top, right, top + length, null, flags);            }        ......        // 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        ......        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);        }        ......        // Step 6, draw decorations (scrollbars)        onDrawScrollBars(canvas);        ......    }复制代码

看见整个View的draw方法很复杂,但是源码注释也很明显。从注释可以看出整个draw过程分为了6步。源码注释说(”skip step 2 & 5 if possible (common case)”)第2和5步可以跳过,所以我们接下来重点剩余四步。如下:

第一步,对View的背景进行绘制。

可以看见,draw方法通过调运drawBackground(canvas);方法实现了背景绘制。我们来看下这个方法源码,如下:

private void drawBackground(Canvas canvas) {        //获取xml中通过android:background属性或者代码中setBackgroundColor()、setBackgroundResource()等方法进行赋值的背景Drawable        final Drawable background = mBackground;        ......        //根据layout过程确定的View位置来设置背景的绘制区域        if (mBackgroundSizeChanged) {            background.setBounds(0, 0,  mRight - mLeft, mBottom - mTop);            mBackgroundSizeChanged = false;            rebuildOutline();        }        ......            //调用Drawable的draw()方法来完成背景的绘制工作            background.draw(canvas);        ......    }复制代码

第三步,对View的内容进行绘制。

可以看到,这里去调用了一下View的onDraw()方法,所以我们看下View的onDraw方法(ViewGroup也没有重写该方法),如下:

/**     * Implement this to do your drawing.     *     * @param canvas the canvas on which the background will be drawn     */    protected void onDraw(Canvas canvas) {    }复制代码

可以看见,这是一个空方法。因为每个View的内容部分是各不相同的,所以需要由子类去实现具体逻辑。

第四步,对当前View的所有子View进行绘制,如果当前的View没有子View就不需要进行绘制。

我们来看下View的draw方法中的dispatchDraw(canvas);方法源码,可以看见如下:

/**     * Called by draw to draw the child views. This may be overridden     * by derived classes to gain control just before its children are drawn     * (but after its own view has been drawn).     * @param canvas the canvas on which to draw the view     */    protected void dispatchDraw(Canvas canvas) {    }复制代码

看见没有,View的dispatchDraw()方法是一个空方法,而且注释说明了如果View包含子类需要重写他,所以我们有必要看下ViewGroup的dispatchDraw方法源码(这也就是刚刚说的对当前View的所有子View进行绘制,如果当前的View没有子View就不需要进行绘制的原因,因为如果是View调运该方法是空的,而ViewGroup才有实现),如下:

@Override    protected void dispatchDraw(Canvas canvas) {        ......        final int childrenCount = mChildrenCount;        final View[] children = mChildren;        ......        for (int i = 0; i < childrenCount; i++) {            ......            if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {                more |= drawChild(canvas, child, drawingTime);            }        }        ......        // Draw any disappearing views that have animations        if (mDisappearingChildren != null) {            ......            for (int i = disappearingCount; i >= 0; i--) {                ......                more |= drawChild(canvas, child, drawingTime);            }        }        ......    }复制代码

可以看见,ViewGroup确实重写了View的dispatchDraw()方法,该方法内部会遍历每个子View,然后调用drawChild()方法,我们可以看下ViewGroup的drawChild方法,如下:

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

可以看见drawChild()方法调运了子View的draw()方法。所以说ViewGroup类已经为我们重写了dispatchDraw()的功能实现,我们一般不需要重写该方法,但可以重载父类函数实现具体的功能。

第六步,对View的滚动条进行绘制。

可以看到,这里去调用了一下View的onDrawScrollBars()方法,所以我们看下View的onDrawScrollBars(canvas);方法,如下:

/**     * 

Request the drawing of the horizontal and the vertical scrollbar. The * scrollbars are painted only if they have been awakened first.

* * @param canvas the canvas on which to draw the scrollbars * * @see #awakenScrollBars(int) */ protected final void onDrawScrollBars(Canvas canvas) { //绘制ScrollBars分析不是我们这篇的重点,所以暂时不做分析 ...... }复制代码

可以看见其实任何一个View都是有(水平垂直)滚动条的,只是一般情况下没让它显示而已。

到此,View的draw绘制部分源码分析完毕,我们接下来进行一些总结。

4-2 draw原理总结

可以看见,绘制过程就是把View对象绘制到屏幕上,整个draw过程需要注意如下细节:

  • 如果该View是一个ViewGroup,则需要递归绘制其所包含的所有子View。

  • View默认不会绘制任何内容,真正的绘制都需要自己在子类中实现。

  • View的绘制是借助onDraw方法传入的Canvas类来进行的。

  • 区分View动画和ViewGroup布局动画,前者指的是View自身的动画,可以通过setAnimation添加,后者是专门针对ViewGroup显示内部子视图时设置的动画,可以在xml布局文件中对ViewGroup设置layoutAnimation属性(譬如对LinearLayout设置子View在显示时出现逐行、随机、下等显示等不同动画效果)。

  • 在获取画布剪切区(每个View的draw中传入的Canvas)时会自动处理掉padding,子View获取Canvas不用关注这些逻辑,只用关心如何绘制即可。

  • 默认情况下子View的ViewGroup.drawChild绘制顺序和子View被添加的顺序一致,但是你也可以重载ViewGroup.getChildDrawingOrder()方法提供不同顺序。

                       

                                                         关注『DvlpNews』

                                                         把握前沿技术脉搏

转载地址:http://yziga.baihongyu.com/

你可能感兴趣的文章
ASP.NET中 Repeater 的使用前台绑定
查看>>
微信公众平台模拟群发技术
查看>>
C语言学习之指针详解
查看>>
学习使用Bing Maps Silverlight Control(一):准备和新建
查看>>
什么是Scrum
查看>>
nginx负载均衡的5种策略
查看>>
90%人都不知道:SVN 和 Git 的一些误解和真相
查看>>
防火墙配置十大任务之九,验证防火墙的运行
查看>>
【linux】浅谈Linux下的 find 指令
查看>>
CentOS 7 使用kubeadm 部署 Kubernetes
查看>>
我的友情链接
查看>>
透视美国大数据爆发全景
查看>>
java学习第一天1.2
查看>>
清空输入缓冲区的方法
查看>>
Yii2 项目优化小贴士
查看>>
UIScrollView的判断位置的属性如下:
查看>>
Applicatin Loader上传app步骤记录
查看>>
两种方法修改table表的内容
查看>>
张小龙莫慌 马化腾莫急 你们要好好的 微信还有时间
查看>>
一些常用软件静默安装参数(nsis,msi,InstallShield ,Inno)
查看>>