【问题标题】:onBindViewHolder(VH, position) improperly calling the old and new ViewHolder copies when using notifyItemChanged()onBindViewHolder(VH, position) 在使用 notifyItemChanged() 时不正确地调用新旧 ViewHolder 副本
【发布时间】:2016-04-17 03:11:47
【问题描述】:

EDIT2: 我在holder.showTaskRecyclerViewfalse 的条件下明确表示隐藏视图,以便在onBindViewHolder 中明确覆盖truefalse 情况.我还是有同样的问题。

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。在这个sn-p中使用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 次点击后看到隐藏/取消隐藏行为。

点击 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

有什么想法吗?

【问题讨论】:

    标签: android android-layout android-recyclerview android-viewholder


    【解决方案1】:

    你需要实现holder.showTaskRecyclerViewfalse时的条件逻辑。

    View 保持其状态,因此您必须显式修改它们。放置一些逻辑隐藏在那里并检查它是否有效。

    【讨论】:

    • 据我了解,如果我想“隐藏”它,recyclerview 会收到一个空适配器。 mTasksRecyclerViewAdapter 适配器在开始时默认设置为 null。如果我达到holder.showTaskRecyclerView 为真的条件,则适配器已设置。因此,添加 else 子句在逻辑上没有任何作用。如果您查看第二个 sn-p 中的倒数第二行代码,则视图的适配器始终设置为 null 或已获取的适配器。
    • View 被回收并因此保持状态。这是一个演示此问题的简单示例。 pastie.org/private/ftgz5lnnlcjch8yqs1jyzq你试过甩了吗?你可能会看到问题。没有两边的逻辑会导致数据不正确的失效或重新绑定。
    • 是的,扔东西很好。我也明确地实现了逻辑的双方(我编辑了我的帖子以显示新代码)但问题仍然存在。
    【解决方案2】:

    我通过在ViewHolder 之外定义ListArray 并为适配器中的每个项目使用showTaskRecyclerView 来解决此问题。

    通过这种方式,旧的和新的ViewHolder 看起来相同,每个项目单个showTaskRecyclerView,而不是两个不同步的showTaskRecyclerView 驻留在ViewHolder 中的每个项目。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-07-30
      • 1970-01-01
      相关资源
      最近更新 更多