57

I want to add some child Views (List Items), which come to me from JSON formatted data. Each child list is under a parent list item row. How can I populate it in RecyclerView for each row item (Parent items with child list items)?

I have tried using RecyclerView within RecyclerView parent row (for populating child lists), but here child views are not visible.

Parent Adapter Class

public class DigitizedPrescAdapter extends RecyclerView.Adapter<DigitizedPrescAdapter.ListItemViewHolder>{
    private List<PrescriptionModal> prescriptionList;

    MedicinesInPrescAdapter adapter;

    public DigitizedPrescAdapter(List<PrescriptionModal> prescriptionListModal) {

        if (prescriptionListModal == null) {
            throw new IllegalArgumentException(
                    "PrescriptionList must not be null");
        }
        this.prescriptionList = prescriptionListModal;
    }

    @Override
    public ListItemViewHolder onCreateViewHolder(
            ViewGroup viewGroup, int viewType) {
        View itemView = LayoutInflater.
                from(viewGroup.getContext()).
                inflate(R.layout.item_row_digitised_request,
                        viewGroup,
                        false);
        return new ListItemViewHolder(itemView);
    }

    @Override
    public void onBindViewHolder(
            ListItemViewHolder viewHolder, int position) {
        PrescriptionModal model = prescriptionList.get(position);

        viewHolder.prescnum.setText("Prescription "+ ++position);
        viewHolder.prescNo.setText("Prescription: "+model.getPrescriptionID());
        viewHolder.doctorType.setText("Type: "+model.getDoctorType());
        viewHolder.doctorName.setText("Doctor: "+model.getDoctorName());
        viewHolder.patientName.setText("Patient: "+model.getPatientName());

        adapter = new MedicinesInPrescAdapter(model.getLstproduct());
        viewHolder.lstMedicines.setAdapter(adapter);

    }

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

    public final static class ListItemViewHolder
            extends RecyclerView.ViewHolder {

        TextView prescnum;
        TextView prescNo;
        TextView doctorType;
        TextView patientName;
        TextView doctorName;
        CheckBox selectAll;
        RecyclerView lstMedicines;

        public ListItemViewHolder(View itemView) {
            super(itemView);
            prescnum = (TextView) itemView.findViewById(R.id.prescnum);
            prescNo = (TextView) itemView.findViewById(R.id.prescNo);
            doctorType = (TextView) itemView.findViewById(R.id.doctorType);
            patientName = (TextView) itemView.findViewById(R.id.patientName);
            doctorName = (TextView) itemView.findViewById(R.id.doctorName);
            selectAll = (CheckBox) itemView.findViewById(R.id.selectAll);
            lstMedicines = (RecyclerView) itemView.findViewById(R.id.lstAllMedicines);
            MyLinearLayoutManager layoutManager = new MyLinearLayoutManager(itemView.getContext(),LinearLayoutManager.VERTICAL,false);
            lstMedicines.setHasFixedSize(false);
            lstMedicines.setLayoutManager(layoutManager);
        }
    }
}

Child Adapter Class

public class MedicinesInPrescAdapter extends RecyclerView.Adapter<MedicinesInPrescAdapter.MedicineListItemViewHolder>{

    List<Modal_Product_List> prescriptionProducts;

    public MedicinesInPrescAdapter(List<Modal_Product_List> prescriptionListProd) {

        if (prescriptionListProd == null) {
            throw new IllegalArgumentException(
                    "PrescriptionProductList must not be null");
        }
        this.prescriptionProducts = prescriptionListProd;
    }

    @Override
    public MedicineListItemViewHolder onCreateViewHolder(
            ViewGroup viewGroup, int viewType) {
        View itemView = LayoutInflater.
                from(viewGroup.getContext()).
                inflate(R.layout.item_row_medicine_productlist,
                        viewGroup,
                        false);
        return new MedicineListItemViewHolder(itemView);
    }

    @Override
    public void onBindViewHolder(
            MedicineListItemViewHolder viewHolder, int position) {
        Modal_Product_List modelMedicine = prescriptionProducts.get(position);

        viewHolder.medicineName.setText(modelMedicine.getMedicinename());
        viewHolder.medQty.setText(modelMedicine.getQuantity());
        viewHolder.days.setText("30");
        viewHolder.Amount.setText(modelMedicine.getQuantitybasedprice());
    }

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

