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() 只触发了第三步。

相关推荐
用户41659673693552 小时前
兼容 Android Q+ 实现 WebView 图片长按保存与复制
android
2501_915918412 小时前
HTTP和HTTPS工作原理、安全漏洞及防护措施全面解析
android·http·ios·小程序·https·uni-app·iphone
Little丶Seven3 小时前
使用adb获取安卓模拟器日志
android·unity·adb·个人开发
凉栀お_3 小时前
MySQL第五次作业(触发器,存储过程)
android·mysql·adb
limingade3 小时前
ADB点击实战-做一个自动点广告播放领金币的脚本app(中)
android·adb·智能手机·ocr识别手机广告·ocr识别手机屏幕·adb自动关闭广告
珹洺3 小时前
Java-Spring入门指南(二十九)Android交互核心:按钮点击事件与Activity跳转实战
android·java·spring
2501_916007474 小时前
如何在 Windows 电脑上调试 iOS 设备上的 Safari?完整方案与实战经验分享
android·windows·ios·小程序·uni-app·iphone·safari
2501_915918414 小时前
uni-app iOS日志管理实战,从调试控制台到系统日志的全链路采集与分析指南
android·ios·小程序·https·uni-app·iphone·webview