You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android RecyclerView中addTextChangedListener的正确实现位置

实现位置结论

addTextChangedListener 逻辑必须放在TimeTableAdapter类中实现,放在TimeTable类里完全不具备可行性:

  • 你在Activity(即TimeTable类)中无法直接获取RecyclerView滚动复用时生成的每个Item内的EditText实例,根本没法给所有输入框绑定监听
  • 你之前想把逻辑写在addBtn.setOnClickListener()的else分支里更是错的:这段代码只有点击添加按钮的时候才会执行,平时用户在输入框打字时完全不会触发,根本做不到实时同步输入内容。
正确实现方式

别直接把监听无脑塞到onBindViewHolder里——RecyclerView的ViewHolder是复用的,直接在onBind里反复加监听会导致重复绑定、输入错位、内存泄漏、回调乱触发的问题,按以下步骤改:

  • 监听只在ViewHolder初始化时绑定一次,不要每次bind都新建监听
  • 每次绑定数据时,先移除EditText上已有的TextWatcher,等给EditText设置完已有列表数据后,再重新把监听加回去,避免代码主动调用setText()时误触发输入回调
  • 拿当前条目的位置时用getBindingAdapterPosition(),不要硬存onBind传入的position,避免列表增删后位置不更新导致数据写错条目

修改后的TimeTableAdapter代码

package com.example.timetable;

import android.content.Context;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.EditText;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

import java.util.ArrayList;

// 注意:Java类名规范为大驼峰,建议把row类改名为Row,避免编译问题
public class TimeTableAdapter extends RecyclerView.Adapter<TimeTableAdapter.rowVH> {

    ArrayList<row> rows;
    Context context;

    public TimeTableAdapter(ArrayList<row> oneCell, Context context){
        rows = oneCell;
        this.context = context;
    }

    public class rowVH extends RecyclerView.ViewHolder{

        public TextView index;
        public EditText tasksName;
        public EditText totalHours;
        // 声明两个TextWatcher实例,复用避免重复创建
        private TextWatcher nameWatcher;
        private TextWatcher hoursWatcher;

        public rowVH(@NonNull View itemView) {
            super(itemView);
            index = itemView.findViewById(R.id.index);
            tasksName = itemView.findViewById(R.id.taskName);
            totalHours = itemView.findViewById(R.id.totalHours);
        }

        // 单独写绑定数据的方法,处理监听的移除和添加
        public void bind(row currentRow){
            // 先移除旧监听,避免setText触发回调
            if (nameWatcher != null) {
                tasksName.removeTextChangedListener(nameWatcher);
            }
            if (hoursWatcher != null) {
                totalHours.removeTextChangedListener(hoursWatcher);
            }

            // 给控件赋值当前已存的数据
            index.setText(String.valueOf(currentRow.getIndex()));
            tasksName.setText(currentRow.getTasksName());
            if (currentRow.getHours() == 0) {
                totalHours.setText("");
            } else {
                totalHours.setText(String.valueOf(currentRow.getHours()));
            }

            // 新建任务名输入监听
            nameWatcher = 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) {
                    int pos = getBindingAdapterPosition();
                    if (pos != RecyclerView.NO_POSITION) {
                        rows.get(pos).setTasksName(s.toString().trim());
                    }
                }

                @Override
                public void afterTextChanged(Editable s) {}
            };

            // 新建时长输入监听
            hoursWatcher = 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) {
                    int pos = getBindingAdapterPosition();
                    if (pos != RecyclerView.NO_POSITION) {
                        String input = s.toString().trim();
                        int hours = 0;
                        if (!input.isEmpty()) {
                            try {
                                hours = Integer.parseInt(input);
                            } catch (NumberFormatException e) {
                                hours = 0;
                            }
                        }
                        rows.get(pos).setHours(hours);
                    }
                }

                @Override
                public void afterTextChanged(Editable s) {}
            };

            // 给输入框绑定新的监听
            tasksName.addTextChangedListener(nameWatcher);
            totalHours.addTextChangedListener(hoursWatcher);
        }
    }

    @NonNull
    @Override
    public rowVH onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.each_row, parent, false);
        return new rowVH(v);
    }

    @Override
    public void onBindViewHolder(@NonNull rowVH holder, int position) {
        // 所有绑定逻辑移到ViewHolder的bind方法里处理
        holder.bind(rows.get(position));
    }

    @Override
    public int getItemCount() {
        return rows.size();
    }
}
额外注意点
  • 你原来在rowVH构造方法里给tasksName和totalHours强行setText("")的逻辑要删掉,不然每次创建ViewHolder都会把输入框清空,会导致列表滚动时输入内容丢失
  • Activity里的rows集合是引用传给Adapter的,所以Adapter里直接修改rows里的对象属性,Activity里拿到的就是实时更新后的数据,点添加按钮判断最后一行是否为空的逻辑不用改,可以直接生效
  • 给totalHours做了数字格式异常捕获,避免用户输入非数字内容时app崩溃

内容的提问来源于stack exchange,提问作者Parth Gupta

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 22:27:31