嵌套RecyclerView首次滚动卡顿问题排查求助
解决嵌套RecyclerView首次滚动卡顿问题
看起来你遇到的是嵌套RecyclerView首次加载时的典型性能问题——首次滚动卡顿,后续流畅,重启后又重复出现。这主要是因为首次滚动时大量的对象重复创建、主线程耗时操作导致的,咱们一步步来优化:
核心问题分析
你的代码里有几个关键的性能瓶颈:
- 父Adapter的
onBindViewHolder中每次都新建子Adapter,每次滚动到新的父项都会创建新的TemplateChooserAdapter实例,产生不必要的对象开销 - 子RecyclerView的
GridLayoutManager和SpacesItemDecoration在ItemRowHolder构造中重复创建,每个父项ViewHolder都会生成新的布局管理器和装饰器 - 图片直接用
setImageResource加载,这会在主线程同步解码图片,大尺寸图片会严重阻塞主线程 - 布局层级冗余,增加了首次绘制的耗时
具体优化方案
1. 复用子Adapter,避免重复创建
不要在onBindViewHolder里每次都new子Adapter,而是提前初始化或者缓存起来。比如在父Adapter中维护一个缓存Map,根据sectionName来复用Adapter:
public class RecyclerViewDataAdapter extends RecyclerView.Adapter<RecyclerViewDataAdapter.ItemRowHolder> { private ArrayList<SectionDataModel> dataList; private Context mContext; private RecyclerListItemClick onListClick; // 新增:缓存子Adapter private HashMap<String, TemplateChooserAdapter> adapterCache = new HashMap<>(); public RecyclerViewDataAdapter(Context context, ArrayList<SectionDataModel> dataList) { this.dataList = dataList; this.mContext = context; onListClick = (RecyclerListItemClick) context; // 提前初始化所有子Adapter for (SectionDataModel model : dataList) { String sectionName = model.getHeaderTitle(); adapterCache.put(sectionName, new TemplateChooserAdapter(mContext, model.getTemplateModelArrayList(), sectionName)); } } @Override public void onBindViewHolder(ItemRowHolder itemRowHolder, int i) { ArrayList<SingleTemplateModel> templateModelArrayList = dataList.get(i).getTemplateModelArrayList(); String sectionName = dataList.get(i).getHeaderTitle(); itemRowHolder.itemTitle.setText(sectionName); // 直接从缓存取Adapter,不用每次新建 TemplateChooserAdapter itemListDataAdapter = adapterCache.get(sectionName); itemRowHolder.recycler_view_list.setAdapter(itemListDataAdapter); } // ... 其他代码不变 }
2. 子RecyclerView布局配置只初始化一次
现在每个ItemRowHolder构造时都会新建GridLayoutManager和SpacesItemDecoration,这些对象是可以复用的,把它们提到父Adapter的成员变量中:
public class RecyclerViewDataAdapter extends RecyclerView.Adapter<RecyclerViewDataAdapter.ItemRowHolder> { // 新增:全局复用的布局管理器和装饰器 private GridLayoutManager childGridLayoutManager; private SpacesItemDecoration childItemDecoration; public RecyclerViewDataAdapter(Context context, ArrayList<SectionDataModel> dataList) { this.dataList = dataList; this.mContext = context; onListClick = (RecyclerListItemClick) context; // 初始化一次即可 childGridLayoutManager = new GridLayoutManager(mContext, 2); childItemDecoration = new SpacesItemDecoration(2, 25, false); // ... 初始化Adapter缓存 } public class ItemRowHolder extends RecyclerView.ViewHolder implements View.OnClickListener { private TextView itemTitle; private RecyclerView recycler_view_list; private ItemRowHolder(View view) { super(view); this.itemTitle = view.findViewById(R.id.itemTitle); this.recycler_view_list = view.findViewById(R.id.recycler_view_list); this.recycler_view_list.setOnClickListener(this); this.recycler_view_list.setHasFixedSize(true); // 复用全局的布局管理器和装饰器,避免重复创建 this.recycler_view_list.setLayoutManager(childGridLayoutManager); // 注意:不要重复添加ItemDecoration,否则会叠加 if (recycler_view_list.getItemDecorationCount() == 0) { this.recycler_view_list.addItemDecoration(childItemDecoration); } // 新增:关闭子RecyclerView的嵌套滚动,减少滚动计算 recycler_view_list.setNestedScrollingEnabled(false); } // ... 点击事件代码不变 } }
3. 优化图片加载,避免主线程解码
setImageResource会在主线程同步解码Drawable,大图片会直接导致卡顿。建议使用Glide(或Picasso)来异步加载图片,示例如下:
修改子Adapter的onBindViewHolder:
@Override public void onBindViewHolder(final ViewHolder holder, int position) { int imageRes = templateModelArrayList.get(position).getImage(); // 用Glide异步加载图片,自动处理缓存和线程 Glide.with(context) .load(imageRes) .fitCenter() // 适配你的scaleType .into(holder.templateView); }
如果不想用第三方库,也可以手动在子线程解码图片,然后用Handler在主线程设置,但Glide已经帮你处理了缓存、内存优化等问题,更省心。
4. 布局层级优化
检查子项布局single_row_template_chooser.xml,里面的RelativeLayout是多余的,可以直接去掉,让ImageView作为CardView的直接子View:
<?xml version="1.0" encoding="utf-8"?> <android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:card_view="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="280dp" card_view:cardElevation="6dp"> <ImageView android:id="@+id/template_view" android:layout_width="match_parent" android:layout_height="match_parent" android:scaleType="fitXY" android:src="@mipmap/model_9" /> </android.support.v7.widget.CardView>
5. 额外的RecyclerView优化配置
给父RecyclerView添加以下配置,提升滚动流畅度:
// 在Activity的recyclerViewJob方法中 private void recyclerViewJob() { recyclerView.setHasFixedSize(true); recyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)); // 增加ViewHolder缓存数量 recyclerView.setItemViewCacheSize(10); // 开启预加载 recyclerView.setDrawingCacheEnabled(true); recyclerView.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_HIGH); RecyclerViewDataAdapter adapter = new RecyclerViewDataAdapter(this, allSampleData); recyclerView.setAdapter(adapter); }
为什么这些优化能解决问题?
- 复用Adapter和布局管理器,减少了对象创建的开销,避免了GC频繁触发
- 异步图片加载把耗时的解码操作移到子线程,不会阻塞主线程的滚动和绘制
- 布局层级减少,降低了首次绘制的时间
- 关闭子RecyclerView的嵌套滚动,避免了嵌套滚动时的冗余计算
这些优化做完后,首次滚动的卡顿应该会明显改善,重启应用后的首次滚动也会流畅很多。
内容的提问来源于stack exchange,提问作者jazzbpn
相关产品推荐
相关产品推荐

