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

嵌套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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 08:45:06