• Android中的view绘制流程,简单理解


    简单理解

    Android中的View类代表用户界面中基本的构建块。一个View在屏幕中占据一个矩形区域、并且负责绘制和事件处理。View是所有widgets的基础类,widgets是我们通常用于创建和用户交互的组件,比如按钮、文本输入框等等。子类ViewGroup是所有布局(layout)的基础类。layout是一个不看见的容器,里面堆放着其他的view或者ViewGroup,并且设置他们的布局属性。

    所有的view在窗口中是以树状结构来管理。你可以通过代码或者编辑xml布局文件来添加一个view。view有很多的子类来负责控制或者有能力展示图片,文字等。

    Android绘制View

    当一个Activity启动时,会被要求绘制出它的布局。Android框架会处理这个请求,当然前提是Activity提供了合理的布局。绘制从根视图开始,从上至下遍历整棵视图树,每一个ViewGroup负责让自己的子View被绘制,每一个View负责绘制自己,通过draw()方法.绘制过程分三步走。

    • Measure
    • Layout
    • Draw

    整个绘制流程是在ViewRoot中的performTraversals()方法展开的。部分源代码如下。

    private void performTraversals() {
        ......
        //最外层的根视图的widthMeasureSpec和heightMeasureSpec由来
        //lp.width和lp.height在创建ViewGroup实例时等于MATCH_PARENT
        int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
        int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
        ......
        mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        ......
        mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
        ......
        mView.draw(canvas);
        ......
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    在绘制之前当然要知道view的尺寸和绘制。所以先进行measu和layout(测量和定位),如下图。

    Measure过程

    public final void measure(int widthMeasureSpec, int heightMeasureSpec) {  
        //....  
    ​
        //回调onMeasure()方法    
        onMeasure(widthMeasureSpec, heightMeasureSpec);  
    ​
        //more  
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    计算view的实际大小,获得高宽存入mMeasuredHeight和mMeasureWidth,measure(int, int)传入的两个参数。MeasureSpec是一个32位int值,高2位为测量的模式,低30位为测量的大小。测量的模式可以分为以下三种。

    • EXACTLY

    精确值模式,当layout_width或layout_height指定为具体数值,或者为match_parent时,系统使用EXACTLY。

    • AT_MOST

    最大值模式,指定为wrap_content时,控件的尺寸不能超过父控件允许的最大尺寸。

    • UNSPECIFIED

    不指定测量模式,View想多大就多大,一般不太使用。

    根据上面的源码可知,measure方法不可被重写,自定义时需要重写的是onMeasure方法

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                    getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
        }
    
    • 1
    • 2
    • 3
    • 4

    查看源码可知最终的高宽是调用setMeasuredDimension()设定的,如果不重写,默认是直接调用getDefaultSize获取尺寸的。

    使用View的getMeasuredWidth()和getMeasuredHeight()方法来获取View测量的宽高,必须保证这两个方法在onMeasure流程之后被调用才能返回有效值。

    Layout过程

    Layout方法就是用来确定view布局的位置,就好像你知道了一件东西的大小以后,总要知道位置才能画上去。

    mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight());
    
    • 1

    layout获取四个参数,左,上,右,下坐标,相对于父视图而言。这里可以看到,使用了刚刚测量的宽和高。

    public void layout(int l, int t, int r, int b) {
        int oldL = mLeft;
        int oldT = mTop;
        int oldB = mBottom;
        int oldR = mRight;
        boolean changed = setFrame(l, t, r, b);
        if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) {
            .....
            onLayout(changed, l, t, r, b);
            .....
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    通过setFrame设置坐标。如果坐标改变过了,则重新进行定位。如果是View对象,那么onLayout是个空方法。因为定位是由ViewGroup确定的。

    当layout结束以后getWidth()与getHeight()才会返回正确的值。

    这里出现一个问题,getWidth/Height() 和 getMeasuredWidth/Height()有什么区别?

    • getWidth():View在设定好布局后整个View的宽度。
    • getMeasuredWidth():对View上的內容进行测量后得到的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);
            ......
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    重点是第三步调用onDraw方法。其它几步都是绘制一些边边角角的东西比如背景、scrollBar之类的。其中dispatchDraw,是用来递归调用子View,如果没有则不需要。本文主要解析了Android view的绘制,更深入的学习或者Android开发进阶,可以前往《Android核心架构笔记》查看详细的学习类目。

    总结

    • View是Android中可视化UI组件的实体。
    • View的呈现依赖于Activity,是Activity所容纳的基本元素。
    • View主要提供了组件绘制和事件处理的方法。
    • View可以分为容器类型和实体类型。
    • 容器类型的View(ViewGroup)可容纳其它的容器类型View和实体类型View。
    • 实体类型的View主要用于用户交互,如:按钮,文本框。
  • 相关阅读:
    【前端面试题3】
    TeeChart .NET 4.2023.10.25 Crack
    【iOS开发】(六)react Native 路由嵌套传参与框架原理(完)20240423
    makefile的基础使用
    深度模型中的优化(三)、梯度下降及其优化
    【npm】常用的NPM命令及在开发过程中的应用
    详解 Moloch DAO 特性与治理模式
    基于长短期记忆神经网络的锂电池寿命预测
    Maximum And Queries (easy version)
    BDD - SpecFlow & SpecRun Web UI 多浏览器测试
  • 原文地址:https://blog.csdn.net/m0_70748845/article/details/132697665