Android Nested RecyclerViews with expandable list

 The following is an example for creating nested RecyclerViews in Android. A list RecyclerViews are items of a parent RecyclerView. The parent RecyclerView with vertical scrolling containing a list of RecyclerViews that are also with vertical scrolling and each of them can be expanded or collapsed.

Dependencies used:

compile 'com.android.support:recyclerview-v7:25.1.0'
compile 'com.android.support:cardview-v7:25.1.0'
compile 'com.jakewharton:butterknife:7.0.1'

Model classes:

public class Child {
    String child_name;
    public Child() {}
    public String getChild_name() {
        return child_name;
    }
    public void setChild_name(String child_name) {
        this.child_name = child_name;
    }
}
import java.util.ArrayList;

public class ParentChild {
    ArrayList<Child> child;

    public ParentChild() {}

    public ArrayList<Child> getChild() {
        return child;
    }

    public void setChild(ArrayList<Child> child) {
        this.child = child;
    }
}

Adapter classes:

import android.os.Handler;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;

import com.example.nestedrecyclerview.R;
import com.example.nestedrecyclerview.model.Child;

import java.util.ArrayList;

import butterknife.Bind;
import butterknife.ButterKnife;

public class ChildAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

    private ArrayList<Child> childData;
    private ArrayList<Child> childDataBk;

    public ChildAdapter(ArrayList<Child> childData) {
        this.childData = childData;
        childDataBk = new ArrayList<>(childData);
    }

    public static class ViewHolder extends RecyclerView.ViewHolder {

        @Bind(R.id.tv_child) TextView tvChild;
        @Bind(R.id.iv_expand_collapse_toggle) ImageView tvExpandCollapseToggle;

        public ViewHolder(View itemView) {
            super(itemView);
            ButterKnife.bind(this, itemView);
        }

    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View itemLayoutView = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.nested_recycler_item_child, parent, false);

        ChildAdapter.ViewHolder cavh = new ChildAdapter.ViewHolder(itemLayoutView);
        return cavh;
    }


    final Handler handler = new Handler();
    Runnable collapseList = new Runnable() {
        @Override
        public void run() {
            if (getItemCount() > 1) {
                childData.remove(1);
                notifyDataSetChanged();
                handler.postDelayed(collapseList, 50);
            }
        }
    };

    Runnable expandList = new Runnable() {
        @Override
        public void run() {
            int currSize = childData.size();
            if (currSize == childDataBk.size()) return;

            if (currSize == 0) {
                childData.add(childDataBk.get(currSize));
            } else {
                childData.add(childDataBk.get(currSize));
            }
            notifyDataSetChanged();

            handler.postDelayed(expandList, 50);
        }
    };


    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) {
        final ViewHolder vh = (ViewHolder) holder;

        if (position == 0 && getItemCount() == 1) {
            vh.tvExpandCollapseToggle.setImageResource(R.drawable.ic_expand_more_white_24dp);
            vh.tvExpandCollapseToggle.setVisibility(View.VISIBLE);
        } else if (position == childDataBk.size() - 1) {
            vh.tvExpandCollapseToggle.setImageResource(R.drawable.ic_expand_less_white_24dp);
            vh.tvExpandCollapseToggle.setVisibility(View.VISIBLE);
        } else {
            vh.tvExpandCollapseToggle.setVisibility(View.GONE);
        }

        Child c = childData.get(position);
        vh.tvChild.setText(c.getChild_name());

        vh.itemView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (getItemCount() > 1) {
                    handler.post(collapseList);
                } else {
                    handler.post(expandList);
                }
            }
        });
    }

    @Override
    public int getItemCount() {
        return childData.size();
    }
}
import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import com.example.nestedrecyclerview.NestedRecyclerLinearLayoutManager;
import com.example.nestedrecyclerview.R;
import com.example.nestedrecyclerview.model.Child;
import com.example.nestedrecyclerview.model.ParentChild;

import java.util.ArrayList;

import butterknife.Bind;
import butterknife.ButterKnife;