    public final static class MedicineListItemViewHolder
            extends RecyclerView.ViewHolder {

        TextView medicineName;
        EditText medQty;
        TextView days;
        TextView Amount;
        CheckBox selectMe;

        public MedicineListItemViewHolder(View itemView) {
            super(itemView);
            medicineName = (TextView) itemView.findViewById(R.id.medicineName);
            medQty = (EditText) itemView.findViewById(R.id.medQty);
            days = (TextView) itemView.findViewById(R.id.days);
            Amount = (TextView) itemView.findViewById(R.id.amount);
            selectMe = (CheckBox) itemView.findViewById(R.id.selectMe);
        }
    }
}
Jeff
  • 12,555
  • 5
  • 33
  • 60
Ravi Kabra
  • 1,284
  • 1
  • 11
  • 17
  • Try [Expandablelistview](http://www.androidhive.info/2013/07/android-expandable-list-view-tutorial/), put the parent as the header and child as its view. – avinash Aug 17 '15 at 12:56

4 Answers4

60

I got this issue few days ago and finally solved it. All you have to do is @override the layout manager onMeasure function as below:

CustomLinearLayoutManager

public class CustomLinearLayoutManager extends LinearLayoutManager {

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

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

    public CustomLinearLayoutManager(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();
        } 
    }
}

You can just copy and paste this CustomLinearLayoutManager set it to your child RecyclerView like this:

RecyclerView.LayoutManager layoutManager = new CustomLinearLayoutManager(mContext);
holder.childRecyclerView.setLayoutManager(layoutManager);

Remember : Don't use CustomLinearLayoutManager in parent RecyclerView, or it will throw error.

Bugs Happen
  • 2,169
  • 4
  • 33
  • 59
pptang
  • 1,931
  • 1
  • 15
  • 18
  • 1
    Thanks pptang, its working fine, but the height is still an issue, if i try to scroll, some childs loose their visibility and also i have some edittexts in rows. So i need that their data should not be lost while scrolling.. – Ravi Kabra Aug 19 '15 at 08:00
  • But what if the nested RecyclerView has an unpredictable amount of elements? Then each time this RecyclerView shows up in it's parent RecyclerView's row, it will have a different height. – IgorGanapolsky Oct 21 '15 at 18:08
  • I know what your are saying. It still works in that case. Any issue occurred while your were trying? – pptang Oct 25 '15 at 05:15
  • @pptang i did as you suggested i lost items below the child recycler view what should i do ? – Vivek Pratap Singh Jun 28 '16 at 09:10
  • @VivekPratapSingh send me your code snippet and I can help fix it. My mailbox: donaldduck518@gmail.com – pptang Jul 05 '16 at 12:53
  • 2nd row of child recyclerview is not visible – Amey Jahagirdar Jan 24 '17 at 11:59
  • i have set as you said but getting this error. android.view.InflateException: Binary XML file line #8: Attempt to invoke virtual method 'boolean java.lang.String.equals(java.lang.Object)' on a null object reference Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'boolean java.lang.String.equals(java.lang.Object)' on a null object reference – Milon Jun 05 '17 at 07:21
  • how to link the recyclerview parent with the child? – Hass Jun 01 '21 at 15:09
16

By Android Support Library 23.2 of a support library version 23.2.0. So all WRAP_CONTENT should work correctly.

Please update version of a library in gradle file.

compile 'com.android.support:recyclerview-v7:23.2.0'

This allows a RecyclerView to size itself based on the size of its contents. This means that previously unavailable scenarios, such as using WRAP_CONTENT for a dimension of the RecyclerView, are now possible. You’ll find all built in LayoutManagers now support auto-measurement.

you’ll be required to call setAutoMeasureEnabled(true)

Below is the sample code

RecyclerView.LayoutManager layout = new LinearLayoutManager(context); 
layout.setAutoMeasureEnabled(true);

As setAutoMeasureEnabled is deprecated alternate solution This method was deprecated in API level 27.1.0. Implementors of LayoutManager should define whether or not it uses AutoMeasure by overriding isAutoMeasureEnabled().

Maheshwar Ligade
  • 6,709
  • 4
  • 42
  • 59
14

