Is there a way to use a ViewStub inside a RecyclerView?
Asked Answered
D

1

6

i'm new to android, I've been working on a project, and in my news feeds page, I'm trying to include a modular feed RecyclerView, which shows a question with different answer forms, varrying according to the Question type. The way I was doing it so far was by using the include and turning the forms visible when needed. recently since i added more modules, the app started to slowdown segnificantly, so i'm trying to implement ViewStubs.

This is my RecyclerView adapter:

public class ReQuestionAdapter extends RecyclerView.Adapter<FeedItem> {
private ArrayList<Question> myQuestions;
public ReQuestionAdapter(Context context, ArrayList<Question> questions) {
    myQuestions = questions ;
}

@Override
public FeedItem onCreateViewHolder(ViewGroup parent, int viewType) {
    View view = LayoutInflater.from(parent.getContext())
            .inflate(R.layout.list_item_re_question, parent, false);
    return new FeedItem(view);
}

@Override
public void onBindViewHolder(FeedItem holder, int position) {
    Question q = myQuestions.get(position);
    holder.bindQuestion(q);
}

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

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

And this is the ViewHolder class for the adapter:

public class FeedItem extends RecyclerView.ViewHolder{
private Question mQuestion;
public TextView tvName;
public TextView tvTime;
public TextView tvContent;
public ProfilePictureView profilePictureView;
public ViewStub moduleView;
private int moduleType;

public FeedItem(View itemView) {
    super(itemView);

}

public void bindQuestion(Question question) {
    mQuestion = question;
    tvTime = (TextView) itemView.findViewById(R.id.li_q_date);
    tvContent = (TextView) itemView.findViewById(R.id.li_q_content);
    moduleView = (ViewStub) itemView.findViewById(R.id.module_viewstub);
    tvTime.setText(TimeHandler.When(mQuestion.publish_time));
    tvContent.setText(mQuestion.content);
    moduleType = question.type;
        switch (moduleType) {
            case Question.TYPE_YN:
                moduleView.setLayoutResource(R.layout.module_yes_no);
                moduleView.inflate();
                break;
            case Question.TYPE_CUSTOM:
                moduleView.setLayoutResource(R.layout.module_custom);
                moduleView.inflate();
                break;
            default:
                break;
        }
    }
}

Now, the problem is that the ViewStub which contains a certain layout, cannot be reinflated with a new one, the reason for that is that it gets removed from the view hirarchy as soon as it leaves the screen, the symptoms: When scrolling down the RecyclerView, the first list items that fill the screen are working perfect, but others to load when the previous leave the screen cause the FeedItem binding to bring a NullPointerException. (It canno't find it in the list item layout).

I'm looking for a solution as efficiant as ViewStubs, or a way to make them work properly, since I got many modules and inflating them all in each item as invisible would make my app slow.

Dewyeyed answered 24/7, 2015 at 11:19 Comment(0)
T
7

In your bindQuestion() method you are referencing two different layouts to inflate, so in essence you have two different view types.

Adapter views have an efficient way way to handle this built right in.

Start by overriding getItemViewType(). When the item at position gets the module_yes_no layout, return 0. When it gets the module_custom layout, return 1.

Then in onCreateViewHolder(), when the viewType parameter is 0, inflate a list_item_re_question view complete with the module_yes_no layout. When viewType == 1, inflate the module_custom version of the view.

Now when you get a view in onBindViewHolder(), it will already have the correct subview, so you proceed to fill out that view as needed. By using getItemViewType(), the RecyclerView is working with you to recycle the exact view you need.

You can even have two FeedItem subclasses, one for module_yes_no and one for module_custom, so in onBindViewHolder(), you just check the class of the ViewHolder and branch accordingly.

That should help improve the performance of your app.

Tema answered 24/7, 2015 at 12:7 Comment(9)
Thx alot, so i get that using two different FeedItem classes, the recyclerview would know by itself which parts to preserve and which are different? or will it create 2 cycling views repeating according to the request?Dewyeyed
The two FeedItem classes are just for your benefit. RecyclerView knows which view to recycle for you by virtue of you overriding getItemViewType(). So let's say item 20 is a yes_no. If you have set up getItemViewType() to return 0 for yes_no items, then when user scrolls to item 20, RecyclerView calls your getItemViewType() with position == 20. It receives 0, and says, "Oh look, I have a type 0 view for recycling right here", and passes that view to onBindViewHolder(). Make sense?Tema
Then if RecyclerView doesn't have a view available to recycle, it calls onCreateViewHolder() with viewType == 0 so you know which type of view to inflate and which type of view holder to create.Tema
Got it, thank you very much sir you really helped me :P I was also wandring if i should be using a question display layout and a answer form layout, and include them together to different layouts which i'll use for the inflation:Dewyeyed
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:padding="15dp" android:background="#ffffff"> <include layout="@layout/list_item_re_question" /> <include layout="@layout/module_yes_no"/> </RelativeLayout>Dewyeyed
or is there an easier way to do that, like implamenting the part by code somehow (simillarly to viewstub)Dewyeyed
You can do that a few different ways. You could even have two different versions of the question layout fully defined without using includes (that would be my choice). That part isn't as critical. Your app gets the performance benefit because your recycled views are fully inflated and you don't have to inflate something every single time the user scrolls. I think you should try a couple different ways and see if one seems to make scrolling a little more snappy.Tema
@kris larson I have a similar use case where I need to select one of two different layouts for a RecyclerView that inflates (only) a single CardView. If the CardView has a small amount of data from the Room database then use a "small" layout. If the CardView has a large amount of data from the database then use an expanded layout. Would your recommendation to override getItemViewType() work in this case? If so, are both set up or do I need to use an "if (small data)" {layout 1}, else {layout 2}? I would appreciate any small example you could provide.Rapt
@Rapt Yes, you should override getItemViewType(). It's very simple: Step 1: Override getItemViewType() so it returns 0 for small data, 1 for large data. Step 2: Override onCreateViewHolder() with a switch on the viewType parameter: 0 inflates small layout, 1 inflates expanded layout. Step 3: In onBindViewHolder(), your view holder parameter will already have the correct layout inflated for your data by virtue of step 1 and step 2. If you're stuck, post a new question so the community has a chance to answer. If you link to your question in a comment here I will take a look at it.Tema

© 2022 - 2024 — McMap. All rights reserved.