简短结论:
- onCreate() 里通常拿不到 View 的最终宽/高(多为 0) 。
- 原因是 measure/layout/draw 尚未发生;只有首帧布局完成后视图才有确定尺寸。
- 正确拿法:把读取尺寸的代码放到布局完成之后的回调里(如 doOnLayout、OnGlobalLayoutListener、view.post{} 等)。
为啥 onCreate 拿不到?
setContentView() 只完成 inflate + 加入层级。真正的尺寸在 ViewRootImpl 发起的首帧“遍历”(performMeasure → performLayout → performDraw)里计算;这一步是由 Choreographer 安排到稍后的主线程帧执行的,时间线大致如下:
onCreate() → onStart() → onResume() → (窗口附着)
→ 首帧 traversal: measure → layout → draw
→ 此后 view.width / view.height 才稳定
因此在 onCreate() 立即读取,常得到 0。
正确获取尺寸的几种方式(按推荐度)
- KTX: doOnLayout {} (最语义化)
view.doOnLayout { v ->
val w = v.width
val h = v.height
// 使用 w/h
}
- 全局布局回调: ViewTreeObserver.OnGlobalLayoutListener
view.viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
view.viewTreeObserver.removeOnGlobalLayoutListener(this)
val w = view.width
val h = view.height
}
})
-
view.post { ... } (常见且简单)
把任务投递到 UI 线程消息队列,等本次生命周期回调结束、通常在首帧布局之后再执行,所以此时可读到尺寸。
view.post {
val w = view.width
val h = view.height
}
post 的本质是“稍后在主线程执行”,一般能落在首次布局之后;若需要“下一帧”语义,用 view.postOnAnimation { ... }。
-
窗口获得焦点: onWindowFocusChanged(true)
通常首帧已完成,也能读到尺寸(但依赖焦点,不如上面两个直接)。
-
自定义 View 回调
重写 onSizeChanged(w,h,oldw,oldh):尺寸一变就会回调,最直接可靠。
Compose 场景补充:用 Modifier.onGloballyPositioned { coordinates.size }。
常见坑
- wrap_content 但无内容/约束 → 测量可能仍是 0。
- GONE 不参与测量,宽高为 0;INVISIBLE 会被测量。
- RecyclerView 的 item 在 onBindViewHolder 里读尺寸多为 0,改到 itemView.post {} 或 onViewAttachedToWindow()。
- OnGlobalLayoutListener 会多次触发,若只需一次要记得移除。
一句话记忆
onCreate 拿不到是因为还没走到 measure/layout;把代码挪到“布局完成之后”的回调里(doOnLayout / post / OnGlobalLayoutListener / onSizeChanged)即可稳定拿到宽高。