RecyclerView
is the upgrade of ListView
and there are a lot of advantages compared to ListView
such as CardView
and ViewHolder
.
In this tutorial we are going to see how to customize RecyclerView by adding separate layout for header in LinearLayoutManager
and GridLayoutManager
.
To implement heterogeneous layouts inside the RecyclerView
, most of the work is done within the RecyclerView.Adapter
. In particular, there are special methods to be overridden within the adapter:
getItemViewType()
onCreateViewHolder()
onBindViewHolder()
LinearLayoutManager
We need two classes, which represents two objects working with header and list item. I named these classes Header
and Item
. For simplicity, I just made the Header
and Item
classes with one field. However you can modify it to meet your needs by adding more fields or change the type of field.
Let's start from ListItem
interface which is base for items in RecyclerView
.
public interface ListItem { int TYPE_ITEM = 0; int TYPE_HEADER = 1; int getItemType(); }
Declare Header
public class Header implements ListItem { String name; public Header(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public int getItemType() { return ListItem.TYPE_HEADER; } }
Declare Item
public class Item implements ListItem { String name; public Person(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public int getItemType() { return ListItem.TYPE_ITEM; } }
Following is layout for header_layout.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="0dp"> <TextView android:id="@+id/tvName" android:textSize="16sp" android:layout_width="match_parent" android:layout_height="wrap_content" android:textStyle="bold" android:textColor="@android:color/white" android:background="@color/colorAccent" android:padding="5dp"/> </LinearLayout>
Following is layout for item_layout.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="wrap_content"> <TextView android:id="@+id/tvItem" android:layout_marginTop="15dp" android:layout_marginBottom="15dp" android:layout_gravity="center" android:textSize="14sp" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </LinearLayout>
We will create an adapter for our RecyclerView
. This adapter must extend RecyclerView.Adapter
which is followed by a ViewHolder
pattern. ViewHolder
will help the system avoid calling findViewById
method every time it shows a row.
public class DataAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { private ArrayList<ListItem> items; public DataAdapter(ArrayList<ListItem> items) { this.items = items; } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if(viewType == ListItem.TYPE_HEADER) { View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.header_layout, parent, false); return new VHHeader(v); } else if(viewType == ListItem.TYPE_ITEM) { View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_layout, parent, false); return new VHItem(v); } throw new RuntimeException("there is no type that matches the type " + viewType + " + make sure your using types correctly"); } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { if(holder instanceof VHHeader) { Header header = (Header) items.get(position); VHHeader VHheader = (VHHeader)holder; VHheader.tvName.setText(header.getName()); } else if(holder instanceof VHItem) { Person person = (Person) items.get(position); VHItem VHitem = (VHItem)holder; VHitem.tvItem.setText(person.getName()); } } @Override public int getItemCount() { return items.size(); } @Override public int getItemViewType(int position) { return items.get(position).getItemType(); } class VHHeader extends RecyclerView.ViewHolder{ TextView tvName; public VHHeader(View itemView) { super(itemView); this.tvName = (TextView)itemView.findViewById(R.id.tvName); } } class VHItem extends RecyclerView.ViewHolder{ TextView tvItem; public VHItem(View itemView) { super(itemView); this.tvItem = (TextView)itemView.findViewById(R.id.tvItem); } } }
Following is layout for activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="proft.me.sandbox.RecycleHeaderActivity"> <android.support.v7.widget.RecyclerView android:id="@+id/rvItems" android:scrollbars="vertical" android:layout_width="match_parent" android:layout_height="match_parent"/> </android.support.constraint.ConstraintLayout>
Following is MainActivity
public class MainActivity extends AppCompatActivity { private ArrayList<ListItem> items = new ArrayList<>(); private DataAdapter adapter; private RecyclerView recyclerView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); recyclerView = (RecyclerView)findViewById(R.id.rvItems); recyclerView.setHasFixedSize(true); RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(getApplicationContext()); recyclerView.setLayoutManager(layoutManager); adapter = new DataAdapter(items); recyclerView.setAdapter(adapter); items.add(new Header("Header 1")); items.add(new Person("Item 1", true)); items.add(new Person("Item 2", true)); items.add(new Person("Item 3", true)); items.add(new Header("Header 2")); items.add(new Person("Item 4", false)); items.add(new Person("Item 5", false)); items.add(new Person("Item 6", false)); items.add(new Person("Item 7", false)); items.add(new Person("Item 8", false)); adapter.notifyDataSetChanged(); } }
Result
GridLayoutManager
The GridLayoutManager
allows you to show a RecyclerView
as a grid. Using SpanSizeLookup
you can change the span size of the header so it expands to fit as many columns as you have.
Just modify MainActivity, add GridLayoutManager
and redefine SpanSizeLookup.
public class MainActivity extends AppCompatActivity { private ArrayList<ListItem> items = new ArrayList<>(); private DataAdapterHeader adapter; private RecyclerView recyclerView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); recyclerView = (RecyclerView)findViewById(R.id.rvItems); recyclerView.setHasFixedSize(true); adapter = new DataAdapterHeader(items); recyclerView.setAdapter(adapter); GridLayoutManager gd = new GridLayoutManager(this, 2); gd.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { @Override public int getSpanSize(int position) { return adapter.getItemViewType(position) == ListItem.TYPE_HEADER ? 2 : 1; } }); recyclerView.setLayoutManager(gd); items.add(new Header("Header 1")); items.add(new Person("Item 1", true)); items.add(new Person("Item 2", true)); items.add(new Person("Item 3", true)); items.add(new Header("Header 2")); items.add(new Person("Item 4", false)); items.add(new Person("Item 5", false)); items.add(new Person("Item 6", false)); items.add(new Person("Item 7", false)); items.add(new Person("Item 8", false)); adapter.notifyDataSetChanged(); } }