[SOLVED] Android – Custom dynamically-generated compound View recreated in RecyclerView, causing poor performance

Issue

I’m using a custom CompoundView which extends LinearLayout to display items of a RecyclerView. Each item displays an article which contains multiple paragraphs and images.

The CompoundView adds TextView or ImageView dynamically based on the data attached by CompoundView.setData(List<DataPiece> pieces), the number of which is unknown before data is attached.

Each DataPiece object tells CompoundView whether it’s a piece of text or an image. And here is the code for CompoundView.setData(List<DataPiece> pieces):

public void setData(List<DataPiece> pieces) {
    removeAllViews();
    for (DataPiece dataPiece : pieces) {
        switch (dataPiece.getType()) {
            case IMAGE:
                ImageView imageView = new ImageView(getContext());
                ...
                addView(imageView);
                break;
            case TEXT:
                TextView textView = new TextView(getContext());
                ...
                addView(textView);
                break;
        }
    }
}

In the RecyclerView.Adapter.onBindViewHolder(), the data is attached to CompoundView by calling MyViewHolder.compoundView.setData(...). And it works fine when the RecyclerView is created.

However, for a CompoundView item with multiple ImageViews and TextViews, when I scroll away from it and then scroll back, the scroll becomes heavily unsmooth.

I guess it’s because removeAllViews() in setData() is called, and the CompoundView creation for-loop is executed again by the recycler. But I don’t know how to avoid this.

And I also wonder why the scroll is always smooth when using TextView(with Images) in a RecyclerView even it’s recycled too.

Thanks in advance!

Solution

There are multiple considerations that could go into deciding what the best approach might be.

First, do you have an idea about the maximum number of items in the recycler’s list? If it is just a handful, maybe you could ditch the RecyclerView approach and just add your CompoundView into a container hosted by a ScrollView.

Secondly – is the layout of each item fairly complicated (a.k.a. are there many TextViews, ImageViews etc. in it)? If yes, maybe you could take an approach that would resemble an ExpandableListView – show a summary as each list item and expand to the full layout of the item on click.

Thirdly – if none of the above is acceptable and you still want to go the current approach – don’t construct/add your view in the binding method. Do it in the onCreateViewHolder, when the system expects you to construct your view (I don’t know for sure but by the time you’re called on onBindViewHolder your view might have been already added to the hierarchy and any hierarchical change to it has a ripple effect on its containers – but don’t take my word for it, I don’t actually know the view is already added, it is just an assumption). You will have to assign each item a different type, so that in onCreateViewHolder you could match the view type with the supporting data (for the addition of the corresponding number of child views); create the view from scratch each time – this way you don’t need to call on removeAllViews. Something like(I left out parts of the adapter that are not relevant to the case):

public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    ArrayList<DataPiecesList> mItems;
    public RecyclerViewAdapter(ArrayList<DataPiecesList> items) {
        mItems = items;
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        CompoundView compoundView = new CompoundView();
        List<DataPiece> dataPieces = mItems.get(viewType);
        for (int i = 0; i < dataPieces.size(); i++)
        {
            // construct TextView or ImageView or whatever
            compoundView.add(child);
        }
        MyViewHolder view = new MyViewHolder(compoundView);
        return view;
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int i) {
        CompoundView compoundView = viewHolder.itemView;
        DataPiece dataPiece = mItems.get(i);
        for (int j = 0; j < compoundView.getChildCount(); j++)
        {
            compoundView.getChildAt(j) <- dataPiece.get(j);
        }
    }

    @Override
    public int getItemViewType(int position) {
        return position;
    }

    @Override
    public int getItemCount() {
        return mItems.size();
    }

    public class MyViewHolder extends RecyclerView.ViewHolder {
        ...
        public MyViewHolder(View itemView) {
            super(itemView);
        }
    }
}

Answered By – N.T.

Answer Checked By – Marie Seifert (BugsFixing Admin)

Leave a Reply

Your email address will not be published. Required fields are marked *