Android View生命周期:如何在正确时机获取View宽高

669 阅读2分钟

Android View生命周期:如何在正确时机获取View宽高


一、核心原因:View的渲染滞后于onCreate

在 Android 中,View 的宽高是在其渲染流程的测量(onMeasure布局(onLayout 阶段确定的。而这个渲染流程,发生在 ActivityonCreate 方法之后

  • onCreate 阶段setContentView() 方法只是将布局文件解析成一个视图树,并将其绑定到 ActivityWindow 上。此时,View 的宽高尚未被计算,其值通常为 0。
  • onResume 之后:在 ActivityonResume() 之后,DecorViewActivity 窗口的根视图)才会被添加到 WindowManager 中。这会触发 ViewRootImplperformTraversals() 方法,从而开始完整的 measurelayoutdraw 流程。

因此,onCreateonMeasure 之间存在一个时间差,这是导致在 onCreate 中无法获取 View 宽高的根本原因。


二、正确获取View宽高的三种方法

为了在 View 尺寸确定后执行操作,开发者需要使用适当的回调机制。

1. View.post()(推荐简单场景)

View.post() 方法将一个 Runnable 放入主线程的消息队列的队尾。由于 Viewmeasurelayout 也是在主线程中调度的,因此 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

  • ViewRootImplActivity 窗口的根。在 ActivityonResume() 之后,ActivityDecorView 被添加到 WindowManager 中,系统会为这个窗口创建一个 ViewRootImpl 实例。
  • ViewRootImpl 负责驱动 measurelayoutdraw 的完整流程。它会与 VSync 信号同步,确保每帧的渲染都在 16.6ms 的时间窗口内完成。