[英]Android RecyclerView items shuffles on scroll
Items/cells of my multiple view type RecyclerView Adapter get shuffles on the scroll.我的多视图类型 RecyclerView 适配器的项目/单元格在滚动上随机播放。 I go through all below possible solutions but none of them working.我通过以下所有可能的解决方案 go 但没有一个工作。
1. 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我的适配器 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
.我研究过这种类型的RecyclerView
,这里是DynamicAdapter
class 与多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:)如果您有任何疑问,请告诉我,Happy Coding:)
Make the RecyclerView ViewModel non-recyclable.使 RecyclerView ViewModel 不可回收。 I think the recyclerview is set to refresh by default, therefore, shuffling the items For example我认为recyclerview默认设置为刷新,因此,打乱项目例如
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).此外,您可以在填充项目之前使用 Collection.sort(items) 实现排序方法,因此即使视图被回收,项目仍然保持排序(例如按 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 增加缓存的一种方法是:
recyclerView.setItemViewCacheSize(20)
You can also get extra space with the help of LinearLayoutManager's method calculateExtraLayoutSpace .您还可以借助 LinearLayoutManager 的方法calculateExtraLayoutSpace获得额外空间。 I've added links to the docs.我已经添加了文档的链接。
Alternatively you can use:或者,您可以使用:
setIsRecyclable(false);
But it beats the purpose of using RecyclerView.但这超出了使用 RecyclerView 的目的。
You should not use hashCode()
as an ID as it is not guaranteed to be unique.您不应该使用hashCode()
作为 ID,因为它不能保证是唯一的。 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.请尝试为每个项目实现一个唯一 ID,并在您的getItemId()
方法中使用它。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.