I have a RecyclerView adapter. Within the onBindViewHolder
, I check how many images each item passed to the adapter has. Depending on the number of images, I want to load/inflate a child layout into the main layout.
For example:
images_one.xml
into the main layoutimages_two.xml
into the main layoutimages_eight.xml
)Here is the main layout, main_layout.xml
:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
// INFLATE CHILD LAYOUT HERE (images_one.xml, images_two.xml, etc.)
<TextView
android:id="@+id/desc"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
</LinearLayout>
And here is one of the child layouts that need to be inflated into the main layout, images_two.xml
:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<ImageView
android:id="@+id/image_one"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
<ImageView
android:id="@+id/image_two"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
</LinearLayout>
And lastly, here is my RecyclerView adapter:
public class RecyclerAdapter extends RecyclerView.Adapter<RecyclerAdapter.ViewHolder> {
private static Context context;
private List<Message> mDataset;
public RecyclerAdapter(Context context, List<Message> myDataset) {
this.context = context;
this.mDataset = myDataset;
}
public static class ViewHolder extends RecyclerView.ViewHolder implements View.OnCreateContextMenuListener, View.OnClickListener {
public TextView title;
public TextView desc;
public ViewHolder(View view) {
super(view);
view.setOnCreateContextMenuListener(this);
title = (TextView) view.findViewById(R.id.title);
desc = (TextView) view.findViewById(R.id.desc);
}
}
@Override
public RecyclerAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.main_layout, parent, false);
ViewHolder vh = new ViewHolder((LinearLayout) view);
return vh;
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
Message item = mDataset.get(position);
holder.title.setText(item.getTitle());
holder.desc.setText(item.getDesc());
int numImages = item.getImages().size();
if (numImages == 1) {
// Inflate images_one.xml into main_layout.xml
} else if (numImages == 2) {
// Inflate images_two.xml into main_layout.xml
} else if (numImages == 3) {
// Inflate images_three.xml into main_layout.xml
}
// ETC...
}
@Override
public int getItemCount() {
return mDataset.size();
}
}
What's the best way of implementing this?
The way Android intends ViewHolder
s to be used is for views to be created/inflated in onCreateViewHolder()
then filled with adapter data in onBindViewHolder()
. This is important because onCreateViewHolder()
is only called when there is nothing to recycle.
Unlike ListView
, RecyclerView
is actually recycling ViewHolder
s rather than View
s. So trying to inflate views in onBindViewHolder()
is not going to work.
Notice that viewType
parameter in onCreateViewHolder()
? That's the key.
First you need to override getItemViewType()
in your adapter. Let's take a shortcut here; since the view type is an int
, let's just use the number of images in the item as the view type:
@Override
public int getItemViewType(int position) {
// ... return the number of images for data at position
}
(Normally I would define final int
s like VIEW_TYPE_ONE_IMAGE
etc. to be returned, which is the proper way to do it.)
How does the adapter use the view types? If your data at position 0 has a view type of 4, then the RecyclerView
will only pass a ViewHolder
created in onCreateViewHolder()
with viewType == 4
to onBindViewHolder()
with position == 0
.
Now your onCreateViewHolder()
might look like this:
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.main_layout, parent, false);
return new ViewHolder(view, viewType);
}
(FYI: The return type for onCreateViewHolder()
should be your ViewHolder
type, not RecyclerView
s.)
Notice that I added a viewType
parameter to your ViewHolder
constructor:
public static class ViewHolder extends RecyclerView.ViewHolder implements View.OnCreateContextMenuListener, View.OnClickListener {
public TextView title;
public TextView desc;
public ImageView img1;
public ImageView img2;
public ViewHolder(View view, int viewType) {
super(view);
view.setOnCreateContextMenuListener(this);
title = (TextView) view.findViewById(R.id.title);
desc = (TextView) view.findViewById(R.id.desc);
ViewGroup placeholder = (ViewGroup) view.findViewById(R.id.placeholder);
// here you can use the viewType to help you figure
// out which child views you need references for
switch (viewType) {
case 1:
// ...
break;
case 2:
LayoutInflater.from(parent.getContext()).inflate(R.layout.images_two, placeholder); // this combines inflate() and addView()
img1 = placeholder.findViewById(R.id.image_one);
img2 = placeholder.findViewById(R.id.image_two);
break;
// ...
}
}
}
You don't necessarily need viewType
in the constructor if you are using the same ids in all your different views; you can just check if they're null in onBindViewHolder()
.
If you have many different view types, one great way to organize the create/bind code is to make your ViewHolder
subclass abstract, further subclass it into a different type for each view type, and then return an instance of the appropriate type in onCreateViewHolder()
. Declare an abstract method like public void bind(YourItemType item)
in your abstract type and implement in your subclasses, then your onBindViewHolder()
method is just:
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
holder.bind(data.get(position)); // ... or however you access an item at position
}
...and creating looks like:
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = null;
switch (viewType) {
case 1:
view = LayoutInflater.from(parent.getContext()).inflate(R.layout.images_one, parent, false);
return new OneImageViewHolder(view);
case 2:
view = LayoutInflater.from(parent.getContext()).inflate(R.layout.images_two, parent, false);
return new TwoImageViewHolder(view);
// ... etc. etc.
}
}
Don't forget, you may be passed a recycled ViewHolder
in onBindViewHolder()
, so you have to assume all your views are "dirty" and set every single child view to a known state for the data at position
.
Collected from the Internet
Please contact [email protected] to delete if infringement.
Comments