最近在做项目时,UI设计师给了一个设计图,添加多个人的信息时,item里面包含了EditText在填写姓名时出现数据混乱,如下图所示:
RecyclerView中EditText的监听处理
这个界面的主要逻辑是监听每个EditText的输入,然后保存到个人对象里,最后保存到服务器上。RecyclerView中Item里EditText的监听可以按如下代码实现:
首先定义个回调接口:
public interface OnTextChangeListener{
void onClickItem(int position,EditTextBean bean);
}
在自定义Adapter中添加一个如上自定义的接口成员变量mTextListener,然后在onBindViewHolder方法里面添加Item的EditText输入监听,即EditText.addTextChangedListener(),然后在监听事件回调里的afterTextChanged 方法中 调用 mTextListener.onClickItem来达到接口回调的目的。
自定义EditListAdapter类:
/**
* Created by ysp
* on 2020/12/22
*/
public class EditListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private Context mContext;
private LayoutInflater mInflater;
private ArrayList<EditTextBean> mList;
private OnTextChangeListener mTextListener;
public EditListAdapter(Context mContext, ArrayList<EditTextBean> mList) {
this.mContext = mContext;
mInflater=LayoutInflater.from(mContext);
this.mList = mList;
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view=mInflater.inflate(R.layout.item_edittext_refresh,parent,false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, final int position) {
ViewHolder viewHolder= (ViewHolder) holder;
if (mList.size()<=0){
return;
}
final EditTextBean editTextBean = mList.get(position);
viewHolder.tv_title.setText(editTextBean.getTitle());
viewHolder.et_content.setText(editTextBean.getContent());
viewHolder.et_content.setHint("请填写");
//添加EditText的监听事件
viewHolder.et_content.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void afterTextChanged(Editable editable) {
editTextBean.setContent(editable.toString());
mTextListener.onClickItem(position,editTextBean);
}
});
}
@Override
public int getItemCount() {
return mList.size();
}
class ViewHolder extends RecyclerView.ViewHolder{
private TextView tv_title;
private EditText et_content;
public ViewHolder(@NonNull View itemView) {
super(itemView);
tv_title=itemView.findViewById(R.id.tv_title);
et_content=itemView.findViewById(R.id.et_content);
}
}
public interface OnTextChangeListener{
void onClickItem(int position,EditTextBean bean);
}
public void setOnTextChangeListener(OnTextChangeListener onTextChangeListener){
mTextListener=onTextChangeListener;
}
}
Activity中的代码实现,在mAdapter中添加匿名内部类来监听EditText中的接口回调数据:
public class EditTextRadioGroupActivity extends AppCompatActivity implements View.OnClickListener {
private RecyclerView recyclerView;
private EditListAdapter adapter;
private TextView tv_add;
private ArrayList<EditTextBean>list=new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_edit_text_radio_group);
recyclerView=findViewById(R.id.recyclerView);
tv_add=findViewById(R.id.tv_add);
tv_add.setOnClickListener(this);
adapter=new EditListAdapter(this,list);
LinearLayoutManager layoutManager=new LinearLayoutManager(this);
layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
recyclerView.setLayoutManager(layoutManager);
recyclerView.setAdapter(adapter);
adapter.setOnTextChangeListener(new EditListAdapter.OnTextChangeListener() {
@Override
public void onClickItem(int position, EditTextBean bean) {
list.set(position,bean);
}
});
}
@Override
public void onClick(View view) {
switch (view.getId()){
case R.id.tv_add:
EditTextBean bean=new EditTextBean("姓名","");
list.add(bean);
adapter.notifyDataSetChanged();
break;
}
}
}
OK 上面的监听事件处理完了 我们来试试效果怎么样。首先点击按钮添加一条数据,填写一下第一个人的名字:小米;然后再点击按钮添加一条数据,填写一下第二个人的名字:小兰;然后再点击按钮添加一条数据,你会发现第一个人的名字也变成了小兰;就出现上图中的监听事件数据错乱的重大的Bug。
Recycler中 多Item中EditText焦点混乱网上的解决方法
现在网上的解决方法很多是使用EditText的TAG来解决问题,EditText中有setTag和getTag可以储存Object对象作为标签,然后把textWatcher作为标签设置TAG来作为标记表明这个item的editText是否已经设置textChange监听了。在Adapter的onBindViewHolder中每次都先判断editText的Tag是否有textWatcher对象,有的话就调用removeTextChangedListener来移除由于视图复用之前绑定的textWatcher,然后就设置editText的内容setText,最后再给editText设置监听器addTextChangedListener,并把这个textWatcher加入到editText的TAG标签中。总结来说就是在适配器里先移除被复用事件,再添加新事件,如下:
//避免复用
EditText editText = helper.getView(R.id.et_comment);
if (editText.getTag() instanceof TextWatcher) {
editText.removeTextChangedListener((TextWatcher) editText.getTag());
}
helper.setText(R.id.et_comment, item.content);
TextWatcher watcher = 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.content = s.length() > 0 ? s.toString() : "";
}
};
editText.addTextChangedListener(watcher);
editText.setTag(watcher);
这种方式并不优雅,而且还要进行类型判断和强制类型转型,对性能有一定的开销,下面来介绍真正的优雅解决EditText焦点混乱的解决方法:
Recycler中 多Item中EditText焦点混乱,数据错乱的正确优雅的解决方法 !
首先要知道EditText焦点错乱的问题就是RecyclerView的视图回收与复用问题,例如在位置0有item1视图中的editText已经绑定了textWatcher1监听器,但是又没有移除监听器,在位置6时又复用了item1视图,editText又再次绑定了textWatcher2监听器,此时item1视图中的editText已经绑定了2个textWatcher监听器了,当在位置6进行输入数据时,必定会导致位置0的数据变化了,令位置0的数据跟位置6的数据一模一样,也就是数据错乱了。原因是editText中的addTextChangedListener是可以绑定多个监听器的,来看看它的实现:
public void addTextChangedListener(TextWatcher watcher) {
if (mListeners == null) {
mListeners = new ArrayList<TextWatcher>();
}
mListeners.add(watcher);
}
可以看到mListeners 是一个ArrayList数组,当多次调用addTextChangedListener时,一个editText可以绑定多个textWatcher监听器,所以要记住在每次调用addTextChangedListener完之后必须调用removeTextChangedListener来接触绑定。
那调用这两个方法的时间节点是在哪里的呢?首先我们要知道如果一个editText要获取输入值那它就首先必须要获取到焦点了,没错,答案就是设置setOnFocusChangeListener监听器来判断焦点的变化从而设置addTextChangedListener和removeTextChangedListener。
代码如下
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, final int position) {
final ViewHolder viewHolder = (ViewHolder) holder;
if (mList.size() <= 0) {
return;
}
final EditTextBean editTextBean = mList.get(position);
viewHolder.tv_title.setText(editTextBean.getTitle());
viewHolder.et_content.setText(editTextBean.getContent());
viewHolder.et_content.setHint("请填写");
//添加EditText的监听事件
final TextWatcher textWatcher = new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void afterTextChanged(Editable editable) {
if (viewHolder.et_content.hasFocus()) {//判断当前EditText是否有焦点在
editTextBean.setContent(editable.toString());
//通过接口回调将数据传递到Activity中
mTextListener.onClickItem(position, editTextBean);
}
}
};
//设置EditText的焦点监听器判断焦点变化,当有焦点时addTextChangedListener,失去焦点时removeTextChangedListener
viewHolder.et_content.setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View view, boolean hasFocus) {
if (hasFocus){
viewHolder.et_content.addTextChangedListener(textWatcher);
}else {
viewHolder.et_content.removeTextChangedListener(textWatcher);
}
}
});
}
通过设置setOnFocusChangeListener来监听焦点就能完美的解决了RecyclerView中EditText数据监听textWatcher导致的数据错乱问题了。目前来说是最优雅的一种方式了,也不需要用到Tag了
Recycler中 多Item中CheckBox焦点混乱,数据错乱的解决方法:
//避免复用
CheckBox checkBox = helper.getView(R.id.mCheckBox);
checkBox.setOnCheckedChangeListener(null);
if (item.anonymous) {
checkBox.setChecked(true);
} else {
checkBox.setChecked(false);
}
checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
item.anonymous = isChecked;
}
});
Recycler中 多Item中RadioGroup ,RadioButton焦点混乱,数据错乱的解决方法
CertigierInfoListBean bean= mItems.get(position);
//避免复用
viewHolder.radioGroup_have_partner.setOnCheckedChangeListener(null);
String isSpouse = bean.getIsSpouse();
if ("1".equals(isSpouse)) {//有配偶
viewHolder.rb_yes_partner.setChecked(true);
viewHolder.rb_no_partner.setChecked(false);
} else {
viewHolder.rb_yes_partner.setChecked(false);
viewHolder.rb_no_partner.setChecked(true);
}
viewHolder.radioGroup_have_partner.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(RadioGroup radioGroup, int id) {
switch (id) {
case R.id.rb_yes_partner_borrower://有配偶
bean.setIsSpouse("1");
if (onInfoClickedListener != null) {
onInfoClickedListener.onClickItem(position, bean);
}
break;
case R.id.rb_no_partner://无配偶
bean.setIsSpouse("0");
if (onInfoClickedListener != null) {
onInfoClickedListener.onClickItem(position, bean);
}
break;
}
}
});