public class ParentAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

    ArrayList<ParentChild> parentChildData;
    Context ctx;

    public ParentAdapter(Context ctx, ArrayList<ParentChild> parentChildData) {
        this.ctx = ctx;
        this.parentChildData = parentChildData;
    }

    public static class ViewHolder extends RecyclerView.ViewHolder {
        @Bind(R.id.rv_child)
        RecyclerView rv_child;

        public ViewHolder(View itemView) {
            super(itemView);
            ButterKnife.bind(this, itemView);
        }
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View itemLayoutView = LayoutInflater.from(parent.getContext()).inflate(R.layout.nested_recycler_item_parent, parent, false);
        ParentAdapter.ViewHolder pavh = new ParentAdapter.ViewHolder(itemLayoutView);
        return pavh;
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        ViewHolder vh = (ViewHolder) holder;
        ParentChild p = parentChildData.get(position);
        initChildLayoutManager(vh.rv_child, p.getChild());
    }

    private void initChildLayoutManager(RecyclerView rv_child, ArrayList<Child> childData) {
        rv_child.setLayoutManager(new NestedRecyclerLinearLayoutManager(ctx));
        ChildAdapter childAdapter = new ChildAdapter(childData);
        rv_child.setAdapter(childAdapter);
    }

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

Layouts
activity_main.xml

<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:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/rv_parent"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</RelativeLayout>

nested_recycler_item_child.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">
    <TextView
        android:id="@+id/tv_child"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="10dp"
        android:textStyle="bold"
        android:background="@android:color/darker_gray"
        android:textColor="@android:color/white"
        android:text="Child Item"/>
    <ImageView
        android:id="@+id/iv_expand_collapse_toggle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:layout_alignParentBottom="true"
        android:src="@drawable/ic_expand_more_white_24dp"
        android:visibility="gone"/>
</RelativeLayout>

nested_recycler_item_parent

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView
    xmlns:card_view="http://schemas.android.com/apk/res-auto"
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_margin="16dp"
    card_view:cardCornerRadius="0dp"
    android:clickable="true"
    android:background="?android:attr/selectableItemBackground">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/rv_child"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</android.support.v7.widget.CardView>

NestedRecyclerLinearLayoutManager.java

import android.content.Context;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.View;
import android.view.ViewGroup;

/**
 * http://stackoverflow.com/a/32086918/2069407
 */
public class NestedRecyclerLinearLayoutManager extends LinearLayoutManager {

    private static final String TAG = NestedRecyclerLinearLayoutManager.class.getSimpleName();

    public NestedRecyclerLinearLayoutManager(Context context) {
        super(context);
    }

    public NestedRecyclerLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
        super(context, orientation, reverseLayout);
    }

    private int[] mMeasuredDimension = new int[2];

    @Override
    public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state, int widthSpec, int heightSpec) {

        final int widthMode = View.MeasureSpec.getMode(widthSpec);
        final int heightMode = View.MeasureSpec.getMode(heightSpec);
        final int widthSize = View.MeasureSpec.getSize(widthSpec);
        final int heightSize = View.MeasureSpec.getSize(heightSpec);

        int width = 0;
        int height = 0;
        for (int i = 0; i < getItemCount(); i++) {
            measureScrapChild(recycler, i, View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
                    View.MeasureSpec.makeMeasureSpec(i, View.MeasureSpec.UNSPECIFIED),
                    mMeasuredDimension);


            if (getOrientation() == HORIZONTAL) {
                width = width + mMeasuredDimension[0];
                if (i == 0) {
                    height = mMeasuredDimension[1];
                }
            } else {
                height = height + mMeasuredDimension[1];
                if (i == 0) {
                    width = mMeasuredDimension[0];
                }
            }
        }
        switch (widthMode) {
            case View.MeasureSpec.EXACTLY:
                width = widthSize;
            case View.MeasureSpec.AT_MOST:
            case View.MeasureSpec.UNSPECIFIED:
        }

        switch (heightMode) {
            case View.MeasureSpec.EXACTLY:
                height = heightSize;
            case View.MeasureSpec.AT_MOST:
            case View.MeasureSpec.UNSPECIFIED:
        }

        setMeasuredDimension(width, height);
    }

    private void measureScrapChild(RecyclerView.Recycler recycler, int position, int widthSpec,
                                   int heightSpec, int[] measuredDimension) {
        try {
            View view = recycler.getViewForPosition(0); //fix IndexOutOfBoundsException

            if (view != null) {
                RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) view.getLayoutParams();

                int childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec,
                        getPaddingLeft() + getPaddingRight(), p.width);

                int childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec,
                        getPaddingTop() + getPaddingBottom(), p.height);

                view.measure(childWidthSpec, childHeightSpec);
                measuredDimension[0] = view.getMeasuredWidth() + p.leftMargin + p.rightMargin;
                measuredDimension[1] = view.getMeasuredHeight() + p.bottomMargin + p.topMargin;
                recycler.recycleView(view);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

