Items/cells of my multiple view type RecyclerView Adapter get shuffles on the scroll. I go through all below possible solutions but none of them working.
1.
@Override
public long getItemId(int position) {
return mDataset.get(position).hashCode();
or
return mDataset.get(position).getBaseFormElementId();
}
setHasStableIds(true);
Please share if anyone has any solution.
My Adapter Class
public class FormAdapter extends RecyclerView.Adapter<BaseViewHolder> {
private Context mContext;
private List<BaseFormElement> mDataset;
public FormAdapter(Context context) {
mContext = context;
mDataset = new ArrayList<>();
setHasStableIds(true);
}
public List<BaseFormElement> getDataset() {
return mDataset;
}
public OnFormElementValueChangedListener getValueChangeListener() {
return mListener;
}
@Override
public int getItemCount() {
return mDataset.size();
}
@Override
public int getItemViewType(int position) {
return mDataset.get(position).getType();
}
@Override
public long getItemId(int position) {
//return super.getItemId(position);
return mDataset.get(position).getBaseFormElementId();
}
@NonNull
@Override
public BaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
// get layout based on header or element type
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
View v;
switch (viewType) {
case BaseFormElement.TYPE_HEADER:
v = inflater.inflate(R.layout.form_element_header, parent, false);
return new FormElementHeader(v);
case BaseFormElement.TYPE_EDIT_TEXT:
v = inflater.inflate(R.layout.form_element, parent, false);
return new FormElementEditTextViewHolder(v, new FormItemEditTextListener(this));
case BaseFormElement.TYPE_PICKER_DATE:
v = inflater.inflate(R.layout.form_element, parent, false);
return new FormElementPickerDateViewHolder(v, mContext, this);
case BaseFormElement.TYPE_PICKER_TIME:
v = inflater.inflate(R.layout.form_element, parent, false);
return new FormElementPickerTimeViewHolder(v, mContext, this);
case BaseFormElement.TYPE_PICKER_SINGLE:
v = inflater.inflate(R.layout.form_element, parent, false);
return new FormElementPickerSingleViewHolder(v, mContext, this, new FormItemEditTextListener(this));
case BaseFormElement.TYPE_PICKER_MULTI:
v = inflater.inflate(R.layout.formelementlabel, parent, false);
return new FormElementPickerMultiViewHolder(v, mContext, this);
case BaseFormElement.TYPE_IMAGE_REMARKS:
v = inflater.inflate(R.layout.formelementlabel, parent, false);
return new FormElementImageWithRemarksViewHolder(v, mContext, this);
case BaseFormElement.TYPE_SWITCH:
v = inflater.inflate(R.layout.form_element_switch, parent, false);
return new FormElementSwitchViewHolder(v, mContext, this);
case BaseFormElement.TYPE_SEGMENT:
v = inflater.inflate(R.layout.form_element_switch, parent, false);
return new FormElementSwitchViewHolder(v, mContext, this);
case BaseFormElement.TYPE_LABEL:
v = inflater.inflate(R.layout.formelementlabel, parent, false);
return new FormElementLabelViewHolder(v, clicklistner);
case BaseFormElement.TYPE_IMAGE:
v = inflater.inflate(R.layout.formelementlabel, parent, false);
return new FromElementImageViewHolder(v, mContext, this);
case BaseFormElement.TYPE_VIDEO:
v = inflater.inflate(R.layout.formelementlabel, parent, false);
return new FormElementVideoViewHolder(v, mContext, this);
case BaseFormElement.DIALOG_LIST:
v = inflater.inflate(R.layout.form_element_mmv, parent, false);
return new FormElementDialogListViewHolder(v, mContext, this);
case BaseFormElement.TYPE_SLIDER:
v = inflater.inflate(R.layout.formelementlabel, parent, false);
return new FormElementSliderViewHolder(v, mContext, this);
default:
v = inflater.inflate(R.layout.form_element, parent, false);
return new FormElementEditTextViewHolder(v, new FormItemEditTextListener(this));
}
}
@Override
public void onBindViewHolder(BaseViewHolder holder, final int position) {
// gets current object
BaseFormElement currentObject = mDataset.get(position);
holder.bind(position, currentObject, mContext);
}
}
I have worked on this type of RecyclerView
and here is the DynamicAdapter
class used with multiple view-type
. I hope you get some clarity on how to use it.
public class DynamicAdapter extends RecyclerView.Adapter <BaseViewHolder> {
private static final String TAG = "DynamicAdapter";
private static final int VIEW_EMPTY = 0;
private static final int VIEW_EDIT_TEXT = 1;
private static final int VIEW_SIGNATURE = 2;
private static final int VIEW_UPLOAD = 3;
private static final int VIEW_DATE_TIME = 4;
private static final int VIEW_DATE_RANGE = 5;
private static final int VIEW_DATE = 6;
private static final int VIEW_DESCRIPTION = 7;
private static final int VIEW_UNDEFINED = 8;
ArrayList <FormData> formDataList;
private Context context;
private AdapterListener adapterListener;
public DynamicAdapter(ArrayList <FormData > formDataList) {
this.formDataList = formDataList;
}
public void setAdapterListener(AdapterListener adapterListener) {
this.adapterListener = adapterListener;
}
@NonNull
@Override
public BaseViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
this.context = parent.getContext();
switch (viewType) {
case VIEW_EDIT_TEXT:
ItemDynamicFormEdittextBinding edittextBinding = ItemDynamicFormEdittextBinding
.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new EditTextViewHolder(edittextBinding);
case VIEW_NUMBER:
ItemDynamicFormNumberBinding numberBinding = ItemDynamicFormNumberBinding
.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new NumberViewHolder(numberBinding);
case VIEW_EMAIL:
ItemDynamicFormEmailBinding emailBinding = ItemDynamicFormEmailBinding
.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new EmailViewHolder(emailBinding);
case VIEW_UPLOAD:
ItemDynamicFormUploadBinding uploadBinding = ItemDynamicFormUploadBinding
.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new UploadViewHolder(uploadBinding);
case VIEW_DATE_RANGE:
ItemDynamicFormDateRangeBinding rangeBinding = ItemDynamicFormDateRangeBinding
.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new DateRangeViewHolder(rangeBinding);
case VIEW_DATE_TIME:
ItemDynamicFormDateTimeBinding dateTimeBinding = ItemDynamicFormDateTimeBinding
.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new DateTimeViewHolder(dateTimeBinding);
case VIEW_DATE:
ItemDynamicFormDateBinding dateBinding = ItemDynamicFormDateBinding
.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new DateViewHolder(dateBinding);
case VIEW_DESCRIPTION:
ItemDynamicFormDescriptionBinding descriptionBinding = ItemDynamicFormDescriptionBinding
.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new DescriptionViewHolder(descriptionBinding);
case VIEW_UNDEFINED:
ItemDynamicFormUnknownBinding unknownBinding = ItemDynamicFormUnknownBinding
.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new UnknownViewHolder(unknownBinding);
default:
ItemDynamicFormEmptyViewBinding emptyViewBinding = ItemDynamicFormEmptyViewBinding
.inflate(LayoutInflater.from(parent.getContext()), parent, false);
return new EmptyViewHolder(emptyViewBinding);
}
}
@Override
public void onBindViewHolder(@NonNull BaseViewHolder holder, final int position) {
holder.onBind(position);
}
@Override
public int getItemCount() {
return formDataList.size();
}
@Override
public int getItemViewType(int position) {
if (formDataList != null && !formDataList.isEmpty()) {
if (formDataList.get(position) != null &&
formDataList.get(position).getType() != null) {
switch (formDataList.get(position).getType()) {
case TEXT:
return VIEW_EDIT_TEXT;
case CAMERA:
return VIEW_CAMERA;
case TOGGLE:
return VIEW_TOGGLE;
case NUMBER:
return VIEW_NUMBER;
case EMAIL:
return VIEW_EMAIL;
case DATE_RANGE:
return VIEW_DATE_RANGE;
case DATE_TIME:
return VIEW_DATE_TIME;
default:
return VIEW_UNDEFINED;
}
} else {
return VIEW_UNDEFINED;
}
} else {
return VIEW_EMPTY;
}
}
/**
* Class used to handle all the text fields for email,text & number.
*/
private class EditTextViewHolder extends BaseViewHolder {
ItemDynamicFormEdittextBinding mBinding;
EditTextViewHolder(ItemDynamicFormEdittextBinding binding) {
super(binding.getRoot());
Log.e(TAG, "EditTextViewHolder: ------------>>");
this.mBinding = binding;
mBinding.edDynamicFormText.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
formDataList.get(getAdapterPosition()).setEnteredValue(charSequence.toString());
}
@Override
public void afterTextChanged(Editable editable) {
}
});
}
@Override
public void onBind(int position) {
final FormData formData = formDataList.get(position);
FormEdittextViewModel emptyItemViewModel = new FormEdittextViewModel(formData);
mBinding.setViewModel(emptyItemViewModel);
// Immediate Binding
// When a variable or observable changes, the binding will be scheduled to change before
// the next frame. There are times, however, when binding must be executed immediately.
// To force execution, use the executePendingBindings() method.
mBinding.executePendingBindings();
}
}
/**
* Class used to handle all the text fields for email, text & number.
*/
private class NumberViewHolder extends BaseViewHolder {
ItemDynamicFormNumberBinding mBinding;
NumberViewHolder(ItemDynamicFormNumberBinding binding) {
super(binding.getRoot());
this.mBinding = binding;
mBinding.edDynamicFormNumber.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
formDataList.get(getAdapterPosition()).setEnteredValue(charSequence.toString());
}
@Override
public void afterTextChanged(Editable editable) {
}
});
}
@Override
public void onBind(int position) {
final FormData formData = formDataList.get(position);
mBinding.setViewModel(new FormNumberViewModel(formData));
// Immediate Binding
// When a variable or observable changes, the binding will be scheduled to change before
// the next frame. There are times, however, when binding must be executed immediately.
// To force execution, use the executePendingBindings() method.
mBinding.executePendingBindings();
}
}
/**
* Class used to handle all the text fields for email, text & number.
*/
private class EmailViewHolder extends BaseViewHolder {
ItemDynamicFormEmailBinding mBinding;
EmailViewHolder(ItemDynamicFormEmailBinding binding) {
super(binding.getRoot());
this.mBinding = binding;
mBinding.edDynamicFormEmail.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
formDataList.get(getAdapterPosition()).setEnteredValue(charSequence.toString());
}
@Override
public void afterTextChanged(Editable editable) {
}
});
}
@Override
public void onBind(int position) {
final FormData formData = formDataList.get(position);
//val radioButton = mBinding.root.findViewById(R.id.radioButtton) as RadioButton
FormEmailViewModel emptyItemViewModel = new FormEmailViewModel(formData);
mBinding.setViewModel(emptyItemViewModel);
// Immediate Binding
// When a variable or observable changes, the binding will be scheduled to change before
// the next frame. There are times, however, when binding must be executed immediately.
// To force execution, use the executePendingBindings() method.
mBinding.executePendingBindings();
}
}
/**
* Class used to handle all the text fields for description only.
*/
private class DescriptionViewHolder extends BaseViewHolder {
ItemDynamicFormDescriptionBinding mBinding;
DescriptionViewHolder(ItemDynamicFormDescriptionBinding binding) {
super(binding.getRoot());
this.mBinding = binding;
mBinding.edDynamicFormDesc.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
formDataList.get(getAdapterPosition()).setEnteredValue(charSequence.toString());
}
@Override
public void afterTextChanged(Editable editable) {
}
});
}
@Override
public void onBind(int position) {
final FormData formData = formDataList.get(position);
FormDescriptionViewModel emptyItemViewModel = new FormDescriptionViewModel(formData);
mBinding.setViewModel(emptyItemViewModel);
// Immediate Binding
// When a variable or observable changes, the binding will be scheduled to change before
// the next frame. There are times, however, when binding must be executed immediately.
// To force execution, use the executePendingBindings() method.
mBinding.executePendingBindings();
}
}
/**
* Class used to select pic form device and upload to server.
*/
private class UploadViewHolder extends BaseViewHolder
implements FormUploadViewModel.UploadListener {
private ItemDynamicFormUploadBinding mBinding;
private UploadViewHolder(ItemDynamicFormUploadBinding mBinding) {
super(mBinding.getRoot());
this.mBinding = mBinding;
}
@Override
public void onBind(int position) {
final FormData data = formDataList.get(position);
FormUploadViewModel uploadViewModel = new FormUploadViewModel(data, this);
mBinding.setViewModel(uploadViewModel);
// Immediate Binding
// When a variable or observable changes, the binding will be scheduled to change before
// the next frame. There are times, however, when binding must be executed immediately.
// To force execution, use the executePendingBindings() method.
mBinding.executePendingBindings();
}
@Override
public void onUploadClick(@NonNull FormData formData) {
adapterListener.onUploadPic(getAdapterPosition(), formData);
}
}
/**
* Class used to pick date and time.
*/
private class DateTimeViewHolder extends BaseViewHolder
implements FormDateTimeViewModel.DateTimeListener {
ItemDynamicFormDateTimeBinding mBinding;
int mYear;
int mMonth;
int mDay;
int mHour;
int mMinute;
int mSecond;
FormDateTimeViewModel dateTimeViewModel;
FormData data;
DateTimeViewHolder(ItemDynamicFormDateTimeBinding mBinding) {
super(mBinding.getRoot());
this.mBinding = mBinding;
}
@Override
public void onBind(int position) {
data = formDataList.get(position);
dateTimeViewModel = new FormDateTimeViewModel(data, this);
mBinding.setViewModel(dateTimeViewModel);
// Immediate Binding
// When a variable or observable changes, the binding will be scheduled to change before
// the next frame. There are times, however, when binding must be executed immediately.
// To force execution, use the executePendingBindings() method.
mBinding.executePendingBindings();
}
@Override
public void dateClick() {
Calendar calendar = Calendar.getInstance();
mHour = calendar.get(Calendar.HOUR_OF_DAY);
mMinute = calendar.get(Calendar.MINUTE);
mSecond = 0;
mYear = calendar.get(Calendar.YEAR);
mMonth = calendar.get(Calendar.MONTH);
mDay = calendar.get(Calendar.DAY_OF_MONTH);
CommonUtils.openDatePicker(context, mYear, mMonth, mDay,
0, 0, (view, year, monthOfYear, dayOfMonth) -> {
String selectedDate = dayOfMonth + "-" + (monthOfYear + 1) + "-" + year;
Calendar cal = Calendar.getInstance();
cal.set(Calendar.YEAR, year);
cal.set(Calendar.MONTH, monthOfYear);
cal.set(Calendar.DAY_OF_MONTH, dayOfMonth);
/*cal.set(Calendar.HOUR_OF_DAY, mHour);
cal.set(Calendar.MINUTE, mMinute);
cal.set(Calendar.SECOND, 0);*/
dateTimeViewModel.getDate().set(DateTimeUtil.getParsedDate(cal.getTimeInMillis()));
data.setEnteredValue(cal.getTimeInMillis() + "");
});
}
@Override
public void timeClick() {
Calendar calendar = Calendar.getInstance();
mHour = calendar.get(Calendar.HOUR_OF_DAY);
mMinute = calendar.get(Calendar.MINUTE);
CommonUtils.openTimePicker(context, mHour, mMinute,
(view, hourOfDay, minute) -> {
Calendar cal = Calendar.getInstance();
cal.set(Calendar.YEAR, mYear);
cal.set(Calendar.MONTH, mMonth);
cal.set(Calendar.DAY_OF_MONTH, mDay);
cal.set(Calendar.HOUR_OF_DAY, hourOfDay);
cal.set(Calendar.MINUTE, minute);
cal.set(Calendar.SECOND, 0);
dateTimeViewModel.getTime().set(DateTimeUtil.getParsedTime(cal.getTimeInMillis()));
data.setEnteredValue(cal.getTimeInMillis() + "");
});
}
}
/**
* Class used to pick date from & to .
*/
private class DateRangeViewHolder extends BaseViewHolder
implements FormDateRangeViewModel.DateRangeListener {
ItemDynamicFormDateRangeBinding mBinding;
FormDateRangeViewModel uploadViewModel;
DateRangeViewHolder(ItemDynamicFormDateRangeBinding mBinding) {
super(mBinding.getRoot());
this.mBinding = mBinding;
}
@Override
public void onBind(int position) {
FormData data = formDataList.get(position);
uploadViewModel = new FormDateRangeViewModel(data, this);
mBinding.setViewModel(uploadViewModel);
// Immediate Binding
// When a variable or observable changes, the binding will be scheduled to change before
// the next frame. There are times, however, when binding must be executed immediately.
// To force execution, use the executePendingBindings() method.
mBinding.executePendingBindings();
}
@Override
public void dateViewClick(@NotNull View view) {
// Get Current Date
Calendar c = Calendar.getInstance();
int mYear = c.get(Calendar.YEAR);
int mMonth = c.get(Calendar.MONTH);
int mDay = c.get(Calendar.DAY_OF_MONTH);
// int mHour = c.get(Calendar.HOUR_OF_DAY);
// int mMin = c.get(Calendar.MINUTE);
CommonUtils.openDatePicker(context, mYear, mMonth, mDay,
c.getTimeInMillis(), 0, (view1, year, monthOfYear, dayOfMonth) -> {
//String selectedDate = dayOfMonth + "-" + (monthOfYear + 1) + "-" + year;
Calendar cal = Calendar.getInstance();
cal.set(Calendar.YEAR, year);
cal.set(Calendar.MONTH, monthOfYear);
cal.set(Calendar.DAY_OF_MONTH, dayOfMonth);
switch (view.getId()) {
case R.id.tvDateRange1:
uploadViewModel.getDate1().set(DateTimeUtil.getParsedDate(cal.getTimeInMillis()));
formDataList.get(getAdapterPosition()).setMaxRange(cal.getTimeInMillis());
break;
case R.id.tvDateRange2:
uploadViewModel.getDate2().set(DateTimeUtil.getParsedDate(cal.getTimeInMillis()));
formDataList.get(getAdapterPosition()).setMinRange(cal.getTimeInMillis());
break;
}
});
}
}
/**
* Class used to pick date
*/
private class DateViewHolder extends BaseViewHolder
implements FormDateViewModel.DateListener {
ItemDynamicFormDateBinding mBinding;
FormDateViewModel emptyItemViewModel;
int mYear, mMonth, mDay;
DateViewHolder(ItemDynamicFormDateBinding mBinding) {
super(mBinding.getRoot());
this.mBinding = mBinding;
}
@Override
public void onBind(int position) {
FormData data = formDataList.get(position);
emptyItemViewModel = new FormDateViewModel(data, this);
mBinding.setViewModel(emptyItemViewModel);
// Immediate Binding
// When a variable or observable changes, the binding will be scheduled to change before
// the next frame. There are times, however, when binding must be executed immediately.
// To force execution, use the executePendingBindings() method.
mBinding.executePendingBindings();
}
@Override
public void onDateClick() {
Calendar calendar = Calendar.getInstance();
mYear = calendar.get(Calendar.YEAR);
mMonth = calendar.get(Calendar.MONTH);
mDay = calendar.get(Calendar.DAY_OF_MONTH);
CommonUtils.openDatePicker(context, mYear, mMonth, mDay,
0, 0, (view, year, monthOfYear, dayOfMonth) -> {
// String selectedDate = dayOfMonth + "-" + (monthOfYear + 1) + "-" + year;
Calendar c = Calendar.getInstance();
c.set(Calendar.YEAR, year);
c.set(Calendar.MONTH, monthOfYear);
c.set(Calendar.DAY_OF_MONTH, dayOfMonth);
formDataList.get(getAdapterPosition()).setMaxRange(c.getTimeInMillis());
emptyItemViewModel.getDate().set(DateTimeUtil.getParsedDate(c.getTimeInMillis()));
});
}
}
/**
* If hashMap is empty show empty view
*/
private class EmptyViewHolder extends BaseViewHolder
implements FormEmptyItemViewModel.ClickListener {
private ItemDynamicFormEmptyViewBinding mBinding;
EmptyViewHolder(ItemDynamicFormEmptyViewBinding binding) {
super(binding.getRoot());
this.mBinding = binding;
}
@Override
public void onBind(int position) {
FormEmptyItemViewModel emptyItemViewModel = new FormEmptyItemViewModel(this);
mBinding.setViewModel(emptyItemViewModel);
}
}
/**
* If view type is not handled then show this view
*/
private class UnknownViewHolder extends BaseViewHolder {
UnknownViewHolder(ItemDynamicFormUnknownBinding unknownBinding) {
super(unknownBinding.getRoot());
}
@Override
public void onBind(int position) {
}
}
If you have any doubt let me know, Happy Coding:)
Make the RecyclerView ViewModel non-recyclable. I think the recyclerview is set to refresh by default, therefore, shuffling the items For example
class YourViewModel extends RecyclerView.ViewHolder {
YourViewModel (@NonNull View view) {
super(view);
// Add the line below
this.setIsRecyclable(false);
}
}
Also, you could implement a sorting method using Collection.sort(items) before the items are populated, so even when the view gets recycled, the items still remain sorted (eg. by id).
You may try:
@Override
public long getItemId(int position) {
return position;
}
Also override:
@Override
public int getItemViewType(int position) {
return position;
}
One way to increase the cache for recyclerview is:
recyclerView.setItemViewCacheSize(20)
You can also get extra space with the help of LinearLayoutManager's method calculateExtraLayoutSpace . I've added links to the docs.
Alternatively you can use:
setIsRecyclable(false);
But it beats the purpose of using RecyclerView.
You should not use hashCode()
as an ID as it is not guaranteed to be unique. My guess is that many of your items are returning the same hash. Please try implementing a unique ID for each item and use the it in your getItemId()
method.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.