Android RecyclerView 项目在滚动时随机播放
Posted
技术标签:
【中文标题】Android RecyclerView 项目在滚动时随机播放【英文标题】:Android RecyclerView items shuffles on scroll 【发布时间】:2020-09-25 11:33:59 【问题描述】:我的多视图类型 RecyclerView 适配器的项目/单元格在滚动上随机播放。我浏览了以下所有可能的解决方案,但没有一个有效。
1.
@Override
public long getItemId(int position)
return mDataset.get(position).hashCode();
or
return mDataset.get(position).getBaseFormElementId();
setHasStableIds(true);
-
增加了 RecyclerView 的缓存
如果有人有任何解决方案,请分享。
我的适配器类
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);
【问题讨论】:
能否请您显示您的适配器的代码以及它如何在回收站视图中使用?你使用 StaggeredGridLayoutManager 吗? 能否请您在此处发布您的适配器文件,以便我检查您做错了什么? @Rahul 已更新,请立即查看。 @AkhileshMani 我已经发布了答案,如果您有任何不明白的地方请检查并告诉我。 @ZSergei 我使用了 LinearLayoutManager... 我刚刚在 RV 中做了 setAdapter。 【参考方案1】:我研究过这种类型的RecyclerView
,这是与多个view-type
一起使用的DynamicAdapter
类。我希望您对如何使用它有所了解。
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)
如果您有任何疑问,请告诉我,Happy Coding :)
【讨论】:
【参考方案2】:使 RecyclerView ViewModel 不可回收。我认为 recyclerview 默认设置为刷新,因此,洗牌项目 例如
class YourViewModel extends RecyclerView.ViewHolder
YourViewModel (@NonNull View view)
super(view);
// Add the line below
this.setIsRecyclable(false);
此外,您可以在填充项目之前使用 Collection.sort(items) 实现排序方法,因此即使视图被回收,项目仍然保持排序(例如按 id)。
【讨论】:
setIsRecyclable(false) - 我做不到。【参考方案3】:您不应该使用hashCode()
作为ID,因为它不能保证是唯一的!
我的猜测是您的许多项目都返回相同的哈希值。
请尝试为每个项目实现一个唯一 ID,并在您的 getItemId()
方法中使用它。
【讨论】:
【参考方案4】:你可以试试:
@Override
public long getItemId(int position)
return position;
也覆盖:
@Override
public int getItemViewType(int position)
return position;
为recyclerview增加缓存的一种方法是:
recyclerView.setItemViewCacheSize(20)
您还可以借助 LinearLayoutManager 的方法calculateExtraLayoutSpace 获得额外的空间。我已添加到文档的链接。
您也可以使用:
setIsRecyclable(false);
但这超出了使用 RecyclerView 的目的。
【讨论】:
setIsRecyclable(false) - 我做不到。以上是关于Android RecyclerView 项目在滚动时随机播放的主要内容,如果未能解决你的问题,请参考以下文章
Android - 网格中项目之间的 RecyclerView 间距
在recyclerview项目android中更改视图之间的焦点
Android Recyclerview项目Textview在某些项目中不会显示