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()

示例

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()

示例

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()
因为重新布局后,视图的位置和大小可能发生了变化,系统认为你需要重新绘制以适应新的布局。

示例

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() 会导致不必要的性能损耗。

组合使用

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

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

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

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值