invalidate(),postInvalidate()和requestLayout()区别

这三个方法都是用于触发视图更新的,但它们的应用场景和触发的"更新级别"完全不同。

  • invalidate() : "重绘 "。意思是"我当前的内容变了(比如颜色、文字、位置等),需要重新画一遍"。它只触发 onDraw() 方法。
  • postInvalidate() : "在非UI线程中安全地重绘 "。功能和 invalidate() 完全一样,但它可以在非UI线程(子线程)中调用。
  • requestLayout() : "重新测量和布局 "。意思是"我的尺寸可能变了,或者子视图的结构变了,整个布局需要重新计算"。它会触发完整的 measure() -> layout() 流程,可能也会触发 onDraw()

详细对比

特性 invalidate() postInvalidate() requestLayout()
核心作用 标记视图的局部区域为脏区 ,请求重绘 非UI线程 中安全地请求重绘 请求重新布局整个视图树。
触发方法 onDraw(Canvas) onDraw(Canvas) onMeasure(), onLayout() (以及可能的 onDraw())
调用线程 必须UI主线程中调用。 可以在任何线程(主线程或子线程)中调用。 必须UI主线程中调用。
性能开销 较小。只影响自身视图的绘制。 较小 。同 invalidate() 较大。会从根视图开始,可能遍历整个视图树,重新测量和布局所有相关视图。
使用场景 内容改变但视图的大小和位置不变 时。 例如: - 改变背景色、文字颜色 - 在 onTouchEvent 中移动一个子视图 - 自定义View时动态改变绘制内容 在子线程中更新UI,例如: - 在 AsyncTaskdoInBackground 中更新进度 - 在子线程中进行计算,并实时反馈到UI上 视图的边界(尺寸)可能发生变化 时。 例如: - 给 TextView 设置新的文字,导致其宽高改变 - 动态添加或移除子View - 在自定义View中改变了 LayoutParams

深入解析与示例

1. invalidate()

当你只改变了视图的内容 ,而它的尺寸和位置 没有变化时,使用 invalidate()

工作流程
invalidate() -> dispatchDraw() -> onDraw()

示例

java 复制代码
public class CustomView extends View {
    private int mCircleColor = Color.RED;

    // 在UI线程中改变颜色并重绘
    public void changeColor() {
        mCircleColor = Color.BLUE;
        invalidate(); // 触发onDraw,视图会变成蓝色
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        Paint paint = new Paint();
        paint.setColor(mCircleColor);
        canvas.drawCircle(100, 100, 50, paint);
    }
}
2. postInvalidate()

invalidate() 的线程安全版本。其内部实现是向主线程的Handler发送了一个消息,最终在主线程中调用了 invalidate()

示例

java 复制代码
public class CustomView extends View {
    private int mProgress = 0;

    // 在子线程中更新进度
    public void startUpdateInBackground() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (mProgress < 100) {
                    mProgress++;
                    // 不能在子线程直接调用invalidate(),否则会崩溃
                    // invalidate(); // 错误!
                    postInvalidate(); // 正确!安全地在主线程触发重绘
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        // 绘制一个根据mProgress变化的进度条...
    }
}
3. requestLayout()

当你认为当前视图的尺寸或布局已经不再满足要求,需要重新计算时,调用此方法。它会从ViewRootImpl开始,执行一个完整的遍历(Traversal)。
工作流程
requestLayout() -> onMeasure() -> onLayout() -> (可能) onDraw()

为什么可能触发 onDraw()

因为重新布局后,视图的位置和大小可能发生了变化,系统认为你需要重新绘制以适应新的布局。

示例

java 复制代码
public class MyTextView extends TextView {

    public void setTextAndResize(String text) {
        setText(text);
        // 设置新文字后,这个TextView所需的宽度和高度可能变了。
        // 我们需要告诉父布局:"我的尺寸可能变了,请重新测量和摆放我"。
        requestLayout(); 
    }

    // 或者在自定义View中,你重写了onMeasure,并且条件发生了变化
    private boolean mIsWideMode = false;

    public void setWideMode(boolean isWide) {
        if (mIsWideMode != isWide) {
            mIsWideMode = isWide;
            // 测量逻辑改变了,必须请求重新布局
            requestLayout();
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (mIsWideMode) {
            // 宽模式的测量逻辑
            setMeasuredDimension(500, 100);
        } else {
            // 窄模式的测量逻辑
            setMeasuredDimension(200, 100);
        }
    }
}

如何选择?

  1. 只涉及视觉表现变化(颜色、位置、透明度等)?

    • -> 使用 invalidate()
    • 如果在子线程 中 -> 使用 postInvalidate()
  2. 视图的尺寸或布局结构发生了变化(宽高、边距、子视图数量等)?

    • -> 使用 requestLayout()
  3. 不确定该用哪个?

    • 先想想你的改变是否影响了视图的尺寸 。如果影响了,用 requestLayout();如果没影响,只用 invalidate()。滥用 requestLayout() 会导致不必要的性能损耗。

组合使用

在某些复杂情况下,你甚至可能需要同时调用两者。

java 复制代码
// 例如,一个自定义View,它既改变了内部状态(需要重绘),又改变了自己的尺寸(需要重新布局)
public void complexChange() {
    changeInternalState(); // 改变状态
    requestLayout(); // 请求重新布局(这会隐式包含重绘)
    // 或者,如果你确定invalidate()是必要的,也可以显式调用,但通常requestLayout()就够了。
    // invalidate(); 
}

总结一下,理解这三个方法的区别,关键在于理解Android视图系统的工作流程:测量(Measure) -> 布局(Layout) -> 绘制(Draw)requestLayout() 触发了前两步(可能包括第三步),而 invalidate()/postInvalidate() 只触发了第三步。

相关推荐
c***21291 小时前
Springboot3学习(5、Druid使用及配置)
android·学习
修炼者2 小时前
【Android 进阶】别再强转 Context 了!手把手教你优雅解耦 View 与 Activity
android·android studio
x***01062 小时前
SpringSecurity+jwt实现权限认证功能
android·前端·后端
程序员江同学2 小时前
线下活动|2025 Kotlin 中文开发者大会北京分会场
android·kotlin
李坤林3 小时前
Android Vulkan 开启VK_GOOGLE_DISPLAY_TIMING 后,一个vsync 会释放两个imageBuffer现象分析
android·vulkan
Jerry3 小时前
Compose 状态思维
android
k***45994 小时前
MySQL----case的用法
android·数据库·mysql
r***86985 小时前
Plugin ‘mysql_native_password‘ is not loaded`
android·数据库·mysql
v***59835 小时前
MySQL-mysql zip安装包配置教程
android·mysql·adb
不用89k6 小时前
Android无法区分USB摄像头是哪一个
android