Android进阶之路Android自定义ViewAndroid开发

Android高频面试专题 - 提升篇(二)View绘制流程

2020-02-27  本文已影响0人  Android扫地僧

View绘制流程,必问,请不要只会回答onMeasure,onLayout,onDraw,更多完整面试专题,请关注公众号获取。

1、View绘制的起点

提升篇(一)中提过,当建立好了decorView与ViewRoot的关联后,ViewRoot类的requestLayout()方法会被调用,以完成应用程序用户界面的初次布局。实际被调用的是ViewRootImpl类的requestLayout()方法,这个方法的主要源码如下:

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

上面的方法中调用了scheduleTraversals()方法来调度一次完成的绘制流程,该方法会向主线程发送一个“遍历”消息,最终会导致ViewRootImpl的performTraversals()方法被调用。下面,我们以performTraversals()为起点,来分析View的整个绘制流程。

2、View的绘制流程

View的绘制,有三个步骤:测量(measure),布局(layout),绘制(draw), 从DecorView自上而下遍历整个View树,注意是所有View执行完一个步骤后,再进行下一步,而不是一个View执行完所有步骤再遍历下一个View。

image

各步骤的主要工作:

3、简单介绍下MeasureSpec

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

image

其中,Mode模式共分为三类:

4、MeasureSpec的确定

这个没啥好说的,理解+记忆这个表格,子View的MeasureSpec由父View根据自身的MeasureSpec和子View的LayoutParams来共同确定子View的MeasureSpec,注意,即使确定了子View的MeasureSpec并不一定决定了子View的大小,自定义View可以根据需要修改这个值,最终通过setMeasuredDimension(width,height)设置最终大小。


5、View执行onMeasure,onLayout的次数

分析ViewRootImpl的源码,scheduleTraversales()内部会执行postCallBack触发mTraversalRunnable重新走一遍performTraversals(),第二次执行performTraversals()就会触发performDraw()。所以performTraversals()走了两次,那么肯定会走2次measure方法,但不一定走2次onMeasure(),读过View measure方法源码的都应知道measure方法做了2级测量优化:

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

getMeasuredWidth()、getMeasuredHeight()必须在onMeasure之后使用才有效)getMeasuredWidth() 的取值最终来源于 setMeasuredDimension() 方法调用时传递的参数, getWidth()返回的是,mRight - mLeft,mRight、mLeft 变量分别表示 View 相对父容器的左右边缘位置,getWidth()与getHeight()方法必须在layout(int l, int t, int r, int b)执行之后才有效

7、如何在onCreate中拿到View的宽度和高度

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监听处理,还不需要手动移除观察者监听事件。

监听View的onLayout()绘制过程,一旦layout触发变化,立即回调onLayoutChange方法。
注意,使用完也要主要调用removeOnGlobalListener()方法移除监听事件。避免后续每一次发生全局View变化均触发该事件,影响性能。

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());
            }
        });

8、invalidate和postInvalidate区别

二者都会出发刷新View,并且当这个View的可见性为VISIBLE的时候,View的onDraw()方法将会被调用,invalidate()方法在 UI 线程中调用,重绘当前 UI。postInvalidate() 方法在非 UI 线程中调用,通过Handler通知 UI 线程重绘。

9、requestLayout()的作用

requestLayout()也可以达到重绘view的目的,但是与前两者不同,它会先调用onLayout()重新排版,再调用ondraw()方法。当view确定自身已经不再适合现有的区域时,该view本身调用这个方法要求parent view(父类的视图)重新调用他的onMeasure、onLayout来重新设置自己位置。特别是当view的layoutparameter发生改变,并且它的值还没能应用到view上时,这时候适合调用这个方法requestLayout()。

10、onDraw() 和dispatchDraw()的区别

draw过程会调用onDraw(Canvas canvas)方法,然后就是dispatchDraw(Canvas canvas)方法, dispatchDraw()主要是分发给子组件进行绘制,我们通常定制组件的时候重写的是onDraw()方法。值得注意的是ViewGroup容器组件的绘制,当它没有背景时直接调用的是dispatchDraw()方法, 而绕过了draw()方法,当它有背景的时候就调用draw()方法,而draw()方法里包含了dispatchDraw()方法的调用。因此要在ViewGroup上绘制东西的时候往往重写的是dispatchDraw()方法而不是onDraw()方法,或者自定制一个Drawable,重写它的draw(Canvas c)和 getIntrinsicWidth()方法,然后设为背景。


在这我也分享一份自己收录整理的Android学习PDF+架构视频+面试文档+源码笔记,还有高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料这些都是我闲暇还会反复翻阅的精品资料。在脑图中,每个知识点专题都配有相对应的实战项目,可以有效的帮助大家掌握知识点

总之也是在这里帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习

image.png

关注微信公众号“Android扫地僧”(微信->添加朋友->公众号->输入“Android扫地僧”)
自动回复,即可获取下载地址

扫码领取资源.png
上一篇 下一篇

猜你喜欢

热点阅读