零 前言
此文发现BUG,换了思路,重写了一篇并更新了DEMO代码 地址如下:http://www.jianshu.com/p/1c91bdc96d16
可以直接忽略下面全文
一 引言
闲着无聊看到挺多群内都有人在咨询listview中的edittext焦点错乱,数据错乱等问题,然后顺手也搜索了一下网上的解决方案和部分demo,发现总有不尽如人意的地方和一些作者也没有发现的bug。
然后在同事的怂恿下,也尝试着提供一个靠谱的解决方案。
但是现在已经是2017年了,列表控件再用listview的话,有点活在过去的感觉啊,所以与时俱进的选择了RecyclerView来代替ListView,经过N个小时的研究与整理,把几个问题点都过滤掉了,留下此DEMO,赠与有缘人。
特别感谢:苏泽兄、逗你玩222对文章的错漏之处提出的改正意见
二 效果图
为了节约大家宝贵的流量,就不放图了
三 解决方案
- 解析整个问题点之前,先把项目的完整demo放送给大家,地址如下:
https://github.com/kaxi4it/EditTextInRecyclerViewDemo
注:阅读以下文章时,建议对照demo代码对比观看 - 焦点错乱
editText的焦点,我们可以通过一个int变量记录他在adapter中的位置
//edittext的焦点位置
int etFocusPos = -1;
然后在onBindViewHolder方法中,通过比较edittext的焦点位置 与 viewholder的position 来判断处理,是否需要让item中的edittext获取焦点,并把光标位置置于输入框内文字的最后一位。
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int i) {
//当前holder的position
final int position = i;
...
//当前holder是我们记录下的焦点位置时,我们给当前的editext设置焦点并设置光标位置
if (etFocusPos == position) {
viewHolder.et.requestFocus();
viewHolder.et.setSelection(viewHolder.et.getText().length());
}
...
//我们给当前holder中的edittext添加touch事件监听,在action_up手指抬起时,记录下焦点position
viewHolder.et.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_UP) {
etFocusPos = position;
}
return false;
}
});
}
接着,因为是在列表中使用了EditText,所以软键盘输入时,会有下一项,下一行的提示帮助用户快速切换下一个输入框,这里就需要对EditText添加OnFocusChangeListener监听,来保障焦点位置的正确监听和记录
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int i) {
final int position = i;
final ItemHolder viewHolder = (ItemHolder) holder;
viewHolder.et.setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View view, boolean b) {
if (b){
etFocusPos = position;
}
}
});
}
- 数据错乱
列表滑动后,EditText内输入的内容错乱问题,也比较好解决,就是把每次输入的文本内容,相应的保存下来,再显示的时候,赋值即可,adapter里的关键代码如下。
//edittext里的文字内容集合
SparseArray<String> etTextAry = new SparseArray();
//监听文字变化的TextWatcher接口
TextWatcher textWatcher = 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) {
//保存输入的文字内容
etTextAry.put(etFocusPos, s.toString());
}
};
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int i) {
//当前holder的position
final int position = i;
final ItemHolder viewHolder = (ItemHolder) holder;
...
//根据对应的位置填入对应的输入框记录内容
viewHolder.et.setText(etTextAry.get(position));
...
//给EditText添加监听
viewHolder.et.addTextChangedListener(textWatcher);
}
- 监听错乱
因为RecyclerView的复用功能,所以显示给你的EditText也许是从cache里提供给你的,那么这个EditText内其实已经绑定过TextWatcher的实现类,我们翻阅TextView(addTextChangedListener是TextView的代码,EditText是继承TextView所实现的)的代码可以发现
public void addTextChangedListener(TextWatcher watcher) {
if (mListeners == null) {
mListeners = new ArrayList<TextWatcher>();
}
mListeners.add(watcher);
}
源码中的TextWatcher是被add进一个Listener集合中,所以当我们复用ViewHolder时,会无意识的导致添加了N个TextWatcher在当前EditText,所以导致了监听事件的错乱,那么我们只需要在ViewHolder被销毁时把之前添加进集合的TextWatcher从集合中remove掉那么就能保证每个EditText只添加一个TextWatcher监听器
@Override
public void onViewRecycled(RecyclerView.ViewHolder holder) {
super.onViewRecycled(holder);
//当前holder被销毁时,把holder的TextChangedListener删除
ItemHolder viewHolder = (ItemHolder) holder;
viewHolder.et.removeTextChangedListener(textWatcher);
}
- 输入法隐藏
举个栗子,当我们在EditText中设置了inputType为number后,会发现焦点滑出后,软键盘或者还在显示,或者关闭了数字键盘,但是英文键盘还显示着,等等系列问题,经过几次试错,发现使用如下方法就可以解决这个问题
//输入法管理类
InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
@Override
public void onViewDetachedFromWindow(RecyclerView.ViewHolder holder) {
super.onViewDetachedFromWindow(holder);
//当获取焦点的editText从window隐藏时
if (etFocusPos == holder.getAdapterPosition()) {
inputMethodManager.hideSoftInputFromWindow(((ItemHolder) holder).et.getWindowToken(), 0);
}
}
- 其他错乱
虽然以上方案解决了大部分问题,但是最后由于RecyclerView的缓存机制,所以默认情况下,他会保留最后分离出来的2个ViewHolder,那么在这种机制下就会导致我们的onBindViewHolder方法并不会100%的在显示ViewHolder时调用,因为总是会有2个ViewHolder是从mCachedViews的集合中读取而来,导致我们在onBindViewHolder中的一些方法并没有被再次执行到。
比如该DEMO中关于EditText的焦点绑定事件,就会因为该机制的原因,会在几率出现已记录的焦点绑定失败的情况,那么我们可以选择把默认的mCachedViews集合数设置为0,那么就能保证每一次ViewHolder的显示都需要经过onBindViewHolder方法。
recyclerView.setItemViewCacheSize(0);
除非有类似该DEMO的特例需求,其他正常情况下并不推荐如此操作,因为缓存是个好东西,只是偶尔有点小尴尬罢了(__)
四 结束语
希望以上的完整DEMO和代码的讲解能帮您解决这些小问题,最后无耻的广告一下
最近正在发展副业,代理了一款《Divoom TimeBox 2代智能蓝牙音箱》的小产品,如果您看了以后,觉得"唉?这小玩意不错哎,有点意思,送礼挺合适的",那么欢迎前来选购,悄悄和客服说下您是简书来的读者,我会帮您改价,在原价的基础上降价100元,如果您也想发展个副业,做个小代理,那么欢迎前来私信
https://item.taobao.com/item.htm?id=545135458341
网友评论
NestedScrollView网上滑动,但是软键盘上面的那一项不是我刚刚点的获取了焦点的
那一项,这个问题也不知道什么情况 楼主帮忙看看