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()调用触发了无限重测循环:
post()会把Runnable放到UI线程的消息队列,在当前测量流程结束后执行- Runnable里修改了
name、tvNoticeTime等View的setMaxWidth(),这个操作会触发requestLayout() requestLayout()会触发整个View树重新执行测量、布局流程,也就是再次调用自定义View的onMeasure方法- 如此反复,形成「测量→修改属性→触发重测→再测量」的无限循环
而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
相关产品推荐
相关产品推荐

