View测量宽高的时机

View的绘制过程

View绘制过程为measure(测量),layout(决定位置),draw(绘制)
由于measure方法为final类型,所以我们无法去重写该方法,但是在测量结束后会回调onMeasure方法,在该方法中可以获取到测量宽/高,之所以说是测量,是因为可能由于某种原因导致最后显示出的宽高并不和测量的一致,但是大部分情况下实际高度等于测量宽/高.
不推荐在onMeasure中去获取测量宽高,某些情况下,view可能会多次测量才会获取到测量宽/高,在这种情况下获取到的很大概率是不准确的.
推荐在onLayout中获取测量宽/高或者最终宽/高


测量宽高的时机

前言
例如在Activity中去获取view的宽高,很多人会选择在onCreateonPause中去获取,但是这样很有可能测量获得的宽高为0
TIPS:View的绘制过程并不是和Activity的生命周期同步的,在Activity回调 onCreateonPause 方法时并不能保证View已经绘制完毕

Activity/View#onWindowFocusChanged

此方法的含义为:View已经绘制完毕,已经获取到焦点.当然,此时的宽高也已经确定了,但是需要注意,在视图获取焦点或者失去焦点都会被调用一次,很容易造成频繁的调用

@Override
public void onWindowFocusChanged(boolean hasFocus) {
    super.onWindowFocusChanged(hasFocus);
    if (hasFocus){
        int width = view.getMeasuredWidth();
        int width = view.getMeasuredHeight();
    }
}

view.post(runnable)

通过post可以将一个runnable投递到消息队列的尾部,然后等待Looper调用此runnable的时候,View也已经初始化好了.

view.post(new Runnable() {
   @Override
    public void run() {
        int width = view.getMeasuredWidth();
        int width = view.getMeasuredHeight();     
    }
});

ViewTreeObserver

使用ViewTreeObserver的回调方法,比如OnGlobalLayoutListener接口,当View树的状态发生改变,或者可见性发生改变时,都会回调onGlobalLayout方法,但是需要注意,随着状态发生改变,onGlobalLayout会被调用多次

ViewTreeObserver observer = view.getViewTreeObserver();
observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
    @Override
    public void onGlobalLayout() {
        view.getViewTreeObserver().removeGlobalOnLayoutListener(this);
        int width = view.getMeasuredWidth();
        int width = view.getMeasuredHeight();
    }
});

view.measure

view的测量需要根据当前view的LayoutParams和父容器的MeasureSpec手动进行测量
MeasureSpec代表一个32位的int值,高2位代表SpecMode,低30位代表SpecSize
SpecMode:EXACTLY, AT_MOST,UNSPECIFIED
SpecSize:dp/px,match_parent,wrap_content

parentSpecMode
EXACTLY
parentSpecMode
AT_MOST
parentSpecMode
UNSPECIFIED
childLayoutParams
dp/px
childSize
EXACTLY
childSize
EXACTLY
childSize
EXACTLY
childLayoutParams
match_parent
parentSize
EXACTLY
parentSize
AT_MOST
0
UNSPECIFIED
childLayoutParams
wrap_content
parentSize
AT_MOST
parentSize
AT_MOST
0
UNSPECIFIED

根据view的LayoutParams分为如下几种情况

  • match_parent
    此时的SpecSizeparentSize,然而此时无法知道parentSize的大小,无法构造出MeasureSpec

  • 具体的数值dp/px
    假设宽高都是100px,可以构造以下MeasureSpec

int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY);
int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec(100, View.MeasureSpec.EXACTLY);
view.measure(widthMeasureSpec,heightMeasureSpec);
  • wrap_content
    表格中的wrap_content行中的parentSize表示view的最大宽/高为parentSize,我们可以使用View理论上支持的最大值(1<<30)-1 (30位二进制)去构造是合理的
int widthMeasureSpec = View.MeasureSpec.makeMeasureSpec((1<<30)-1, View.MeasureSpec.AT_MOST);
int heightMeasureSpec = View.MeasureSpec.makeMeasureSpec((1<<30)-1, View.MeasureSpec.AT_MOST);
view.measure(widthMeasureSpec,heightMeasureSpec);
  • 错误的用法
    违背了系统的内部实现规范(无法通过错误的MeasureSpec去得出合法的SpecMode,从而导致measure过程出错),并且不能保证一定能measure出正确的结果

    int widthMeasureSpec = MeasureSpec.makeMeasureSpec(-1, MeasureSpec.UNSPECIFIED);
    int heightMeasureSpec = MeasureSpec.makeMeasureSpec(-1, MeasureSpec.UNSPECIFIED);
    view.measure(widthMeasureSpec,heightMeasureSpec);

    PS:此处实际实验得知….设置为-1根本不能编译通过…..明确给出了错误提示Value must be >= 0,不知道书中为什么还要列举这个…..

    view.measure(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT);

本文参考Android开发艺术探索进行整理

