Nested recylerview lag while first few scrolls and then scrolls smoothly?

Viewed 5669

I am using nested RecyclerView. Means inside a vertical RecyclerView I have multiple horizontal recycler view

I am attaching adapter to horizontal recylerviews inside onBindViewHolder method of parent RecyclerView as follows.

@Override
public void onBindViewHolder(final MainViewHolder holder, final int position) {
    switch (holder.getItemViewType()) {
        case TYPE_PRODUCT:
            ((ListHolderProduct) holder).itemTitle.setText(browseCategoryHomePageItems.get(position).displayName.toUpperCase());
            ((ListHolderProduct) holder).recyclerView.setAdapter(new CarouselProductsRecyclerAdapter(context
                    , browseCategoryHomePageItems.get(position).products
                    , R.layout.activity_categoryhome_products_grid_item
                    , nestedRecyclerItemClickedListener
                    , position));
            break;
        case TYPE_DEAL:
            ((ListHolderDeal) holder).itemTitle.setText(browseCategoryHomePageItems.get(position).displayName.toUpperCase());
            ((ListHolderDeal) holder).recyclerView.setAdapter(new CarouselDealsRecyclerAdapter(context
                    , browseCategoryHomePageItems.get(position).dealItems
                    , R.layout.activity_categoryhome_deals_grid_item
                    , nestedRecyclerItemClickedListener
                    , position));
            break;
            //few more types like this
    }
}

Now whenever I scroll page it is lagging a bit since I am attaching adapter to horizontal RecyclerView on OnBindViewHolder

And there can be N Number of TYPE_PRODUCT or any type of horizontal lists. Means there can be more that one horizontal lists of same type.

Any idea how can I optimize this thing and improve the scroll speed.

It is lagging since setAdapter is called every time for list previously.

Update on this I am extending LinearLayoutManager and in that I am setting extraLayout space which has fixed my issue but I don't know this is right way or not I am setting extra space as below.

 layoutManager.setExtraLayoutSpace(2 * this.getResources().getDisplayMetrics().heightPixels);

and follwoing is custom layout manager class

public class PreCachingLayoutManager extends LinearLayoutManager {
private static final int DEFAULT_EXTRA_LAYOUT_SPACE = 600;
private int extraLayoutSpace = -1;
private Context context;

public PreCachingLayoutManager(Context context) {
    super(context);
    this.context = context;
}

public PreCachingLayoutManager(Context context, int extraLayoutSpace) {
    super(context);
    this.context = context;
    this.extraLayoutSpace = extraLayoutSpace;
}

public PreCachingLayoutManager(Context context, int orientation, boolean reverseLayout) {
    super(context, orientation, reverseLayout);
    this.context = context;
}

public void setExtraLayoutSpace(int extraLayoutSpace) {
    this.extraLayoutSpace = extraLayoutSpace;
}

@Override
protected int getExtraLayoutSpace(RecyclerView.State state) {
    if (extraLayoutSpace > 0) {
        return extraLayoutSpace;
    }
    return DEFAULT_EXTRA_LAYOUT_SPACE;
}

}

6 Answers

Create the Adapter at your ViewHolder creation, not binding. Also, be aware that sometimes, initial scroll lag happens due to the build variant you are testing on. Release APKs have better performance since the entire debugging tools and profilers activated on a Debug Build are not activated anymore. Most of my performance issues are solved on the release build.

The other answers are focusing on the recycling part of the RecyclerView. If you look carefully, the problem is not about the recycling process at all. In order to have smooth behavior on the first scroll, you should preload the items when the outer adapter created. This can be done by extending LinearLayoutManager and overriding calculateExtraLayoutSpace() method.

// Utility to convert DP to PX
val Int.dp: Float get() = (this * Resources.getSystem().displayMetrics.density).toInt()

// Assign LayoutManager to RecyclerView
recyclerView.layoutManager = object : LinearLayoutManager(this) {
   override fun calculateExtraLayoutSpace(
      state: RecyclerView.State,
      extraLayoutSpace: IntArray
   ) {
      extraLayoutSpace[0] = 500.dp // Extra space for the top/left items
      extraLayoutSpace[1] = 500.dp // Extra space for the bottom/right items
   }
}

The extra space needed is depend on the size of your items. Choose wisely as this will cause RecyclerView to add extra items in the ViewPool. If the LayoutManager space is greater then your overall layout size, the items will not get recycled at all (because all items are visible by LayoutManager).

Related