MainActivity.java

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;

import com.example.nestedrecyclerview.adapter.ParentAdapter;
import com.example.nestedrecyclerview.model.Child;
import com.example.nestedrecyclerview.model.ParentChild;

import java.util.ArrayList;

/**
 * Reference
 * https://github.com/aimanbaharum/RecyclerViewCeption
 */
public class MainActivity extends AppCompatActivity {
    private RecyclerView recyclerViewParent;

    ArrayList<ParentChild> parentChildObj;


    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        recyclerViewParent = (RecyclerView) findViewById(R.id.rv_parent);


        LinearLayoutManager manager = new LinearLayoutManager(this);
        manager.setOrientation(LinearLayoutManager.VERTICAL);
        recyclerViewParent.setLayoutManager(manager);
        recyclerViewParent.setHasFixedSize(true);

        ParentAdapter parentAdapter = new ParentAdapter(this, createData());
        recyclerViewParent.setAdapter(parentAdapter);
    }


    private ArrayList<ParentChild> createData() {
        parentChildObj = new ArrayList<>();
        ArrayList<Child> list1 = new ArrayList<>();
        ArrayList<Child> list2 = new ArrayList<>();
        ArrayList<Child> list3 = new ArrayList<>();
        ArrayList<Child> list4 = new ArrayList<>();
        ArrayList<Child> list5 = new ArrayList<>();

        for (int i = 0; i < 3; i++) {
            Child c1 = new Child();
            c1.setChild_name("Child 1." + (i + 1));
            list1.add(c1);
        }

        for (int i = 0; i < 5; i++) {
            Child c2 = new Child();
            c2.setChild_name("Child 2." + (i + 1));
            list2.add(c2);
        }


        for (int i = 0; i < 2; i++) {
            Child c3 = new Child();
            c3.setChild_name("Child 3." + (i + 1));
            list3.add(c3);
        }


        for (int i = 0; i < 4; i++) {
            Child c4 = new Child();
            c4.setChild_name("Child 4." + (i + 1));
            list4.add(c4);
        }

        for (int i = 0; i < 2; i++) {
            Child c5 = new Child();
            c5.setChild_name("Child 5." + (i + 1));
            list5.add(c5);
        }


        ParentChild pc1 = new ParentChild();
        pc1.setChild(list1);
        parentChildObj.add(pc1);

        ParentChild pc2 = new ParentChild();
        pc2.setChild(list2);
        parentChildObj.add(pc2);


        ParentChild pc3 = new ParentChild();
        pc3.setChild(list3);
        parentChildObj.add(pc3);

        ParentChild pc4 = new ParentChild();
        pc4.setChild(list4);
        parentChildObj.add(pc4);

        ParentChild pc5 = new ParentChild();
        pc5.setChild(list5);
        parentChildObj.add(pc5);


        return parentChildObj;
    }
}

Android RecyclerView Multiple Layout Sealed Class

 Item.kt, the sealed class with two item types. A list of Items will be created but with different item types, either ItemOne or ItemTwo, but it will be a list of Items to be passed to the RecyclerView adapter.

sealed class Item()
class ItemOne(var name: String = "") : Item()
class ItemTwo(var name: String = "") : Item()

MainActivity.kt, the main class that creates the sample data for the recycerview.

import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.support.v7.widget.DefaultItemAnimator
import android.support.v7.widget.LinearLayoutManager
import kotlinx.android.synthetic.main.activity_main.*

