Android中View的更新方法:invalidate()和requestLayout()
Activity创建View
DecorView下是一个竖直方向的LinearLayout,包含一个id为android.R.id.content
的子View,在Activity的onCreate()
中调用setContentView()
就是设置这个contentView。
Activity的onCreate()
方法调用之前,创建Window(PhoneWindow)
Activity的onCreate方法中调用setContentView()
方法,创建DecorView
和contentView
,将指定的布局文件加载到contentView中
Activity的onResume方法调用之后,将DecorView添加到Window中,之后依次开始View的measure、layout和draw流程
三大流程
performTraversals()
方法内会依次调用measureHierarchy()
、performLayout()
、performDraw()
,进而开始View的三大流程:测量measure、布局layout、绘制draw
measure
measure流程的作用是对View的大小进行测量。
MeasureSpec
类,由一个32位int值表示,高2位表示SpecMode(测量模式),低30位表示SpecSize(测量尺寸大小),通过位运算实现。
三种测量模式:
- UNSPECIFIED:父View不会限制子View的大小,一般用于系统内部,开发中使用很少
- EXACTLY:父View能够确定子View的大小,如
match_parent
或给定精确尺寸(dp或px) - AT_MOST:子View的大小不能超过父View尺寸,具体尺寸需要由子View自身来确定,对应
wrap_content
确定View尺寸的方法:
- 当子View的LayoutParams指定为精确数值时,不管父View的测量模式是什么,子View的测量模式均为EXACTLY,测量尺寸为LayoutParams指定的值
- 当子View的LayoutParams指定为match_parent时,子View的测量模式取决于父View,即如果父View的测量模式为EXACTLY,那么子View的测量模式为EXACTLY;如果父View的测量模式为AT_MOST,那么子View的测量模式为AT_MOST,子View的测量尺寸均为父View可用空间大小
- 当子View的LayoutParams指定为wrap_content时,不管父View的测量模式是什么,子View的测量模式均为AT_MOST,测量尺寸为父View可用空间大小
对于最顶层的DecorView:
- DecorView的LayoutParams指定为MATCH_PARENT时,它的测量模式为EXACTLY,测量尺寸为屏幕尺寸
- DecorView的LayoutParams指定为WRAP_CONTENT时,它的测量模式为WRAP_CONTENT,测量尺寸为屏幕尺寸
View的measure流程:
ViewGroup的measure流程:
ViewGroup中并没有重写onMeasure()方法,因为每个ViewGroup的布局方式都不一样,无法得出一个统一的实现方式,需要不同ViewGroup自己去实现。
遍历子View,获取每个子View的MeasureSpec,然后调用每个子View的measure()方法进行测量。再根据子View的测量结果对ViewGroup自身进行测量。
layout
layout的作用是根据测量大小确定View的最终位置。
layout()
方法中比较left、right、top、bottom是否改变,若改变则执行onLayout()
。对于单一的View,onLayout()中不做任何事。对于ViewGroup,调用抽象的onLayout()方法(同样需要子类自己去实现),遍历子View并调用其layout()方法确定位置,不断递归。
draw
draw的作用是将View绘制到屏幕上。
- 调用drawBackground()方法绘制背景
- 调用onDraw()方法绘制自身内容
- 调用dispatchDraw()方法绘制子View
- 调用onDrawForeground()方法绘制装饰,包括滚动条和前景
其中onDraw()是抽象方法,需要不同的View自己去实现绘制方式。
在单一的View中dispatchDraw()是空方法,ViewGroup则实现了其逻辑,遍历所有的子View并调用其draw()方法。
invalidate、postInvalidate 与 requestLayout
三者都是View中的方法,用于刷新视图,最终都会调用ViewRootImpl
的performTraversals()
方法。
- invalidate不会执行
measure
和layout
流程,只执行draw
流程。 - invalidate与requestLayout都必须在主线程调用,要在子线程中刷新视图用
postInvalidate
方法
调用View
的invalidate()
方法后会逐级调用父View的方法,最终导致ViewRootImpl
的scheduleTraversals()
方法被调用,进而调用performTraversals()
方法。由于mLayoutRequested
的值为false
,因此不会执行measure
和layout
流程,只执行draw
流程。
draw流程的执行过程和是否开启硬件加速有关。如果关闭了硬件加速,从DecorView
开始的所有View都会重新完成绘制。如果开启了硬件加速,只有调用invalidate()
方法的View(包括它的子View)会完成重新绘制。由此也可以看出,开启硬件加速确实可以提高重绘的效率。
postInvalidate
其实就是通过Handler完成了线程的切换,使得invalidate()方法在主线程中被调用。