Reputation: 1838
I want to create the following UI with a single list of dataset.
I tried using multiple view type but could not achieve my requirement. I also implemented this blog Android Horizontal and Vertical RecyclerView Example.
But this uses two recyclerviews and there are two sets of data (horizontal data and vertical data).
I also have tried this one. RecyclerView with multiple views using custom adapter in Android
But this is using static card views in XML and loading them in adapter.
I'm beginner in Android development. Please help!
Thank you in advance.
Upvotes: 2
Views: 3088
Reputation: 11
I had this issue not too long ago, though I may not be an expert I think my answer can help you as well. You can do this by creating two different types of layouts, similar to the article. You need to create an abstract class that both of the layout types can extend. Then, in your adapter, check what type of object should be shown.
Abstract class:
public abstract class ListItem {
public static final int TYPE_HORIZONTAL = 0;
public static final int TYPE_VERTICAL = 1;
abstract public int getType();
}
Horizontal Item:
public class HorizontalItem extends ListItem {
private String text;
private Bitmap image;
public HorizontalItem(String text, Bitmap image) {
this.text = text;
this.image = image;
}
/*
* Getter and setter methods here
*/
@Override
public int getType() {
return ListItem.TYPE_HORIZONTAL;
}
}
Vertical Item:
public class VerticalItem extends ListItem {
private String text1;
private Bitmap image1;
private String text2;
private Bitmap image2;
public VerticalItem(String text1, String text2, Bitmap image1, Bitmap image2) {
this.text1 = text1;
this.image1 = image1;
this.text2 = text2;
this.image2 = image2;
}
/*
* Getter and setter methods here
*/
@Override
public int getType() {
return ListItem.TYPE_HORIZONTAL;
}
}
Adapter:
public class ListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private ArrayList<ListItem> listItems;
public static class HorizontalViewHolder extends RecyclerView.ViewHolder {
public TextView text;
public ImageView imageView;
public HorizontalHolder(View v) {
super(v);
text = (TextView) v.findViewById(R.id.text);
imageView = (ImageView) v.findViewById(R.id.imageView);
}
}
public static class VerticalViewHolder extends RecyclerView.ViewHolder {
public TextView text1;
public ImageView imageView1;
public TextView text2;
public ImageView imageView2;
public HorizontalHolder(View v) {
super(v);
text1 = (TextView) v.findViewById(R.id.text1);
imageView1 = (ImageView) v.findViewById(R.id.imageView1);
text2 = (TextView) v.findViewById(R.id.text2);
imageView2 = (ImageView) v.findViewById(R.id.imageView2);
}
}
// constructor
public ListAdapter(ArrayList<ListItem> listItems) {
this.listItems = listItems;
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v;
RecyclerView.ViewHolder holder = null;
switch (viewType) {
case ListItem.TYPE_HORIZONTAL:
v = LayoutInflater.from(parent.getContext()).inflate(R.layout.template_horizontal, parent, false);
holder = new HorizontalViewHolder(v);
break;
case ListItem.TYPE_VERTICAL:
v = LayoutInflater.from(parent.getContext()).inflate(R.layout.template_vertical, parent, false);
holder = new VerticalViewHolder(v);
break;
}
return holder;
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
switch (holder.getItemViewType()) {
case ListItem.TYPE_HORIZONTAL:
((HorizontalViewHolder) holder).text.setText(listItems.get(position).getText());
((HorizontalViewHolder) holder).imageView.setImageBitmap(listItems.get(position).getBitmap());
// the getText() and getBitmap() methods come from the getters of the HorizontalItems and VerticalItems that are stored in the ArrayList, listItems
break;
case ListItem.TYPE_DECK:
// Identical to above
break;
}
}
@Override
public int getItemCount() {
return listItems.size();
}
// This is extremely important, it is what lets the adapter know what type each listItem element is
@Override
public int getItemViewType(int position) {
return listItems.get(position).getType();
}
}
Activity:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_list);
// Must be of abstract type ListItem
ArrayList<ListItem> items = new ArrayList<>();
// populate your ArrayList
items.add(new HorizontalItem("text", bitmap));
items.add(new VerticalItem("text1", "text2", bitmap1, bitmap2));
// ... and so on
ListAdapter adapter = new ListAdapter(items)
RecyclerView recycler = (RecyclerView) findViewById(R.id.recycler);
recycler.setAdapter(adapter);
recycler.setLayoutManager(new LinearLayoutManager(this));
// ... the rest of your code below
}
In your layout for the vertical list item, I would just create one file with two halves to it. You can use a LinearLayout to easily divide the subsections into perfect halves.
Upvotes: 1
Reputation: 54214
Your desired layout can be achieved by using a GridLayoutManager
along with two "item view types" inside your RecyclerView.Adapter
.
Here are my layout XML files:
activity_main.xml:
-------------------------
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.RecyclerView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/recycler"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
horizontal.xml:
-------------------------
<android.support.v7.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="120dp"
android:layout_margin="4dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<ImageView
android:id="@+id/image"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="#ccc"/>
<TextView
android:id="@+id/text"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center"
android:background="#fff"
android:textColor="#000"
android:textSize="18sp"
android:text="TEXT"/>
</LinearLayout>
</android.support.v7.widget.CardView>
vertical.xml:
-------------------------
<android.support.v7.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="120dp"
android:layout_margin="4dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ImageView
android:id="@+id/image"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:background="#ccc"/>
<TextView
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:gravity="center"
android:background="#fff"
android:textColor="#000"
android:textSize="18sp"
android:text="TEXT"/>
</LinearLayout>
</android.support.v7.widget.CardView>
And here is my Java file:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
GridLayoutManager manager = new GridLayoutManager(this, 2);
manager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
return (position % 4) < 2 ? 2 : 1;
}
});
RecyclerView recycler = (RecyclerView) findViewById(R.id.recycler);
recycler.setLayoutManager(manager);
recycler.setAdapter(new MyAdapter());
}
private static class MyAdapter extends RecyclerView.Adapter<MyViewHolder> {
@Override
public int getItemViewType(int position) {
return (position % 4) < 2
? R.layout.horizontal
: R.layout.vertical;
}
@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
View itemView = inflater.inflate(viewType, parent, false);
return new MyViewHolder(itemView);
}
@Override
public void onBindViewHolder(MyViewHolder holder, int position) {
holder.image.setImageResource(R.drawable.mouse);
holder.text.setText("" + position);
}
@Override
public int getItemCount() {
return Integer.MAX_VALUE;
}
}
private static class MyViewHolder extends RecyclerView.ViewHolder {
private final ImageView image;
private final TextView text;
public MyViewHolder(View itemView) {
super(itemView);
this.image = (ImageView) itemView.findViewById(R.id.image);
this.text = (TextView) itemView.findViewById(R.id.text);
}
}
}
Let's go over the important parts. First up is the combination of GridLayoutManager
and SpanSizeLookup
. We're creating the layout manager with this line:
GridLayoutManager manager = new GridLayoutManager(this, 2);
Which means that, by default, there will be two cards in each row of our grid. But then we apply the SpanSizeLookup
, which says that half of our rows (found by the statement position % 4 < 2
) should actually take up two columns. So we'll have one card, one card, two cards repeating in our "grid".
Then, in the RecyclerView.Adapter
class, we override the getItemViewType()
method. Here we again use the position % 4 < 2
statement to say that half of our views should be horizontal, and half should be vertical.
getItemViewType()
just needs to return any unique int
for each view type, so we use a nice trick of returning R.layout
constants from this method. Since the view type will then be passed into onCreateViewHolder()
, we can use the viewType
argument to inflate the correct layout.
And that's it! Not too bad after all. Here's a screenshot of my code in action:
Upvotes: 3