If I understand, you want a RecyclerView with RecyclerView rows. In this case I recommend you to use an expandable RecyclerView, using this lib. Just follow the expandable example within the link.

There are many more cool features in the same lib, like Drag and Drop, Swipeable rows... Watch this less than a minute example video.

You just have to add the lib to the dependencies in your gradle.build file, like following:

dependencies {
    compile 'com.h6ah4i.android.widget.advrecyclerview:advrecyclerview:0.7.4'
}

To be able to import the lib in your java files.

Shubhral
  • 334
  • 3
  • 15
Geraldo Neto
  • 3,670
  • 1
  • 30
  • 33
  • hey Geraldo I'm trying to use this lib and just noticed it says: "If you are using support library v23.0.x, please use v0.8.1 instead." In my dependencies I have `compile 'com.android.support:recyclerview-v7:23.0.0'`, `compile 'com.android.support:cardview-v7:23.0.0' ` and `compile 'com.android.support:appcompat-v7:23.0.0'` do I have to change this? – CommonSenseCode Nov 06 '15 at 13:46
0

Please note: you should not create new Adapter each time when calls onBindView(), you should do that once in the onCreateView().

The Best Way - to use any library, for example - RendererRecyclerViewAdapter

How to add a NestedRecyclerView:

Step 1: Add the ViewModel interface to your Modal_Product_List

public class Modal_Product_List implements ViewModel {

    String getMedicinename() { ... } //your method
    int getQuantity() { ... } //your method
    int getQuantitybasedprice() { ... } //your method
}

Step 2: Create a ViewBinder for the Modal_Product_List:

private ViewRenderer getModalProductViewRenderer() {
    return new ViewBinder<>(
            R.layout.item_row_medicine_productlist, //your child layout id
            Modal_Product_List.class //your child item class
            (model, finder, payloads) -> finder
                .setText(R.id.medicineName, model.getMedicinename())
                .setText(R.id.medQty, (String) model.getQuantity())
                .setText(R.id.amount, (String) model.getQuantitybasedprice())
                .setChecked(R.id.selectMe, ...)
    );
}

Step 3: Add the CompositeViewModel interface to PrescriptionModal or extend from DefaultCompositeModel:

public class PrescriptionModal extends DefaultCompositeViewModel {

    String getPrescriptionID() {...} //your method
    String getDoctorType() {...} //your method
    String getDoctorName() {...} //your method
    String getPatientName() {...} //your method

    @Override
    List<Modal_Product_List> getItems() { return yourProductItems; }
}

Step 4: Create a ViewBinder for the PrescriptionModal:

private ViewRenderer getModalProductViewRenderer() {
    return new CompositeViewBinder<>(
        R.layout.item_row_digitised_request, //your parent item layout
        R.id.lstAllMedicines, //your nested RecyclerView
        PrescriptionModal.class, //your parent item class
        (model, finder, payloads) -> finder
            //no need to set child items, it will set automatically
            .setText(R.id.prescnum, "Prescription:" + model.getPrescriptionID)
            .setText(R.id.doctorName, "Doctor:" + model.getDoctorName())
            .setText(R.id.doctorType, "Type:" + model.getDoctorType())
            .setText(R.id.patientName, "Patient:" + model.getPatientName())
    ).registerRenderer(getModalProductViewRenderer()); //register ModalProductViewRenderer
     .registerRenderer(...) //if you need you can create other renderer and register here
);

Step 5(Optional): If you need a Custom LayoutManager, then extend the CompositeViewBinder and override the createLayoutManager method and use it instead of CompositeViewBinder

public class CustomCompositeViewBinder extends CompositeViewBinder {

    //...

    @Override
    protected RecyclerView.LayoutManager createLayoutManager() {
        return new MyLinearLayoutManager(getContext(), VERTICAL, false);
    }
}

Step 6: Initialize RendererRecyclerViewAdapter and register renderer:

RendererRecyclerViewAdapter adapter = new RendererRecyclerViewAdapter(getContext());
recyclerView.setAdapter(adapter);

adapter.registerRenderer(getModalProductViewRenderer());
adapter.registerRenderer(...); //if you need you can create other renderers

adapter.setItems(getPrescriptionListModal());

It is really short and simple way to add a Nested RecyclerView

Vitaly
  • 549
  • 1
  • 6
  • 14