Android中View的绘制流程

一、View绘制的起点

当建立好了decorView与ViewRoot的关联后,ViewRoot类的requestLayout()方法会被调用,以完成应用程序用户界面的初次布局。实际被调用的是ViewRootImpl类的requestLayout()方法

java 复制代码
@Override
public void requestLayout() {
  if (!mHandlingLayoutInLayoutRequest) {
    // 检查发起布局请求的线程是否为主线程
    checkThread();
    mLayoutRequested = true;
    scheduleTraversals();
  }
}

requestLayout()方法调用了scheduleTraversals()方法来调度一次完成的绘制流程,该方法会向主线程发送一个"遍历"消息,最终会导致ViewRootImpl的performTraversals()方法被调用

二、View的绘制流程

View的绘制,有三个步骤:测量(measure),布局(layout),绘制(draw), 从DecorView自上而下遍历整个View树

**注意:**是所有View执行完一个步骤后,再进行下一步,而不是一个View执行完所有步骤再遍历下一个View

  • Measure:测量视图大小。从顶层父View到子View递归调用measure方法,measure方法又回调OnMeasure。

  • Layout:确定View位置,进行页面布局。从顶层父View向子View的递归调用view.layout方法的过程,即父View根据上一步measure子View所得到的布局大小和布局参数,将子View放在合适的位置上。

  • Draw:绘制视图。ViewRoot创建一个Canvas对象,然后调用OnDraw()。

    六个步骤:

    1. 绘制视图的背景

    2. 保存画布的图层(Layer)

    3. 绘制View的内容

    4. 绘制View子视图,如果没有就不用

    5. 还原图层(Layer)

    6. 绘制滚动条

三、MeasureSpec详解

MeasureSpec由两部分组成,一部分是测量模式,另一部分是测量的尺寸大小

Mode模式分为三类:

  • EXACTLY:对应LayoutParams中的match_parent和具体数值这两种模式。检测到View所需要的精确大小,这时候View的最终大小就是SpecSize所指定的值,

  • AT_MOST :对应LayoutParams中的wrap_content。View的大小不能大于父容器的大小。

  • UNSPECIFIED :不对View进行任何限制,要多大给多大,一般用于系统内部,如ListView,ScrollView

MeasureSpec的确定:

子View的MeasureSpec由父View根据自身的MeasureSpec和子View的LayoutParams来共同确定子View的MeasureSpec

**注意:**即使确定了子View的MeasureSpec并不一定决定了子View的大小,自定义View可以根据需要修改这个值,最终通过setMeasuredDimension(width, height)设置最终大小。

四、View执行onMeasure,onLayout的次数

根据ViewRootImpl的源码,scheduleTraversales()内部会执行postCallBack触发mTraversalRunnable重新走一遍performTraversals(),第二次执行performTraversals()就会触发performDraw()。所以performTraversals()走了两次,那么肯定会走2次measure方法

但不一定走2次onMeasure(),measure方法做了2级测量优化:

  • 1.如果flag不为forceLayout或者与上次测量规格(MeasureSpec)相比未改变,那么将不会进行重新测量(执行onMeasure方法),直接使用上次的测量值;

  • 2.如果满足非强制测量的条件,即前后二次测量规格不一致,会先根据目前测量规格生成的key索引缓存数据,索引到就无需进行重新测量;如果targetSDK小于API 20则二级测量优化无效,依旧会重新测量,不会采用缓存测量值。

五、getWidth()和getMeasuredWidth()的区别

getMeasuredWidth()、getMeasuredHeight()必须在onMeasure之后使用才有效getMeasuredWidth() 的取值最终来源于 setMeasuredDimension() 方法调用时传递的参数

getWidth()返回的是;mRight - mLeft,mRight、mLeft 变量分别表示View相对父容器的左右边缘位置,getWidth()必须在layout(int l, int t, int r, int b)执行之后才有效

六、如何在onCreate中拿到View的宽高

View.post(runnable)

java 复制代码
view.post(new Runnable() {            
            @Override
            public void run() {
                int width = view.getWidth();
                int measuredWidth = view.getMeasuredWidth();
                Log.i(TAG, "width: " + width);
                Log.i(TAG, "measuredWidth: " + measuredWidth);
            }
        });

利用Handler通信机制,发送一个Runnable到MessageQueue中,当View布局处理完成时,自动发送消息,通知UI进程。获取View的高宽属性,代码简洁

ViewTreeObserver.addOnGlobalLayoutListener()

java 复制代码
       ViewTreeObserver vto = view.getViewTreeObserver();       
       vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                view.getViewTreeObserver().removeGlobalOnLayoutListener(this);
                Log.i(TAG, "width: " + view.getWidth());
                Log.i(TAG, "height: " + view.getHeight());
            }
        });

监听View的onLayout()绘制过程,一旦layout触发变化,立即回调onLayoutChange方法

**注意:**使用完也要主要调用removeOnGlobalListener()方法移除监听事件。避免后续每一次发生全局View变化均触发该事件,影响性能

七、invalidate和postInvalidate区别

二者都会触发刷新View,并且当这个View的可见性为VISIBLE的时候,View的onDraw()方法将会被调用

invalidate()方法在UI线程中调用,重绘当前 UI

postInvalidate()方法在非 UI 线程中调用,通过Handler通知 UI 线程重绘

相关推荐
m0_7482359521 分钟前
CentOS 7使用RPM安装MySQL
android·mysql·centos
ac-er88884 小时前
Yii框架中的队列:如何实现异步操作
android·开发语言·php
流氓也是种气质 _Cookie6 小时前
uniapp 在线更新应用
android·uniapp
zhangphil8 小时前
Android ValueAnimator ImageView animate() rotation,Kotlin
android·kotlin
徊忆羽菲8 小时前
CentOS7使用源码安装PHP8教程整理
android
编程、小哥哥9 小时前
python操作mysql
android·python
Couvrir洪荒猛兽10 小时前
Android实训十 数据存储和访问
android
五味香12 小时前
Java学习,List 元素替换
android·java·开发语言·python·学习·golang·kotlin
十二测试录13 小时前
【自动化测试】—— Appium使用保姆教程
android·经验分享·测试工具·程序人生·adb·appium·自动化
Couvrir洪荒猛兽14 小时前
Android实训九 数据存储和访问
android