Android View生命周期:如何在正确时机获取View宽高
一、核心原因:View的渲染滞后于onCreate
在 Android 中,View 的宽高是在其渲染流程的测量(onMeasure) 和布局(onLayout) 阶段确定的。而这个渲染流程,发生在 Activity 的 onCreate 方法之后。
onCreate阶段:setContentView()方法只是将布局文件解析成一个视图树,并将其绑定到Activity的Window上。此时,View的宽高尚未被计算,其值通常为 0。onResume之后:在Activity的onResume()之后,DecorView(Activity窗口的根视图)才会被添加到WindowManager中。这会触发ViewRootImpl的performTraversals()方法,从而开始完整的measure、layout和draw流程。
因此,onCreate 和 onMeasure 之间存在一个时间差,这是导致在 onCreate 中无法获取 View 宽高的根本原因。
二、正确获取View宽高的三种方法
为了在 View 尺寸确定后执行操作,开发者需要使用适当的回调机制。
1. View.post()(推荐简单场景)
View.post() 方法将一个 Runnable 放入主线程的消息队列的队尾。由于 View 的 measure 和 layout 也是在主线程中调度的,因此 post 中的代码总是会在 measure/layout 之后执行。
Kotlin
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val textView = findViewById<TextView>(R.id.tv_hello)
textView.post {
// 这段代码在主线程空闲时执行,此时宽高已确定
Log.d("Size", "宽度:${textView.width},高度:${textView.height}")
}
}
2. ViewTreeObserver(推荐复杂场景)
ViewTreeObserver 允许你监听视图树的全局变化。
addOnGlobalLayoutListener:在视图树的布局发生变化时被调用。它可能会被频繁调用(如软键盘弹出),因此你需要在使用后手动移除监听。OnPreDrawListener:在视图树即将被绘制时调用。这是一个更精确的时机,因为它发生在layout之后、draw之前,允许你在绘制前进行最终的修改。
// 使用 OnGlobalLayoutListener
textView.viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
Log.d("Size", "宽度:${textView.width},高度:${textView.height}")
// 确保只执行一次
textView.viewTreeObserver.removeOnGlobalLayoutListener(this)
}
})
3. onWindowFocusChanged(适用于界面显示后)
当 Activity 获得或失去焦点时,会触发 onWindowFocusChanged 方法。当首次获得焦点时,所有视图都已完成测量和布局。
override fun onWindowFocusChanged(hasFocus: Boolean) {
super.onWindowFocusChanged(hasFocus)
// 确保只有在界面获得焦点时才执行,避免重复调用
if (hasFocus) {
val textView = findViewById<TextView>(R.id.tv_hello)
Log.d("Size", "宽度:${textView.width},高度:${textView.height}")
}
}
四、View渲染的幕后推手
理解 View 的渲染,需要关注一个关键的幕后角色:ViewRootImpl。
ViewRootImpl是Activity窗口的根。在Activity的onResume()之后,Activity的DecorView被添加到WindowManager中,系统会为这个窗口创建一个ViewRootImpl实例。ViewRootImpl负责驱动measure、layout和draw的完整流程。它会与VSync信号同步,确保每帧的渲染都在 16.6ms 的时间窗口内完成。