You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android RecyclerView Adapter反复调用getItemViewType()问题解决

RecyclerView Adapter无限调用getItemViewType/getItemCount的原因分析

问题场景

我的RecyclerView Adapter出现了**无限调用getItemViewType()和getItemCount()**的情况,排查后发现只有这两个方法被反复触发,onBindViewHolder()、onCreateViewHolder()以及ViewHolder的bind方法都没有执行。进一步定位到自定义View的onMeasure方法是诱因,通过添加标志位限制重复测量解决了问题,但不清楚背后的逻辑。

相关代码

Fragment 代码

public class Fragment{
    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (presenter == null) {
            presenter = new Presenter(this);
        }
        initViewModel();
    }
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        super.onCreateView(inflater, container, savedInstanceState);
        setRecyclerView();
        //this will modify adapter
        loadData();
        return rootView;
    }
    void initViewModel(){
        messageViewModel = new ViewModelProvider(this).get(messageViewModel.class);
        final Observer<ArrayList<WallPost>> wallPostObserver = wallPostList -> {
            //this will modify adapter
            updateRecyclerView(wallPostList);
        };
        messageViewModel.getWallPostsLiveData().observe(this, wallPostObserver);
    }
    public void setRecyclerView() {
        layoutManager = new LinearLayoutManager(getContext());
        recyclerView.setLayoutManager(layoutManager);
        adapter = new Adapter(getContext(), new ArrayList<>(), Repository.getInstance().getMemberData().getValue(), getType());
        recyclerView.setAdapter(adapter);
        recyclerView.addOnScrollListener(scrollListener);
    }
}

Adapter 的 getItemViewType() 方法

@Override
    public int getItemViewType(int position) {
        if (wallPostList.size() == 0) {
            return VIEW1;
        }else if(position >= wallPostList.size()){
            return VIEW7;
        }

        WallPost wallPost = wallPostList.get(position);
        Log.d("Adapter","position:"+position);
        if(wallPost.getStatus().equals(POST_STATUS_REVOKE)){
            return VIEW8;
        }
        switch (wallPost.getType()) {
            case azz:
                return VIEW5;
            case axx:
                return VIEW6;
            case ayy:
            default:
                if(wallPost.getMsgItemList().size() > 0){
                    return VIEW4;
                }else if(StringUtils.checkHasLink(wallPost.getContent())){
                    return VIEW3;
                }else {
                    return VIEW2;
                }
        }
    }

引发问题的自定义View onMeasure 代码

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        if(type.equals(SINGLE)){
            info.post(()->{
                halfWidth = (info.getMeasuredWidth()- arrowImageView.getMeasuredWidth())/2;
                name.setMaxWidth(halfWidth-image.getMeasuredWidth());
                tvNoticeTime.setMaxWidth(halfWidth-image.getMeasuredWidth());
            });
        }
        else if(type.equals(MULTI)){
            info.post(()->{
                name.setMaxWidth(info.getMeasuredWidth()-image.getMeasuredWidth() - (int)PixelUtil.convertDpToPixel(8,context));
            });
        }
        else{
            int dis = (int)PixelUtil.convertDpToPixel(8,context);
                info.post(()->{
                name.setMaxWidth(info.getMeasuredWidth()-image.getMeasuredWidth()-dis-
                        infoImageView.getMeasuredWidth()-dis);
            });
        }
    }

原因分析

问题的核心是自定义View的onMeasure方法里的post()调用触发了无限重测循环:

  1. post()会把Runnable放到UI线程的消息队列,在当前测量流程结束后执行
  2. Runnable里修改了name、tvNoticeTime等View的setMaxWidth(),这个操作会触发requestLayout()
  3. requestLayout()会触发整个View树重新执行测量、布局流程,也就是再次调用自定义View的onMeasure方法
  4. 如此反复,形成「测量→修改属性→触发重测→再测量」的无限循环

而RecyclerView在布局过程中,需要不断确认每个item的类型(getItemViewType)和总数(getItemCount)来完成布局计算。当自定义View所在的item持续触发重测时,RecyclerView会不断向Adapter请求这些信息,导致你看到的getItemViewType和getItemCount被无限调用。

修复思路

方案1:移除post,直接在onMeasure中同步处理

onMeasure本身就在UI线程执行,此时已经完成了当前View的测量,能直接获取到正确的measuredWidth值,不需要通过post()延迟执行:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    if(type.equals(SINGLE)){
        halfWidth = (info.getMeasuredWidth()- arrowImageView.getMeasuredWidth())/2;
        name.setMaxWidth(halfWidth-image.getMeasuredWidth());
        tvNoticeTime.setMaxWidth(halfWidth-image.getMeasuredWidth());
    }
    else if(type.equals(MULTI)){
        name.setMaxWidth(info.getMeasuredWidth()-image.getMeasuredWidth() - (int)PixelUtil.convertDpToPixel(8,context));
    }
    else{
        int dis = (int)PixelUtil.convertDpToPixel(8,context);
        name.setMaxWidth(info.getMeasuredWidth()-image.getMeasuredWidth()-dis-
                infoImageView.getMeasuredWidth()-dis);
    }
}

方案2:添加标志位避免重复触发

如果确实需要使用post(),可以添加一个boolean变量标记是否已经执行过修改操作,防止循环:

private boolean hasSetMaxWidth = false;

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    if(hasSetMaxWidth) return;
    
    if(type.equals(SINGLE)){
        info.post(()->{
            halfWidth = (info.getMeasuredWidth()- arrowImageView.getMeasuredWidth())/2;
            name.setMaxWidth(halfWidth-image.getMeasuredWidth());
            tvNoticeTime.setMaxWidth(halfWidth-image.getMeasuredWidth());
            hasSetMaxWidth = true;
        });
    }
    // 其他分支同理添加标志位设置
}

内容的提问来源于stack exchange,提问作者Jeff Lin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 09:31:03