onBindViewHolder(VH, position) 在使用 notifyItemChanged() 时不正确地调用旧的和新的 ViewHolder 副本
onBindViewHolder(VH, position) improperly calling the old and new ViewHolder copies when using notifyItemChanged()
EDIT2: 我在 holder.showTaskRecyclerView
是 false
的情况下明确表示要隐藏视图,以便明确地覆盖两者 true
和 onBindViewHolder
中的 false
个案例。我仍然有同样的问题。
EDIT1: 我应该补充一点,如果我使用 notifyDataSetChanged()
而不是 notifyItemChanged()
,tasksRecyclerView
的显示和隐藏效果很好,但这会禁用动画并且成本更高。
我的 ViewHolder 中有一个名为 tasksRecyclerView
的 RecyclerView,它应该在单击视图时显示和隐藏。 (这是主 RecyclerView 内部的 RecyclerView,可以这么说。):
public class ViewHolder extends RecyclerView.ViewHolder {
public ImageView routineStateImgView;
public TextView alarmTextView;
public TextView routineNameTextView;
public RecyclerView tasksRecyclerView;
public boolean showTaskRecyclerView = false;
public ViewHolder(View routineItemView){
super(routineItemView);
routineItemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//toggle showing TaskRecyclerView
showTaskRecyclerView = !showTaskRecyclerView;
notifyItemChanged(getAdapterPosition());
}
});
routineStateImgView = (ImageView) routineItemView.findViewById(R.id.routineStateImgView);
alarmTextView = (TextView) routineItemView.findViewById(R.id.alarmTextView);
routineNameTextView = (TextView) routineItemView.findViewById(R.id.routineNameTextView);
tasksRecyclerView = (RecyclerView) routineItemView.findViewById(R.id.tasksRecyclerView);
}
public ImageView getRoutineStateImgView() {
return routineStateImgView;
}
public TextView getAlarmTextView(){
return alarmTextView;
}
public TextView getRoutineNameTextView(){
return routineNameTextView;
}
public RecyclerView getTasksRecyclerView(){
return tasksRecyclerView;
}
}
目前代码中的 onClick 设置为切换 ViewHolder 中定义的变量 (showTaskRecyclerView
),然后调用 notifyItemChanged(getAdapterPosition())
以使用动画更新项目更改。
调用 OnBindViewHolder 时,它会检查切换变量的状态(showTaskRecyclerView
,再次)并根据变量隐藏或取消隐藏 RecyclerView。您可以在代码中的 Log.d 方法行之后的代码中看到此逻辑:
public void onBindViewHolder(RoutinesRecyclerViewAdapter.ViewHolder holder, int position) {
//Replace contents of view with data from item in adapterRoutinesList at position.
Routine routine = adapterRoutinesList.get(position);
TasksRecyclerViewAdapter mTasksRecyclerViewAdapter = null;
if(routine == null){
holder.getRoutineStateImgView().setImageResource(android.R.color.transparent);
holder.getAlarmTextView().setText("");
holder.getRoutineNameTextView().setText("");
holder.getTasksRecyclerView().setVisibility(View.GONE);
}else{
DateFormat dayTimeFormat = new SimpleDateFormat("HH:mm", Locale.US);
String dayTimeString = dayTimeFormat.format(routine.getWakeupTime().getTime());
holder.getAlarmTextView().setText(dayTimeString);
holder.getRoutineNameTextView().setText(routine.getName());
if(routine.getEnableRoutine()) {
holder.getRoutineStateImgView().setImageResource(R.drawable.check_mark);
}
else{
holder.getRoutineStateImgView().setImageResource(R.drawable.x_mark);
}
Log.d(TAG, "position: " + position + " holder: " + holder + " show?: " + holder.showTaskRecyclerView);
if(holder.showTaskRecyclerView) {
TasksManager mTasksManager = adapterTasksListCache.get(routine.getId());
if (mTasksManager == null) {
mTasksManager = new TasksManager(routine.getId());
mTasksManager.readTasksFromDisk(mContext);
adapterTasksListCache.put(routine.getId(), mTasksManager);
}
mTasksRecyclerViewAdapter = adapterTasksViewCache.get(routine.getId());
if (mTasksRecyclerViewAdapter == null) {
mTasksRecyclerViewAdapter = new TasksRecyclerViewAdapter(
mContext,
position,
mTasksManager.getTasksListFromCache(),
mRequestImageCaptureCallBack,
mTaskEditTextListener);
adapterTasksViewCache.put(routine.getId(), mTasksRecyclerViewAdapter);
}
RecyclerView.LayoutManager mTaskLayoutManager = new LinearLayoutManager(mContext);
holder.getTasksRecyclerView().setLayoutManager(mTaskLayoutManager);
holder.getTasksRecyclerView().setAdapter(mTasksRecyclerViewAdapter);
holder.getTasksRecyclerView().setHasFixedSize(true);
holder.getTasksRecyclerView().setVisibility(View.VISIBLE);
}
else{
holder.getTasksRecyclerView().setVisibility(View.GONE);
}
}
}
我希望看到 RecyclerView 在每次点击时隐藏或取消隐藏。但是我只在每点击 2 次后看到 hide/unhide 行为。
这是点击 5 次后 Log.d 的输出:
04-16 21:33:37.026 13522-13522/co.edu.javeriana.faros D/RoutinesViewAdapter: position: 0 holder: ViewHolder{263b1503 position=0 id=-1, oldPos=-1, pLpos:-1 no parent} show?: false
04-16 21:33:42.626 13522-13522/co.edu.javeriana.faros D/RoutinesViewAdapter: position: 0 holder: ViewHolder{883eb98 position=0 id=-1, oldPos=-1, pLpos:-1 no parent} show?: false
04-16 21:33:44.511 13522-13522/co.edu.javeriana.faros D/RoutinesViewAdapter: position: 0 holder: ViewHolder{263b1503 position=0 id=-1, oldPos=-1, pLpos:-1 no parent} show?: true
04-16 21:33:46.274 13522-13522/co.edu.javeriana.faros D/RoutinesViewAdapter: position: 0 holder: ViewHolder{883eb98 position=0 id=-1, oldPos=-1, pLpos:-1 no parent} show?: true
04-16 21:33:47.938 13522-13522/co.edu.javeriana.faros D/RoutinesViewAdapter: position: 0 holder: ViewHolder{263b1503 position=0 id=-1, oldPos=-1, pLpos:-1 no parent} show?: false
04-16 21:41:52.756 13522-13522/co.edu.javeriana.faros D/RoutinesViewAdapter: position: 0 holder: ViewHolder{883eb98 position=0 id=-1, oldPos=-1, pLpos:-1 no parent} show?: false
我的解释是,使用 notifyItemChanged 会提示使用同一 ViewHolder 的 2 个副本来实现过渡动画目的(一个具有视图的旧状态,一个具有新状态)。但是,带有变量的副本在旧副本中发生了错误的更改。我假设我对 notifyItemChanged 的工作原理有错误的理解,所以我来这里寻求帮助:/
此外,我不知道是什么原因导致我点击得非常快:如果我点击得足够快(大约不到半秒),我看不到任何变化
logcat 显示如下:
04-16 21:43:54.597 13522-13522/co.edu.javeriana.faros D/RoutinesViewAdapter: position: 0 holder: ViewHolder{883eb98 position=0 id=-1, oldPos=-1, pLpos:-1 no parent} show?: true
04-16 21:43:54.862 13522-13522/co.edu.javeriana.faros D/RoutinesViewAdapter: position: 0 holder: ViewHolder{263b1503 position=0 id=-1, oldPos=-1, pLpos:-1 scrap [changeScrap] tmpDetached not recyclable(1) no parent} show?: true
04-16 21:43:55.387 13522-13522/co.edu.javeriana.faros D/RoutinesViewAdapter: position: 0 holder: ViewHolder{883eb98 position=0 id=-1, oldPos=-1, pLpos:-1 no parent} show?: true
04-16 21:43:55.668 13522-13522/co.edu.javeriana.faros D/RoutinesViewAdapter: position: 0 holder: ViewHolder{263b1503 position=0 id=-1, oldPos=-1, pLpos:-1 scrap [changeScrap] tmpDetached not recyclable(1) no parent} show?: true
04-16 21:43:55.949 13522-13522/co.edu.javeriana.faros D/RoutinesViewAdapter: position: 0 holder: ViewHolder{883eb98 position=0 id=-1, oldPos=-1, pLpos:-1 no parent} show?: true
04-16 21:43:56.189 13522-13522/co.edu.javeriana.faros D/RoutinesViewAdapter: position: 0 holder: ViewHolder{263b1503 position=0 id=-1, oldPos=-1, pLpos:-1 scrap [changeScrap] tmpDetached not recyclable(1) no parent} show?: true
04-16 21:43:56.479 13522-13522/co.edu.javeriana.faros D/RoutinesViewAdapter: position: 0 holder: ViewHolder{883eb98 position=0 id=-1, oldPos=-1, pLpos:-1 no parent} show?: true
有什么想法吗?
您需要实现 holder.showTaskRecyclerView
为 false
时的条件逻辑。
View
保持它们的状态,因此您必须显式修改它们。将一些逻辑隐藏在那里并检查它是否有效。
我通过在 ViewHolder
之外定义一个 ListArray
并为适配器中的每个项目定义一个 showTaskRecyclerView
来解决这个问题。
这样,旧的和新的 ViewHolder
看起来是一样的,每个项目单个 showTaskRecyclerView
而不是 2 个不同步的 showTaskRecyclerView
每个项目都位于两个 [=11] =].
EDIT2: 我在 holder.showTaskRecyclerView
是 false
的情况下明确表示要隐藏视图,以便明确地覆盖两者 true
和 onBindViewHolder
中的 false
个案例。我仍然有同样的问题。
EDIT1: 我应该补充一点,如果我使用 notifyDataSetChanged()
而不是 notifyItemChanged()
,tasksRecyclerView
的显示和隐藏效果很好,但这会禁用动画并且成本更高。
我的 ViewHolder 中有一个名为 tasksRecyclerView
的 RecyclerView,它应该在单击视图时显示和隐藏。 (这是主 RecyclerView 内部的 RecyclerView,可以这么说。):
public class ViewHolder extends RecyclerView.ViewHolder {
public ImageView routineStateImgView;
public TextView alarmTextView;
public TextView routineNameTextView;
public RecyclerView tasksRecyclerView;
public boolean showTaskRecyclerView = false;
public ViewHolder(View routineItemView){
super(routineItemView);
routineItemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//toggle showing TaskRecyclerView
showTaskRecyclerView = !showTaskRecyclerView;
notifyItemChanged(getAdapterPosition());
}
});
routineStateImgView = (ImageView) routineItemView.findViewById(R.id.routineStateImgView);
alarmTextView = (TextView) routineItemView.findViewById(R.id.alarmTextView);
routineNameTextView = (TextView) routineItemView.findViewById(R.id.routineNameTextView);
tasksRecyclerView = (RecyclerView) routineItemView.findViewById(R.id.tasksRecyclerView);
}
public ImageView getRoutineStateImgView() {
return routineStateImgView;
}
public TextView getAlarmTextView(){
return alarmTextView;
}
public TextView getRoutineNameTextView(){
return routineNameTextView;
}
public RecyclerView getTasksRecyclerView(){
return tasksRecyclerView;
}
}
目前代码中的 onClick 设置为切换 ViewHolder 中定义的变量 (showTaskRecyclerView
),然后调用 notifyItemChanged(getAdapterPosition())
以使用动画更新项目更改。
调用 OnBindViewHolder 时,它会检查切换变量的状态(showTaskRecyclerView
,再次)并根据变量隐藏或取消隐藏 RecyclerView。您可以在代码中的 Log.d 方法行之后的代码中看到此逻辑:
public void onBindViewHolder(RoutinesRecyclerViewAdapter.ViewHolder holder, int position) {
//Replace contents of view with data from item in adapterRoutinesList at position.
Routine routine = adapterRoutinesList.get(position);
TasksRecyclerViewAdapter mTasksRecyclerViewAdapter = null;
if(routine == null){
holder.getRoutineStateImgView().setImageResource(android.R.color.transparent);
holder.getAlarmTextView().setText("");
holder.getRoutineNameTextView().setText("");
holder.getTasksRecyclerView().setVisibility(View.GONE);
}else{
DateFormat dayTimeFormat = new SimpleDateFormat("HH:mm", Locale.US);
String dayTimeString = dayTimeFormat.format(routine.getWakeupTime().getTime());
holder.getAlarmTextView().setText(dayTimeString);
holder.getRoutineNameTextView().setText(routine.getName());
if(routine.getEnableRoutine()) {
holder.getRoutineStateImgView().setImageResource(R.drawable.check_mark);
}
else{
holder.getRoutineStateImgView().setImageResource(R.drawable.x_mark);
}
Log.d(TAG, "position: " + position + " holder: " + holder + " show?: " + holder.showTaskRecyclerView);
if(holder.showTaskRecyclerView) {
TasksManager mTasksManager = adapterTasksListCache.get(routine.getId());
if (mTasksManager == null) {
mTasksManager = new TasksManager(routine.getId());
mTasksManager.readTasksFromDisk(mContext);
adapterTasksListCache.put(routine.getId(), mTasksManager);
}
mTasksRecyclerViewAdapter = adapterTasksViewCache.get(routine.getId());
if (mTasksRecyclerViewAdapter == null) {
mTasksRecyclerViewAdapter = new TasksRecyclerViewAdapter(
mContext,
position,
mTasksManager.getTasksListFromCache(),
mRequestImageCaptureCallBack,
mTaskEditTextListener);
adapterTasksViewCache.put(routine.getId(), mTasksRecyclerViewAdapter);
}
RecyclerView.LayoutManager mTaskLayoutManager = new LinearLayoutManager(mContext);
holder.getTasksRecyclerView().setLayoutManager(mTaskLayoutManager);
holder.getTasksRecyclerView().setAdapter(mTasksRecyclerViewAdapter);
holder.getTasksRecyclerView().setHasFixedSize(true);
holder.getTasksRecyclerView().setVisibility(View.VISIBLE);
}
else{
holder.getTasksRecyclerView().setVisibility(View.GONE);
}
}
}
我希望看到 RecyclerView 在每次点击时隐藏或取消隐藏。但是我只在每点击 2 次后看到 hide/unhide 行为。
这是点击 5 次后 Log.d 的输出:
04-16 21:33:37.026 13522-13522/co.edu.javeriana.faros D/RoutinesViewAdapter: position: 0 holder: ViewHolder{263b1503 position=0 id=-1, oldPos=-1, pLpos:-1 no parent} show?: false
04-16 21:33:42.626 13522-13522/co.edu.javeriana.faros D/RoutinesViewAdapter: position: 0 holder: ViewHolder{883eb98 position=0 id=-1, oldPos=-1, pLpos:-1 no parent} show?: false
04-16 21:33:44.511 13522-13522/co.edu.javeriana.faros D/RoutinesViewAdapter: position: 0 holder: ViewHolder{263b1503 position=0 id=-1, oldPos=-1, pLpos:-1 no parent} show?: true
04-16 21:33:46.274 13522-13522/co.edu.javeriana.faros D/RoutinesViewAdapter: position: 0 holder: ViewHolder{883eb98 position=0 id=-1, oldPos=-1, pLpos:-1 no parent} show?: true
04-16 21:33:47.938 13522-13522/co.edu.javeriana.faros D/RoutinesViewAdapter: position: 0 holder: ViewHolder{263b1503 position=0 id=-1, oldPos=-1, pLpos:-1 no parent} show?: false
04-16 21:41:52.756 13522-13522/co.edu.javeriana.faros D/RoutinesViewAdapter: position: 0 holder: ViewHolder{883eb98 position=0 id=-1, oldPos=-1, pLpos:-1 no parent} show?: false
我的解释是,使用 notifyItemChanged 会提示使用同一 ViewHolder 的 2 个副本来实现过渡动画目的(一个具有视图的旧状态,一个具有新状态)。但是,带有变量的副本在旧副本中发生了错误的更改。我假设我对 notifyItemChanged 的工作原理有错误的理解,所以我来这里寻求帮助:/
此外,我不知道是什么原因导致我点击得非常快:如果我点击得足够快(大约不到半秒),我看不到任何变化
logcat 显示如下:
04-16 21:43:54.597 13522-13522/co.edu.javeriana.faros D/RoutinesViewAdapter: position: 0 holder: ViewHolder{883eb98 position=0 id=-1, oldPos=-1, pLpos:-1 no parent} show?: true
04-16 21:43:54.862 13522-13522/co.edu.javeriana.faros D/RoutinesViewAdapter: position: 0 holder: ViewHolder{263b1503 position=0 id=-1, oldPos=-1, pLpos:-1 scrap [changeScrap] tmpDetached not recyclable(1) no parent} show?: true
04-16 21:43:55.387 13522-13522/co.edu.javeriana.faros D/RoutinesViewAdapter: position: 0 holder: ViewHolder{883eb98 position=0 id=-1, oldPos=-1, pLpos:-1 no parent} show?: true
04-16 21:43:55.668 13522-13522/co.edu.javeriana.faros D/RoutinesViewAdapter: position: 0 holder: ViewHolder{263b1503 position=0 id=-1, oldPos=-1, pLpos:-1 scrap [changeScrap] tmpDetached not recyclable(1) no parent} show?: true
04-16 21:43:55.949 13522-13522/co.edu.javeriana.faros D/RoutinesViewAdapter: position: 0 holder: ViewHolder{883eb98 position=0 id=-1, oldPos=-1, pLpos:-1 no parent} show?: true
04-16 21:43:56.189 13522-13522/co.edu.javeriana.faros D/RoutinesViewAdapter: position: 0 holder: ViewHolder{263b1503 position=0 id=-1, oldPos=-1, pLpos:-1 scrap [changeScrap] tmpDetached not recyclable(1) no parent} show?: true
04-16 21:43:56.479 13522-13522/co.edu.javeriana.faros D/RoutinesViewAdapter: position: 0 holder: ViewHolder{883eb98 position=0 id=-1, oldPos=-1, pLpos:-1 no parent} show?: true
有什么想法吗?
您需要实现 holder.showTaskRecyclerView
为 false
时的条件逻辑。
View
保持它们的状态,因此您必须显式修改它们。将一些逻辑隐藏在那里并检查它是否有效。
我通过在 ViewHolder
之外定义一个 ListArray
并为适配器中的每个项目定义一个 showTaskRecyclerView
来解决这个问题。
这样,旧的和新的 ViewHolder
看起来是一样的,每个项目单个 showTaskRecyclerView
而不是 2 个不同步的 showTaskRecyclerView
每个项目都位于两个 [=11] =].