为什么在 Recycler View 中滚动后值会消失?

Why do values ​disappear after scrolling in Recycler View?

滚动前的数据

滚动后的数据


我的应用程序的问题如上图所示。

输入数据后,如果我在将项目添加为可滚动后滚动,数据会消失。

进一步说明,有时输入的数据会出现在已添加的其他项目中。

解释一下这个应用,它是一个运动记录应用,它使用multi-type recycler view

我用了ListAdapter,也用了DiffUtil。和图片相关的是Detail item.

TextWatcher用于保存输入的数据。

我一直在寻找解决这个问题的方法。

搜索次数最多的两个解决方案是 here

  1. 使用 getItemViewType()getItemId() ->我使用了这个link中的方法,但是没有解决问题。

  2. Using setIsRecyclable(false) inside holder -> 这个方法奏效了。但是我听说setIsRecyclable(false)是一个没有recycle的函数。 如果我使用它,这不是一个好方法吗,因为使用 RecyclerView 没有任何优势?

RoutineAdapter.java

public class RoutineListAdapter extends ListAdapter<Object, RecyclerView.ViewHolder> {
    Context context;
    RoutineListAdapter.OnRoutineItemClickListener routinelistener;
    RoutineListAdapter.OnRoutineAddClickListener routineAddListener;

    final static int TYPE_ROUTINE = 1;
    final static int TYPE_ROUTINE_DETAIL = 2;
    final static int TYPE_ROUTINE_FOOTER = 3;

    public RoutineListAdapter(@NonNull DiffUtil.ItemCallback<Object> diffCallback) {
        super(diffCallback);
    }

    // add routine interface
    public interface OnRoutineAddClickListener {
        public void onAddRoutineClick();
    }

    public void setOnAddRoutineClickListener(RoutineListAdapter.OnRoutineAddClickListener listener) {
        this.routineAddListener = listener;
    }

    // add/remove detail interface
    public interface OnRoutineItemClickListener {
        public void onAddBtnClicked(int curRoutinePos);
        public void onDeleteBtnClicked(int curRoutinePos);
        public void onWritingCommentBtnClicked(int curRoutinePos);
    }

    public void setOnRoutineClickListener(RoutineListAdapter.OnRoutineItemClickListener listener) {
        this.routinelistener = listener;
    }

    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        context = parent.getContext();
        View itemView;
        if(viewType == TYPE_ROUTINE){
            itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.routine_item, parent, false);
            return new RoutineListAdapter.RoutineViewHolder(itemView);
        }
        else if(viewType == TYPE_ROUTINE_DETAIL){
            itemView = LayoutInflater.from(context).inflate(R.layout.routine_detail_item, parent, false);
            return new RoutineListAdapter.RoutineDetailViewHolder(itemView);
        }
        else {
            itemView = LayoutInflater.from(context).inflate(R.layout.add_routine_item, parent, false);
            return new RoutineListAdapter.RoutineAddFooterViewHolder(itemView);
        }
    }

    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
        Object curItem;
        switch (getItemViewType(position)) {
            case TYPE_ROUTINE:
                curItem = getItem(position);
                setRoutineData((RoutineListAdapter.RoutineViewHolder) holder, (RoutineModel) curItem);
                break;
            case TYPE_ROUTINE_DETAIL:
                curItem = getItem(position);
                RoutineDetailModel item = (RoutineDetailModel) curItem;
                ((RoutineListAdapter.RoutineDetailViewHolder) holder).bind(item);
                ((RoutineDetailViewHolder) holder).weight.addTextChangedListener(new TextWatcher() {
                    @Override
                    public void beforeTextChanged(CharSequence s, int start, int count, int after) {

                    }

                    @Override
                    public void onTextChanged(CharSequence s, int start, int before, int count) {

                    }

                    @Override
                    public void afterTextChanged(Editable s) {
                        item.setWeight(((RoutineDetailViewHolder) holder).weight.getText().toString());
                    }
                });
                break;
            case TYPE_ROUTINE_FOOTER:
                break;
        }
    }

    private void setRoutineData(RoutineListAdapter.RoutineViewHolder holder, RoutineModel routineItem){
        holder.routine.setText(routineItem.getRoutine());
    }

    public Object getRoutineItem(int position) {
        if(getCurrentList() == null || position < 0 || position >= getCurrentList().size())
            return null;
        return getItem(position);
    }

    @Override
    public int getItemCount() {
        return getCurrentList().size() + 1;
    }

    @Override
    public int getItemViewType(int position) {
        if(position == getCurrentList().size()) {
            return TYPE_ROUTINE_FOOTER;
        }
        else {
            Object obj = getItem(position);
            if(obj instanceof RoutineModel) {
                return TYPE_ROUTINE;
            }
            else {
                // obj instanceof RoutineDetailModel
                return TYPE_ROUTINE_DETAIL;
            }
        }
    }

    private class RoutineViewHolder extends RecyclerView.ViewHolder {
        public TextView routine;
        public Button addSet;
        public Button deleteSet;
        public Button comment;

        public RoutineViewHolder(@NonNull View itemView) {
            super(itemView);
            routine = itemView.findViewById(R.id.routine);
            addSet = itemView.findViewById(R.id.add_set);
            deleteSet = itemView.findViewById(R.id.delete_set);
            comment = itemView.findViewById(R.id.write_comment);

            addSet.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    if(routinelistener != null && getAdapterPosition() != RecyclerView.NO_POSITION)
                        routinelistener.onAddBtnClicked(getAdapterPosition());
                }
            });

            deleteSet.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    if(routinelistener != null && getAdapterPosition() != RecyclerView.NO_POSITION)
                        routinelistener.onDeleteBtnClicked(getAdapterPosition());
                }
            });

            comment.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    if(routinelistener != null && getAdapterPosition() != RecyclerView.NO_POSITION)
                        routinelistener.onWritingCommentBtnClicked(getAdapterPosition());
                }
            });
        }
    }

    private class RoutineDetailViewHolder extends RecyclerView.ViewHolder {
        private TextView set;
        private EditText weight;

        public RoutineDetailViewHolder(@NonNull View itemView) {
            super(itemView);
            set = itemView.findViewById(R.id.set);
            weight = itemView.findViewById(R.id.weight);
        }

        private void bind(RoutineDetailModel item) {
            set.setText(item.getSet().toString() + "set");
            weight.setText(item.getWeight());
        }
    }

    private class RoutineAddFooterViewHolder extends RecyclerView.ViewHolder {
        TextView textView;

        public RoutineAddFooterViewHolder(@NonNull View itemView) {
            super(itemView);
            textView = itemView.findViewById(R.id.add_text);
            ConstraintLayout regionForClick = itemView.findViewById(R.id.clickable_layout);
            regionForClick.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    if (routineAddListener != null) {
                        routineAddListener.onAddRoutineClick();
                    }
                }
            });
        }
    }
}

