view.requestLayout()
view.requestLayout() 是 Android 视图系统中的一个关键方法,它的作用是标记当前视图(View)需要重新测量(measure)和布局(layout),但不会立即执行,而是等待系统下一次界面刷新时统一处理。具体来说View.requestLayout()做了以下几件事:
- 通知 View 系统:这个 View 需要重新测量和布局(measure 和 layout)。
- 触发从该 View 开始,向上逐层传播请求,最终可能影响整个 View 树。
- 下一帧时,
View会在onMeasure()和onLayout()阶段重新处理这个 View。
具体作用
- 触发重新布局:当你调用
view.requestLayout()时,View 会向其父容器请求重新执行测量(measure)、布局(layout)过程,以确保 View 及其子 View 的位置和大小正确更新。 - 适用于动态变化:如果 View 的属性(如宽度、高度、位置、内容等)发生变化,调用此方法可以确保布局反映这些变化。例如,动态调整 View 的大小、添加或移除子 View、更改文本内容等场景。
- 异步操作:调用
requestLayout()不会立即触发重新布局,而是将请求加入到系统的布局队列中,等待下一次布局周期(通常在下一帧)执行。 - **影响范围:**不仅影响调用该方法的 View,还可能影响其父容器和整个 View 树(取决于布局依赖关系)。
使用场景
你需要在以下情况调用view.requestLayout():
- View 尺寸变化:例如,修改了
view.layoutParams.width或view.layoutParams.height。 - View 位置变化:调整了 View 的位置(如 setTranslationX/Y 可能需要配合重新布局)。
- 子 View 变化:添加、移除或修改了子 View,导致父容器需要重新布局。
- 内容变化影响布局:例如,TextView 文本变化导致尺寸变化。
注意事项
- 性能影响:频繁调用 requestLayout() 可能导致性能问题,因为它会触发整个 View 树的重新测量和布局(比如它有一个父布局,你修改了高度,而父布局的高度恰好是
wrapper_content),应尽量避免不必要的调用。 - 不一定立即生效:布局过程是异步的,实际的重绘可能在下一帧发生。
- 仅影响布局:requestLayout() 只处理 View 的测量和布局,不会直接触发重绘(invalidate() 负责重绘)。
示例
// 假设有一个 TextView
TextView textView = findViewById(R.id.text_view);
textView.setText("新内容"); // 改变文本内容
textView.requestLayout(); // 请求重新布局以适应新内容总结来说,view.requestLayout()是用来通知系统重新计算 View 布局的方法,适用于 View 尺寸或内容变化的场景,但需谨慎使用以避免性能问题。
实际案例
private fun animateHeight(view: View, from: Int, to: Int) {
val animator = ValueAnimator.ofInt(from, to)
animator.duration = 300
animator.interpolator = DecelerateInterpolator()
animator.addUpdateListener {
val value = animator.animatedValue as Int
view.layoutParams.height = value
view.requestLayout()
}
animator.start()
}在动画过程中,ValueAnimator会不断变化value,这个value被用来更新view.layoutParams.height,也就是动态改变view的高度。
但是,仅仅修改layoutParams.height是不够的,因为系统不会自动知道你修改了布局参数。为了让系统知道这个 View 的尺寸变了,就必须调用:
view.requestLayout()如果不调用requestLayout(),那么可能会发生以下情形:
- 系统不会重新测量这个 View。
- View 的显示区域可能不会马上更新。
- 你看到的界面不会立即反映高度变化。
- 有时甚至可能根本没有任何效果(视觉上)。
一些疑问
v.updateLayoutParams<ViewGroup.LayoutParams> {
height = newHeight.toInt()
}Q:为什么上述代码块这种可以马上得到布局的更新?
updateLayoutParams { ... }是 Kotlin Android KTX 提供的一个扩展函数,其定义大致如下:
inline fun <reified T : ViewGroup.LayoutParams> View.updateLayoutParams(block: T.() -> Unit) {
val params = layoutParams as? T
if (params != null) {
block(params)
layoutParams = params // ⚠️ 这一行是关键
}
}当你重新赋值给layoutParams时,系统会自动调用requestLayout()
public void setLayoutParams(ViewGroup.LayoutParams params) {
if (params == null) {
throw new NullPointerException("Layout parameters cannot be null");
}
mLayoutParams = params;
requestLayout(); // 系统在内部调用了requestLayout()
}也就是说,只要你:
v.layoutParams = newParams // 在 Kotlin 里相当于调用了 v.setLayoutParams
// 根据上方代码块可知或通过updateLayoutParams {}间接完成这个操作,系统会帮你调用requestLayout()
这里需要注意一点的是,下方代码片段 A 和代码片段 B 是完全不同的两码事。
val newParams = view.layoutParams
newParams.height = 500
// ⚠️ 这一步相当于“告诉系统参数有变化”,内部触发 requestLayout
view.layoutParams = newParamsview.layoutParams.height = value
view.requestLayout()代码 B 不会触发requestLayout()**。**因为你只是直接修改了 LayoutParams 的字段,系统没有任何通知机制来知道参数已经“变更”。所以视图不会立即重布局,除非你手动 requestLayout()。
Android 不会自动监听你对LayoutParams字段的修改,只有当你显示调用requestLayout()或者通过setLayoutParams()才会触发重布局流程。
invalidate()
view.invalidate()是 Android View 类的一个方法,用于请求 View 系统重新绘制(重绘)View 及其子 View。它通知系统,View 的视觉内容需要更新,通常在 View 的外观(如颜色、文本、背景等)发生变化时调用。
invalidate()是 Android View 系统中一个非常核心的方法,它和requestLayout()是一对经常容易混淆的“好兄弟”。
具体作用
- 触发重绘:调用 view.invalidate() 会使 View 的 onDraw() 方法被调用,重新绘制 View 的内容到屏幕上。
- 异步操作:类似于 requestLayout(),invalidate() 不会立即触发重绘,而是将请求加入绘制队列,等待下一次屏幕刷新(通常每 16 毫秒一帧)。
- 影响范围:默认情况下,invalidate() 只重绘调用它的 View 及其子 View,不会影响布局(位置或大小)。
它不会引起重新测量(measure)和布局(layout),只会触发重新绘制(draw)阶段。
使用场景
你需要在以下情况调用 view.invalidate():
- 外观变化:View 的绘制内容发生变化,例如:
- 更改了 View 的背景颜色、文字内容、文字颜色等。
- 自定义 View 中更新了绘制逻辑(如画布上的图形变化)。
- 动画效果:需要动态更新 View 的视觉效果,例如透明度、旋转、或自定义绘制的动画。
- 内容刷新:TextView 的文本内容变化但不影响尺寸时,可能仅需重绘而非重新布局。
示例代码
val view = findViewById<View>(R.id.my_view)
view.setBackgroundColor(Color.RED) // 更改背景颜色
view.invalidate() // 请求重绘以显示新颜色实际案例
/**
* 只改颜色调用
*
* @param newColor
*/
fun changeColorOnly(newColor: Int) {
color = newColor
// ❗ 这里只需要 invalidate,不能只 requestLayout
invalidate()
}一些疑问
Q:为什么有的时候用
requestLayout()也会引起重绘?好像不需要invalidate()?
比如下段代码:
class MyView(context: Context) : View(context) {
/** 颜色 */
private var color = Color.RED
/** 半径 */
private var circleRadius = 100f
/**
* 改变尺寸和颜色
*
* @param newRadius
* @param newColor
*/
fun changeSizeAndColor(newRadius: Float, newColor: Int) {
circleRadius = newRadius
color = newColor
layoutParams.width = (newRadius * 2).toInt()
layoutParams.height = (newRadius * 2).toInt()
// ❗只调用 requestLayout,不调用 invalidate
requestLayout()
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
Log.d(MyView::class.java.simpleName, "onDraw called") // ⬅️ 看日志判断是否执行了 draw
val paint = Paint().apply { color = this@MyView.color }
// 画一个圆
canvas.drawCircle(width / 2f, height / 2f, circleRadius, paint)
}
}requestLayout()并不会直接调用draw(),但在布局(layout)发生改变时,系统会在下一帧自动安排一次完整的measure → layout → draw流程。因此你看到它“重绘”了,但那不是requestLayout()自己干的。
流程:
- 虽然我们只调用了
requestLayout(),**并没有手动调用 **invalidate() - 但你会发现
onDraw()依然被调用了(从日志中能看到) - 原因是:View 的尺寸变了,系统下一个 VSync 帧发现有 layout 变化,于是调度了 measure → layout → draw 全流程
- 所以这次
draw()也被触发了
背后机制(重要):
当你调用requestLayout():
- 系统标记这个 View 为
layout requested - ViewRootImpl 会在下一个帧(VSync)执行
performTraversals():- 如果检测到 layout 变化,它就会走
measure → layout → draw - 如果 layout 区域发生变化,draw 就必然会执行(否则屏幕显示不对)
- 如果检测到 layout 变化,它就会走
/**
* 只改颜色调用 requestLayout
*
* 因为没有 layout 变化,所以不会调用 invalidate
* @param newColor
*/
fun changeColorOnly(newColor: Int) {
color = newColor
// ❗ 这里只需要 invalidate,不能只 requestLayout
requestLayout()
}比如上述这种没有尺寸变化的情况,调用requestLayout()就不能起作用了。
回顾 View 的渲染流程
理解invalidate()必须先理解 Android View 的三个核心阶段:
measure() --> layout() --> draw()
测量 布局 绘制| 特性 | view.requestLayout() | view.invalidate() |
|---|---|---|
| 作用 | 请求重新测量和布局,更新 View 的位置和大小 | 请求重新绘制,更新 View 的视觉内容 |
| 触发过程 | 触发onMeasure()和onLayout() | 触发onDraw() |
| 使用场景 | 修改尺寸、位置、或布局参数(如layoutParams) | 修改绘制内容(如颜色、文字、自定义图形) |
| 性能影响 | 可能影响整个 View 树,重新计算布局,耗时较多 | 仅重绘指定 View,性能开销通常较小 |
更新: 2025-08-18 21:08:57
原文: https://www.yuque.com/dongpozhouzi-mshe3/zhm85g/fmilexg8ld87rfe6