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
相关产品推荐
相关产品推荐