import java.util.ArrayList

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // Initializing list view with the custom adapter
        val itemList = ArrayList<Item>()
        val itemArrayAdapter = ItemArrayAdapter(itemList)
        item_list.layoutManager = LinearLayoutManager(this)
        item_list.itemAnimator = DefaultItemAnimator()
        item_list.adapter = itemArrayAdapter

        // Populating list items
        for (i in 0..99) {
            if (i % 2 == 0) {
                itemList.add(ItemOne("Item " + i))
            } else {
                itemList.add(ItemTwo("Item " + i))
            }
        }
    }
}

ItemArrayAdapter.kt, in the getItemViewType, it checks the type of the items, and it is being used to determine which layout to use.

import android.support.v7.widget.RecyclerView
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import java.util.ArrayList

class ItemArrayAdapter(private val itemList: ArrayList<Item> = ArrayList()) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {

    // get the size of the list
    override fun getItemCount(): Int = itemList.size

    // determine which layout to use for the row
    override fun getItemViewType(position: Int): Int {
        val item = itemList[position]
        return when (item) {
            is ItemOne -> TYPE_ONE
            is ItemTwo -> TYPE_TWO
        }
    }

    // specify the row layout file and click for each row
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        if (viewType == TYPE_ONE) {
            val view = LayoutInflater.from(parent.context).inflate(R.layout.list_item_type1, parent, false)
            return ViewHolderOne(view)
        } else if (viewType == TYPE_TWO) {
            val view = LayoutInflater.from(parent.context).inflate(R.layout.list_item_type2, parent, false)
            return ViewHolderTwo(view)
        } else {
            throw RuntimeException("The type has to be ONE or TWO")
        }
    }

    // load data in each row element
    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, listPosition: Int) {
        when (holder.itemViewType) {
            TYPE_ONE -> initLayoutOne(holder as ViewHolderOne, listPosition)
            TYPE_TWO -> initLayoutTwo(holder as ViewHolderTwo, listPosition)
            else -> {
            }
        }
    }

    private fun initLayoutOne(holder: ViewHolderOne, pos: Int) {
        val item = itemList[pos] as ItemOne
        holder.item.text = item.name
    }

    private fun initLayoutTwo(holder: ViewHolderTwo, pos: Int) {
        val item = itemList[pos] as ItemTwo
        holder.tvLeft.text = item.name
        holder.tvRight.text = item.name
    }

    // Static inner class to initialize the views of rows
    internal class ViewHolderOne(itemView: View) : RecyclerView.ViewHolder(itemView) {
        var item: TextView
        init {
            item = itemView.findViewById(R.id.row_item) as TextView
        }
    }

    inner class ViewHolderTwo(itemView: View) : RecyclerView.ViewHolder(itemView) {
        var tvLeft: TextView
        var tvRight: TextView

        init {
            tvLeft = itemView.findViewById(R.id.row_item_left) as TextView
            tvRight = itemView.findViewById(R.id.row_item_right) as TextView
        }
    }

    companion object {
        private val TYPE_ONE = 1
        private val TYPE_TWO = 2
    }
}
<pre>

activity_main.xml
<pre>
<?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="match_parent"
    >
    <android.support.v7.widget.RecyclerView
        android:id="@+id/item_list"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scrollbars="vertical" />
</LinearLayout>

list_item_type1.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:padding="10dp">
    <TextView
        android:id="@+id/row_item"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:gravity="center"
        android:textColor="#fff"
        android:text="Left"
        android:background="#10f1ed"/>
</LinearLayout>

list_item_type2.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="10dp">
    <TextView
        android:id="@+id/row_item_left"
        android:layout_width="0dp"
        android:layout_height="50dp"
        android:layout_weight="5"
        android:gravity="center"
        android:textColor="#fff"
        android:text="Left"
        android:background="#c7da1b"/>
    <TextView
        android:id="@+id/row_item_right"
        android:layout_width="0dp"
        android:layout_height="50dp"
        android:layout_weight="5"
        android:gravity="center"
        android:textColor="#fff"
        android:text="Right"
        android:background="#36e6ae"/>
</LinearLayout>

How to extract filename from Uri?

Now, we can extract filename with and without extension :) You will convert your bitmap to uri and get the real path of your file. Now w...