Reputation: 1284
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.
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);
}
}
}
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);
}
}
}
Upvotes: 56
Views: 47359
Reputation: 1921
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.
Upvotes: 59
Reputation: 6855
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()
.
Upvotes: 16
Reputation: 559
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
Upvotes: 0
Reputation: 4040
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.
Upvotes: 13