已更新

适配器

public class RoutineListAdapter extends ListAdapter<Object, RecyclerView.ViewHolder> {

//  detail add / remove iterface
    public interface OnRoutineItemClickListener {
        public void onAddBtnClicked(int curRoutinePos);
        public void onDeleteBtnClicked(int curRoutinePos);
        public void onWritingCommentBtnClicked(int curRoutinePos);
        public void onWritingWeight(int curRoutinePos, View view); // write weight
    }

    public void setOnRoutineClickListener(RoutineListAdapter.OnRoutineItemClickListener listener) {
        if(this.routinelistener != null)
            this.routinelistener = null;
        this.routinelistener = listener;
    }

    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
        Object curItem;
        switch (getItemViewType(position)) {
            case TYPE_ROUTINE:
                curItem = getItem(position);
                setRoutineData((RoutineListAdapter.RoutineViewHolder) holder, (RoutineModel) curItem);
                break;
            case TYPE_ROUTINE_DETAIL:
                ((RoutineListAdapter.RoutineDetailViewHolder) holder).bind();
                break;
            case TYPE_ROUTINE_FOOTER:
                break;
        }
    }

 private class RoutineDetailViewHolder extends RecyclerView.ViewHolder {
        private TextView set;
        private EditText weight;

        public RoutineDetailViewHolder(@NonNull View itemView) {
            super(itemView);
            set = itemView.findViewById(R.id.set);
            weight = itemView.findViewById(R.id.weight);

            weight.addTextChangedListener(new TextWatcher() {
                @Override
                public void beforeTextChanged(CharSequence s, int start, int count, int after) {

                }

                @Override
                public void onTextChanged(CharSequence s, int start, int before, int count) {
                    
                }

                @Override
                public void afterTextChanged(Editable s) {
                  routinelistener.onWritingWeight(getAdapterPosition(), itemView);
                }
            });
        }

        private void bind() {
            RoutineDetailModel item = (RoutineDetailModel) getItem(getAdapterPosition());
            set.setText(item.getSet().toString() + "set");
            weight.setText(item.getWeight()); // Setting the saved value
        }
    }

Activity

public class WriteRoutineActivity extends AppCompatActivity implements WritingCommentDialogFragment.OnDialogClosedListener {
  
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_write_routine);

        initViews();
        setPageTitle(getIntent());
        setRoutineRecyclerview();

        diffUtil2 = new RoutineDiffUtil2();
        listAdapter = new RoutineListAdapter(diffUtil2);
        items = new ArrayList<>();
        routine_rv.setAdapter(listAdapter);

        listAdapter.setOnRoutineClickListener(new RoutineListAdapter.OnRoutineItemClickListener() {
            @Override
            public void onWritingWeight(int curRoutinePos, View v) {
                RoutineDetailModel item = (RoutineDetailModel) listAdapter.getRoutineItem(curRoutinePos);
                EditText weight = v.findViewById(R.id.weight);
                item.setWeight(weight.getText().toString()); // This is saved to set the value again when recycled.
        });
    }
}

如果您需要任何其他额外代码,请告诉我

问题在于您在 onBindViewHolder 中添加的 TextWatcher

目前您已设置好,以便每次 RecyclerView 绑定一个视图(每个实际视图可能发生多次),您将添加一个新的 TextWatcher,然后还将文本设置为项目的权重,然后触发您之前添加的观察者,将项目的权重设置为其他内容,在本例中为空字符串。

您应该做的是在添加另一个听众之前移除所有听众,或者在 onCreateViewHolder 中添加听众并使用支架的适配器位置来获取您的物品。


这里有一些伪代码来阐明我的建议:

onCreateViewHolder

中添加监听器
RoutineDetailViewHolder {
    private EditText weight;

    RoutineDetailViewHolder {

        weight.addTextChangedListener {
            
            items[adapterPosition].setWeight(...)
        }
    }
}

再次绑定前删除监听器:

RoutineDetailViewHolder {
    private EditText weight;
    private TextWatcher weightWatcher;

    void bind() {
    
        weight.removeTextChangedListener(weightWatcher);
        
        weightWatcher = new TextWatcher();
        weight.addOnTextChangedListener(weightWatcher);
    }
}