Android UI界面绘制
Android UI绘制从 ViewRootImpl 的 performTraversals() 方法开始,按照 测量(Measure) ->布局(Layout) -> 绘制(Draw) 的顺序,并从顶级View(DecorView)开始,递归地遍历整个View树,从而完成界面的测量、布局和最终绘制。
详细流程解析
第一步:绘制的发起者 - ViewRootImpl
ViewRootImpl 是连接WindowManager和DecorView的桥梁,它是整个绘制流程的总调度中心。
它的核心方法是 performTraversals() 。这个方法会根据需要依次执行三个关键操作:
- performMeasure() - 测量
- performLayout() - 布局
- performDraw() - 绘制
第二步:执行完整的绘制遍历 - performTraversals()
这是最核心的一步,它按顺序触发:
1、Measure (测量)
- 调用 performMeasure(int widthMeasureSpec, int heightMeasureSpec)
- 从DecorView开始,递归地调用所有子View的measure()方法,从而确定每个View的期望尺寸。
- 测量规格MeasureSpec由父View传递给子View,是父View的要求和子View的LayoutParams共同作用的结果。
- 关键方法
View.java
public final void measure(int widthMeasureSpec, int heightMeasureSpec);
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec);
------------------------------------------------------------------------------
ViewGroup.java中没有这两个方法。
- 方法调用
measure方法 (final修饰,不允许子类重写)->onMeasure方法->setMeasuredDimension方法->setMeasuredDimensionRaw方法
最终完成View的测量,将测量宽、高保存到View类的成员变量mMeasuredWidth和mMeasuredHeight。其中宽、高可以通过getMeasuredWidth()和getMeasuredHeight()方法获取得到。
2、Layout (布局)
- 调用 performLayout(boolean changed, int left, int top, int right, int bottom)
- 同样从DecorView开始,递归地调用所有子View的layout(int l, int t, int r, int b)方法。
- 父View会根据刚刚测量得到的尺寸,来确定每个子View在屏幕上的具体位置(四个顶点的坐标)。
- 关键方法
ViewGroup.java
public final void layout(int l, int t, int r, int b);
protected abstract void onLayout(boolean changed,int l, int t, int r, int b);
--------------------------------------------------------------------------
View.java
public void layout(int l, int t, int r, int b);
protected void onLayout(boolean changed, int left, int top, int right, int bottom);
3、Draw (绘制)
- 调用 performDraw()
- 这个方法会最终调用 draw(boolean fullRedrawNeeded)。
- 绘制流程同样递归进行。它不直接调用View的draw()方法,而是通过硬件加速或软件绘制的方式来进行:
硬件加速(默认):draw()方法会构建并记录一系列的绘制指令(DisplayList),然后将这些指令同步给RenderThread线程。由RenderThread在GPU上真正执行这些指令,将内容渲染到屏幕上。
软件绘制:直接调用View的draw(Canvas canvas)方法,在CPU上利用CanvasAPI一步步将内容绘制到Bitmap上,最终再将Bitmap提交给SurfaceFlinger。
常见问题
- 自定义View继承ViewGroup:必须重写onLayout方法。
- 当UI需要更新时(例如:你调用了View的invalidate()、requestLayout()、或者有动画在进行),最终都会触发ViewRootImpl的 scheduleTraversal() 方法。这个方法会通过Choreographer安排一个任务,在下一个VSYNC信号到来时执行doTraversal(),继而调用performTraversals()。
- View的invalidate()方法不会触发 View 的测量(measure)或布局(layout)流程,它只会标记一个脏区域,并在下一个绘制周期中仅触发重绘(Draw)流程。
- View的requestLayout()通常会触发测量(Measure)和布局(Layout)流程,并且有很高的概率会触发重绘(Draw)流程,但这并不是绝对的。
- 自定义View是否必须重写onMeasure、onLayout方法?
方法 | 继承ViewGroup | 继承View |
---|---|---|
onMeasure方法 | 必须重写 | 必须重写 |
onLayout方法 | 必须重写 | 不需要重写 |