Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

OnCreateViewHolder performance issue

I ran into a problem with a RecyclerView and I thought you guys can help me. So the problem is: Inflating the item view xml takes too much time. The layout is grid layout with 3 items in a row and each item has a complex UI look.

So first I've used android studio's profiler to analyse it and I saw that the CPU has a significant bump at the point of creation. Even if I remove the entire bind code, It still takes some time when I move into this screen/ scrolling inside the recycler view.

Going into separating the items to smaller items will be crazy because it's already a matrix (3 items in a row as I said).

So I thought about 2 things:

  1. maybe I can create more view holders in advance, any idea how to do that? I saw methods like RecyclerView.setItemViewCacheSize and LayoutManager.setInitialPrefetchItemCount but no luck making it happens (it is still calling on create a lot of times)

  2. What about AsyncLayoutInflater? does someone uses it in a RecyclerView?

Any new idea will be great!

This is how the view xml looks like:

<FrameLayout 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="wrap_content">

    <android.support.v7.widget.CardView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        card_view:cardCornerRadius="3dp"
        card_view:cardElevation="2.5dp"
        card_view:cardUseCompatPadding="true">

        <CustomView1>

        </CustomView1>

        <CustomView2>

        </CustomView2>

        <CustomView3>

        </CustomView3>

        <ImageView
            android:id="@+id/imageView"
            android:layout_width="wrap_content"
            android:layout_height="30dp"
            android:layout_gravity="end"
            android:layout_marginEnd="2dp"
            android:layout_marginStart="2dp"
            android:layout_marginTop="2dp"
            android:adjustViewBounds="true" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />

        <include layout="@layout/someLayoutToInclude" />

    </android.support.v7.widget.CardView>
</FrameLayout>

OnCreateViewHolder looks like:

LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
View view = layoutInflater.inflate(viewType, parent, false);
return new ItemViewHolder(view);

Grid Layout initialization:

final GridLayoutManager gridLayoutManager = new GridLayoutManager(getContext(), 3);
        gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
            @Override
            public int getSpanSize(int position) {
                return 1;
            }
        });

Thanks

like image 314
OmB Avatar asked Sep 09 '19 09:09

OmB


1 Answers

Here is the solution I did at the end: As I mentioned, my issue was that onCreateViewHolder, creates a complex view with a huge hierarchy that cannot be changed (product-wise), and I had 3 items in a row(GridLayoutManager)

Because of that, I had 2 things that worked bad:

  1. Scrolling fast/ flinging created new items while scrolling and the UI freezed for few milliseconds.
  2. The animation between the previous screen and the screen with the RecyclerView looked bad and leggy.

So what I did is:

As I mentioned above , there's an AsyncLayoutInflater. In order to use it, I needed to inflate a dummy ViewGroup, and after that to add the async inflated view to this dummy view. Of course some extra logic should be done for supporting onBindViewHolder.

public ItemViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
    LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());

    View view = layoutInflater.inflate(R.layout.async_holder_item, parent, false);
    AsyncLayoutInflater asyncLayoutInflater = new AsyncLayoutInflater(parent.getContext());
    asyncLayoutInflater.inflate(viewType, (ViewGroup) view, new AsyncLayoutInflater.OnInflateFinishedListener() {
        @Override
        public void onInflateFinished(@NonNull View view, int resid, @Nullable ViewGroup parent) {
            parent.addView(view);
        }
    });

    return new ItemViewHolder(view, viewType);
}

You can also use a shimmer or loading state at the dummy view (keep the layout simple!) and add android:animateLayoutChanges="true" to the top view group to make it smoother

like image 85
OmB Avatar answered Sep 20 '22 17:09

OmB