一、核心原理:View的渲染滞后于Activity生命周期
在 Android 中,View 的尺寸是在其渲染流程的测量(onMeasure) 和布局(onLayout) 阶段确定的。而这个渲染流程,发生在 Activity 的 onCreate 和 onResume 方法之后。
onCreate阶段:setContentView()方法只是将布局文件解析成一个视图树,并将其绑定到Activity的Window上。此时,View的宽高尚未被计算,其值通常为 0。onResume之后:在Activity的onResume()之后,DecorView(Activity窗口的根视图)才会被添加到WindowManager中,并触发ViewRootImpl的performTraversals()方法,从而开始完整的measure、layout和draw流程。
二、正确获取View尺寸的四种方法
为了在 View 尺寸确定后执行操作,开发者需要使用适当的回调机制。
1. View.post():通用且可靠
-
原理:
View.post()将一个Runnable放入主线程的消息队列的队尾。由于View的measure和layout也是在主线程中调度的,因此post中的代码总是会在measure/layout之后执行。 -
代码示例:
View view = findViewById(R.id.my_view); view.post(() -> { int width = view.getWidth(); int height = view.getHeight(); });
2. ViewTreeObserver:监听布局变化
-
原理:
ViewTreeObserver允许你监听视图树的全局变化。OnGlobalLayoutListener在布局发生变化时被调用,OnPreDrawListener在视图树即将被绘制时调用。 -
代码示例:
View view = findViewById(R.id.my_view); view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { view.getViewTreeObserver().removeOnGlobalLayoutListener(this); int width = view.getWidth(); int height = view.getHeight(); } });
3. onWindowFocusChanged:窗口焦点变化
-
原理:当
Activity窗口获得焦点时,所有 View 都已完成首次布局。 -
代码示例:
@Override public void onWindowFocusChanged(boolean hasFocus) { super.onWindowFocusChanged(hasFocus); if (hasFocus) { View view = findViewById(R.id.my_view); int width = view.getWidth(); int height = view.getHeight(); } }
4. View.onLayout():自定义ViewGroup
-
原理:对于自定义
ViewGroup,可以在onLayout方法中获取其尺寸,并根据尺寸来布局其子View。 -
代码示例:
public class CustomViewGroup extends ViewGroup { @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { int width = getWidth(); int height = getHeight(); } }
三、View渲染的幕后推手
理解 View 的渲染,需要关注一个关键的幕后角色:ViewRootImpl。
ViewRootImpl是Activity窗口的根。在Activity的onResume()之后,Activity的DecorView被添加到WindowManager中,系统会为这个窗口创建一个ViewRootImpl实例。ViewRootImpl负责驱动measure、layout和draw的完整流程。它会与VSync信号同步,确保每帧的渲染都在 16.6ms 的时间窗口内完成。
结论:
通过理解 View 的测量流程和系统源码,开发者可以确保在正确时机获取尺寸,避免因时机不当导致的错误。