RecyclerView
- an entirely updated approach to showing a collection of data. Google developers released this widget as an alternative to the predecessor: ListView
. While most of what RecyclerView
offers is an improvement over the existing functionality of ListView
, there are a few notable features missing from the RecyclerView
API. For example, we lost our dear friend OnItemClickListener
and our lesser, but still kinda close friend, ChoiceModes
. And if all that lost functionality wasn’t depressing enough, we also lost all of the sub-classes and custom implementations of ListView
, like ExpandableListView
.So, if we would like to build an expandable list view by
RecyclerView
, we must customize it. Today, in this post, I will present a third-party library developed by ThoughtBot, Inc which I think it's the best to this time, which providing us a full-featured expandable list view.DEMO VIDEO:
Importing the library
build.gradle
:
compile 'com.thoughtbot:expandablerecyclerview:1.0'
Customizing POJO classes
MobileOS
(as a group object) and Phone
(stand as child object):
MobileOS.java
package info.devexchanges.expandablerecyclerview.model;
import android.annotation.SuppressLint;
import com.thoughtbot.expandablerecyclerview.models.ExpandableGroup;
import java.util.List;
@SuppressLint("ParcelCreator")
public class MobileOS extends ExpandableGroup<Phone> {
public MobileOS(String title, List<Phone> items) {
super(title, items);
}
}
Phone.java
As you can see, the child class must implement package info.devexchanges.expandablerecyclerview.model;
import android.os.Parcel;
import android.os.Parcelable;
public class Phone implements Parcelable{
private String name;
public Phone(Parcel in) {
name = in.readString();
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Phone(String name) {
this.name = name;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(name);
}
@Override
public int describeContents() {
return 0;
}
public static final Creator<Phone> CREATOR = new Creator<Phone>() {
@Override
public Phone createFromParcel(Parcel in) {
return new Phone(in);
}
@Override
public Phone[] newArray(int size) {
return new Phone[size];
}
};
}
Parcelable
and the group class must have a List
of child class, express that a group has many children in expandable list view.Group and Child ViewHolder
GroupViewHolder
and ChildViewHolder
. These are both wrappers around regular original RecyclerView.ViewHolder
so implement any view inflation and binding methods you may need:
OSViewHolder.java
As you see, you can override package info.devexchanges.expandablerecyclerview.viewholder;
import android.util.Log;
import android.view.View;
import android.widget.TextView;
import com.thoughtbot.expandablerecyclerview.models.ExpandableGroup;
import com.thoughtbot.expandablerecyclerview.viewholders.GroupViewHolder;
import info.devexchanges.expandablerecyclerview.R;
public class OSViewHolder extends GroupViewHolder {
private TextView osName;
public OSViewHolder(View itemView) {
super(itemView);
osName = (TextView) itemView.findViewById(R.id.mobile_os);
}
@Override
public void expand() {
osName.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.down_arrow, 0);
Log.i("Adapter", "expand");
}
@Override
public void collapse() {
Log.i("Adapter", "collapse");
osName.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.up_arrow, 0);
}
public void setGroupName(ExpandableGroup group) {
osName.setText(group.getTitle());
}
}
expand()
and collapse()
to handling group view expand/collapse event.
PhoneViewHolder.java
There are 2 xml files to display group and child views of the expandable list view:
package info.devexchanges.expandablerecyclerview.viewholder;
import android.view.View;
import android.widget.TextView;
import com.thoughtbot.expandablerecyclerview.models.ExpandableGroup;
import com.thoughtbot.expandablerecyclerview.viewholders.ChildViewHolder;
import info.devexchanges.expandablerecyclerview.R;
import info.devexchanges.expandablerecyclerview.model.Phone;
public class PhoneViewHolder extends ChildViewHolder {
private TextView phoneName;
public PhoneViewHolder(View itemView) {
super(itemView);
phoneName = (TextView) itemView.findViewById(R.id.phone_name);
}
public void onBind(Phone phone, ExpandableGroup group) {
phoneName.setText(phone.getName());
if (group.getTitle().equals("Android")) {
phoneName.setCompoundDrawablesWithIntrinsicBounds(R.drawable.nexus, 0, 0, 0);
} else if (group.getTitle().equals("iOS")) {
phoneName.setCompoundDrawablesWithIntrinsicBounds(R.drawable.iphone, 0, 0, 0);
} else {
phoneName.setCompoundDrawablesWithIntrinsicBounds(R.drawable.window_phone, 0, 0, 0);
}
}
}
group_view_holder.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/black">
<TextView
android:id="@+id/mobile_os"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:drawablePadding="5dp"
android:drawableRight="@drawable/down_arrow"
android:gravity="left|center"
android:padding="8dp"
android:textColor="#e6e600" />
</RelativeLayout>
child_view_holder.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/phone_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:padding="10dp" />
</RelativeLayout>
Creating RecyclerView adapter class
ExpandableRecyclerViewAdapter
abstract class, we can customize a RecyclerView
adapter. Requirement methods are similar with original RecyclerView.Adapter
:
RecyclerAdapter.java
The last step is creating a running activity. In it's xml file, please put a package info.devexchanges.expandablerecyclerview.adapter;
import android.app.Activity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.thoughtbot.expandablerecyclerview.ExpandableRecyclerViewAdapter;
import com.thoughtbot.expandablerecyclerview.models.ExpandableGroup;
import java.util.List;
import info.devexchanges.expandablerecyclerview.R;
import info.devexchanges.expandablerecyclerview.model.MobileOS;
import info.devexchanges.expandablerecyclerview.model.Phone;
import info.devexchanges.expandablerecyclerview.viewholder.OSViewHolder;
import info.devexchanges.expandablerecyclerview.viewholder.PhoneViewHolder;
public class RecyclerAdapter extends ExpandableRecyclerViewAdapter<OSViewHolder, PhoneViewHolder> {
private Activity activity;
public RecyclerAdapter(Activity activity, List<? extends ExpandableGroup> groups) {
super(groups);
this.activity = activity;
}
@Override
public OSViewHolder onCreateGroupViewHolder(ViewGroup parent, int viewType) {
LayoutInflater inflater = (LayoutInflater) activity.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
View view = inflater.inflate(R.layout.group_view_holder, parent, false);
return new OSViewHolder(view);
}
@Override
public PhoneViewHolder onCreateChildViewHolder(ViewGroup parent, final int viewType) {
LayoutInflater inflater = (LayoutInflater) activity.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
View view = inflater.inflate(R.layout.child_view_holder, parent, false);
return new PhoneViewHolder(view);
}
@Override
public void onBindChildViewHolder(PhoneViewHolder holder, int flatPosition, ExpandableGroup group, int childIndex) {
final Phone phone = ((MobileOS) group).getItems().get(childIndex);
holder.onBind(phone,group);
}
@Override
public void onBindGroupViewHolder(OSViewHolder holder, int flatPosition, ExpandableGroup group) {
holder.setGroupName(group);
}
}
RecyclerView
object to this layout:
activity_main.xml
In the Java code, it have no important point except set up <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="info.devexchanges.expandablerecyclerview.MainActivity">
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</RelativeLayout>
LayoutManager
for RecyclerView
. Moreover, if you want to save/restore it's expand/collapse state, please overriding onSaveInstanceState()
and onRestoreInstanceState()
method.Sour code for this activity:
MainActivity.java
Running application, we'll have this output:package info.devexchanges.expandablerecyclerview;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import java.util.ArrayList;
import info.devexchanges.expandablerecyclerview.adapter.RecyclerAdapter;
import info.devexchanges.expandablerecyclerview.model.MobileOS;
import info.devexchanges.expandablerecyclerview.model.Phone;
public class MainActivity extends AppCompatActivity {
private RecyclerView recyclerView;
private ArrayList<MobileOS> mobileOSes;
private RecyclerAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
mobileOSes = new ArrayList<>();
setData();
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
recyclerView.setLayoutManager(layoutManager);
adapter = new RecyclerAdapter(this, mobileOSes);
recyclerView.setAdapter(adapter);
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
adapter.onSaveInstanceState(outState);
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
adapter.onRestoreInstanceState(savedInstanceState);
}
private void setData() {
ArrayList<Phone> iphones = new ArrayList<>();
iphones.add(new Phone("iPhone 4"));
iphones.add(new Phone("iPhone 4S"));
iphones.add(new Phone("iPhone 5"));
iphones.add(new Phone("iPhone 5S"));
iphones.add(new Phone("iPhone 6"));
iphones.add(new Phone("iPhone 6Plus"));
iphones.add(new Phone("iPhone 6S"));
iphones.add(new Phone("iPhone 6S Plus"));
ArrayList<Phone> nexus = new ArrayList<>();
nexus.add(new Phone("Nexus One"));
nexus.add(new Phone("Nexus S"));
nexus.add(new Phone("Nexus 4"));
nexus.add(new Phone("Nexus 5"));
nexus.add(new Phone("Nexus 6"));
nexus.add(new Phone("Nexus 5X"));
nexus.add(new Phone("Nexus 6P"));
nexus.add(new Phone("Nexus 7"));
ArrayList<Phone> windowPhones = new ArrayList<>();
windowPhones.add(new Phone("Nokia Lumia 800"));
windowPhones.add(new Phone("Nokia Lumia 710"));
windowPhones.add(new Phone("Nokia Lumia 900"));
windowPhones.add(new Phone("Nokia Lumia 610"));
windowPhones.add(new Phone("Nokia Lumia 510"));
windowPhones.add(new Phone("Nokia Lumia 820"));
windowPhones.add(new Phone("Nokia Lumia 920"));
mobileOSes.add(new MobileOS("iOS", iphones));
mobileOSes.add(new MobileOS("Android", nexus));
mobileOSes.add(new MobileOS("Window Phone", windowPhones));
}
}
Conclusions
RecyclerView
. Of course, you can realize that my post don't provide the way to handle child view click event. Okey, the fact that this library has an another module called expandablecheckrecyclerview
which provide the checkable child views (bot single and multi check mode) and handling the child view click event with onCheckChildCLick
interface, you can go to it's Github page to read and find out the way to use it.References:
- Library page on Github: https://github.com/thoughtbot/expandable-recycler-view
- ThoughBot, Inc home page: https://thoughtbot.com/