<think>我们正在处理一个Android开发中的问题:在ViewHolder中使用View.post()获取视图时结果不正确。根据提供的引用和常见问题,我们可以分析原因并给出解决方案。 原因分析: 1. 在ViewHolder中,视图可能尚未完成布局(测量和布局流程未完成),此时通过View.post()获取可能为0或错误值。 2. View.post()虽然会将任务加入消息队列,但若视图尚未被正确添加到窗口或未完成布局,则无法获取有效。 解决方案: 1. 使用ViewTreeObserver监听布局完成事件(推荐): ```java view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { // 确保移除监听,避免多次调用 view.getViewTreeObserver().removeOnGlobalLayoutListener(this); int width = view.getWidth(); int height = view.getHeight(); // 在这里使用获取到 } }); ``` 2. 在RecyclerView完成布局后获取(例如在onBindViewHolder中延迟执行): 由于RecyclerView的复用机制,直接使用View.post()可能无法保证时机正确。可以尝试在RecyclerView的全局布局监听中处理,但这可能较为复杂。 3. 使用自定义View的onSizeChanged方法(如果自定义View): 在自定义View中重写onSizeChanged,当尺寸变化时回调。 4. 使用View.post()的时机调整:确保在视图被正确添加到窗口后。但ViewHolder中视图可能尚未添加到窗口(如RecyclerView的预加载),因此这种方法并不总是可靠。 根据引用[1]:View需要被添加到Activity的视图层次结构中才会触发完整的生命周期流程。在ViewHolder中,视图可能已被添加(因为已经在列表中显示),但可能尚未完成布局。 引用[3]和[4]提供了获取View度的几种方法,其中使用ViewTreeObserver是可靠的方式。 因此,推荐使用ViewTreeObserver的OnGlobalLayoutListener。 示例代码(在ViewHolder的onBindViewHolder中使用): ```java @Override public void onBindViewHolder(ViewHolder holder, int position) { // ... 其他绑定逻辑 // 获取视图 holder.itemView.getViewTreeObserver().addOnGlobalLayoutListener( new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { // 移除监听,避免重复调用 holder.itemView.getViewTreeObserver().removeOnGlobalLayoutListener(this); int width = holder.itemView.getWidth(); int height = holder.itemView.getHeight(); // 使用进行后续操作 } }); } ``` 注意:由于RecyclerView的视图复用,当视图被复用时,可能会再次触发布局监听,因此务必在获取到尺寸后移除监听器。 另一种方法:在视图已经完成布局的情况下,我们可以直接获取。但若未完成,则使用监听器。我们可以先检查View度和度是否大于0,如果大于0则直接使用,否则添加监听: ```java if (view.getWidth() > 0 && view.getHeight() > 0) { // 直接使用 } else { // 添加布局监听 } ``` 但这种方式在视图未布局时仍然需要监听,所以综合来看,使用监听器是更通用的方法。 相关问题: 1. 在RecyclerView中如何正确获取ItemView? 2. View.post()和ViewTreeObserver有什么区别? 3. 除了OnGlobalLayoutListener,还有哪些方法可以监听View的尺寸变化?</think>### ViewHolder中使用View.post获取视图不准确的原因及解决方案 #### 问题原因分析 1. **视图未完成布局** 在RecyclerViewViewHolder中调用`View.post()`时,视图可能尚未完成测量和布局流程。此时获取的为0或错误值,因为: - ViewHolder创建时视图仅初始化,未完成布局流程[^1] - RecyclerView的预加载机制可能导致视图处于中间状态 2. **时序问题** `View.post()`将任务加入消息队列尾部,但布局流程可能尚未开始: ```java // 错误示例: view.post(() -> { int width = view.getWidth(); // 可能返回0 }); ``` 3. **视图复用干扰** RecyclerView复用ViewHolder时,旧视图的尺寸数据可能残留 #### 解决方案 ##### ✅ 推荐方案:ViewTreeObserver ```java view.getViewTreeObserver().addOnGlobalLayoutListener( new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { // 确保移除监听避免重复调用 view.getViewTreeObserver().removeOnGlobalLayoutListener(this); int actualWidth = view.getWidth(); int actualHeight = view.getHeight(); // 在此使用正确尺寸 } } ); ``` **优点**:精确监听布局完成事件,100%确保获取有效尺寸 ##### ✅ 延迟验证方案 ```java view.post(() -> { if (view.getWidth() == 0 || view.getHeight() == 0) { view.post(this::verifyDimensions); // 递归验证 } else { // 尺寸可用 } }); ``` **适用场景**:简单布局且性能要求不时 ##### ✅ 尺寸缓存方案 ```java // 在onBindViewHolder中 if (view.getTag(R.id.view_dimensions) == null) { // 首次加载监听尺寸 view.getViewTreeObserver().addOnGlobalLayoutListener(...); } else { // 直接使用缓存尺寸 int[] dimensions = (int[]) view.getTag(R.id.view_dimensions); } ``` #### 关键注意事项 1. **生命周期绑定** 在`onViewRecycled()`中移除未完成的监听,避免内存泄漏 2. **异步处理** 尺寸获取后需通过接口回调更新UI: ```java interface OnDimensionReady { void onSizeReady(int width, int height); } ``` 3. **性能优化** - 避免在滚动过程中频繁触发监听 - 使用`View.postDelayed()`增加延迟阈值 #### 其他备选方案 1. **自定义View重写`onSizeChanged`**: ```java @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); // 尺寸变化时触发 } ``` 2. **使用View的`addOnLayoutChangeListener`**(API 16+): ```java view.addOnLayoutChangeListener((v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { int width = right - left; // 处理尺寸 }); ``` > **最佳实践**:在RecyclerView场景中,优先采用`ViewTreeObserver`方案并配合尺寸缓存,可兼顾准确性和性能[^3][^4]。 --- ### 相关问题 1. RecyclerView中如何正确获取ItemView测量度? 2. View.post() 和 View.postDelayed() 在获取尺寸时有何区别? 3. 如何在自定义View中实时监听尺寸变化? 4. ViewTreeObserver 的 OnPreDrawListener 和 OnGlobalLayoutListener 有何异同? 5. 如何避免RecyclerView滚动过程中的布局监听性能损耗?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值