ListView子View包含EditText后的各种问题分析及解决方案
来源:互联网 发布:中文可以编程吗 编辑:程序博客网 时间:2024/05/21 11:11
ListView子View包含EditText后的各种问题分析及解决方案
前不久,群里面一个妹子在群里面咨询了这样了一个需求:ListView
的子View
里面包含一个EditText
,ListView
的item点击事件没有效果了。在尝试了查询资料,独立思考很久后,她还是没有解决问题。当时看到这个问题后,也觉得应该不是什么大问题,以前做过ListView
子View
内包含CheckBox
等容易优先获取焦点的控件相关的需求,所以给她的答复是百度看看 descendantFocusability 这个属性的使用场景。但是过了一会儿,她还是没能解决问题,于是私聊她跟她说帮她写个demo,尝试一下解决这个问题。
在编写布局文件,编写代码后,运行了自己demo后,我惊讶的发现自己也没能解决这个问题。稍微思考了一下,又换了一种方式,没有使用OnItemClick
,直接让Adapter.getView
方法返回的convertView
设置onClick
事件,最后还是成功的了解决了问题。
虽然解决了问题,但是作为一名的合格的程序员应当具有一定的探索精神,在发现descendantFocusability
这个属性没有效果后,便又尝试了一番,这不尝试没什么,一尝试吓一跳,代码一运行后发现,滑动各种数据错乱,这一下子问题就更大了。起初我的demo只是用来验证点击事件没有效果的问题,之后联想到ListView的数据复用性问题,便对demo代码做了修改,这里贴出修改后的代码:
Activity布局代码
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <ListView android:id="@+id/list_view" android:layout_width="match_parent" android:layout_height="match_parent" android:dividerHeight="10dp" /></LinearLayout>
ListView子View布局代码
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/item_root" android:layout_width="match_parent" android:layout_height="50dp" android:background="#ffcccc" android:gravity="center" android:orientation="vertical"> <EditText android:id="@+id/et_test" android:layout_width="200dp" android:layout_height="50dp" android:layout_gravity="right" android:gravity="center" android:hint="EditText提示信息" android:singleLine="true" android:textSize="20dp" /></LinearLayout>
数据填充
public class TestListViewWithEditTextActivity extends AppCompatActivity { public Activity getActivity() { return this; } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.layout_test_list_view_with_edit_text); ListView listView = (ListView) findViewById(R.id.list_view); List<ItemTest> list = new ArrayList<>(); for (int i = 0; i < 20; i++) { ItemTest itemTest = new ItemTest(); itemTest.setText(i + ""); list.add(itemTest); } MyAdapter adapter = new MyAdapter(this, list); listView.setAdapter(adapter); }}
Adapter代码
public class MyAdapter extends ArrayAdapter<ItemTest> implements View.OnClickListener { private int selectedEditTextPosition = -1; private TextWatcher mTextWatcher = new SimpleTextWatcher() { @Override public void onTextChanged(CharSequence s, int start, int before, int count) { if (selectedEditTextPosition != -1) { Log.d("MyAdapter", "onTextChanged position: " + selectedEditTextPosition + ", s: " + s); ItemTest itemTest = getItem(selectedEditTextPosition); itemTest.setText(s.toString()); } } }; public MyAdapter(Context context, List<ItemTest> objects) { super(context, 0, objects); } @NonNull @Override public View getView(int position, View convertView, ViewGroup parent) { VH vh; if (convertView == null) { convertView = LayoutInflater.from(getContext()).inflate(R.layout.item_test, null); vh = new VH(convertView); convertView.setTag(vh); convertView.setTag(R.id.item_root, position); } else { vh = (VH) convertView.getTag(); } vh.editText.addTextChangedListener(mTextWatcher); vh.editText.setOnClickListener(this); vh.editText.setTag(position); String text = getItem(position).getText(); vh.editText.setText(text); vh.editText.setSelection(vh.editText.length()); convertView.setOnClickListener(this); return convertView; } @Override public void onClick(View v) { if (v.getId() == R.id.et_test) { EditText editText = (EditText) v; selectedEditTextPosition = (int) editText.getTag(); Log.d("MyAdapter", "clicked position: " + selectedEditTextPosition); } else if (v.getId() == R.id.item_root) { int position = (int) v.getTag(R.id.item_root); Toast.makeText(getContext(), "点击第 " + position + " 个item", Toast.LENGTH_SHORT).show(); } }}
数据填充以上的代码就不详细解释了,无非就是创建了20个ItemTest
数据填充。重点看Adapter的代码(没有贴ViewHolder
代码),在getView
方法里面, 先常规性的创建并且利用ViewHolder
复用子View
,然后获取item数据填充到EditText
,注意到Adapter
内的一个mTextWatcher
,它用来处理EditText
编辑数据时的回调。同事还有个selectedEditTextPosition
变量,因为TextWatcher是全局变量,所有EditText
共享这个回调接口,为了记录当前是编辑哪一个EditText
,我用了这个变量来保存当前触发了哪一个EditText
的编辑事件,从代码中能看到
vh.editText.setTag(position)
和
if (v.getId() == R.id.et_test) { selectedEditTextPosition = (int) v.getTag();}
第一次运行后的效果
从运行结果的gif来看,当刚加载进入界面后,数据都是完好的,手动滑动到第15个item后,点击了第10个EditText
,然后弹出软键盘,然后关闭软件盘,第二次再点击第十个EditText
,再关闭软键盘,期间并没有做任何修改数据的操作,但是,当第二次关闭软键盘时,第10个EditText
数据变了,变成了9,之后继续滑动,把第10个item向下滑动出去,再滑动出来,发现,第10个EditText
数据又变成0了。。。如果你继续点击任意的EditText
,你会发现,数据变化的越来越离谱。而且,明明设置item点击事件,点击item也不会执行,这问题很无语啊.
运行结果不会欺骗你的眼睛的,实际上数据确实变化了,因为当弹出输入法时,界面上移,所以ListView
复用了子View
,子View
在重新绘制了,由于复用了View
,数据会发生错乱,但是,从代码可以看出,数据都是根据position
来保存和获取的,如果position
没有发生变动,数据理论上不会发生改变的,那么到底是哪里出了问题呢?这里不打算卖关子,实际上我能想到的可能性原因是输入法弹出时,不止一个EditText
获取了焦点!由于一次点击EditText
时,界面重绘,导致很多个EditText
同时获取焦点,当第二次弹出软键盘时,由于界面重绘,同时View复用,导致多个EditText
数据在设置数据时同时回调TextWatcher
的onTextChanged
接口,同时细心的读者会发现,EditText
的光标会在界面滑动时,出现多个在闪烁,因为同时获取了焦点。当然为了验证猜想,肯定得祭出log:
Log.d("MyAdapter", "onTextChanged position: " + selectedEditTextPosition + ", s: " + s);
log截图:
从log来看,操作流程和log打印里面的红框的描述是一致的,但是却出现了一个问题,第一次点击EditText,并没有打印出 clickposition: 10
这样的log,但是第二次点击时却打印了,同时,数据回调接口回调了position为10的次数非常多,但是,数据s确是不同的,这就验证起初猜想。此时暂且假装找到了问题的原因是由于多个EditText同时获得了焦点导致界面重绘时存储了错乱的数据。
既然问题找到了,就自然得有解决的办法。淡定思考三分钟,怎么确保同一时刻EditText
当中只有一个能获取到焦点呢?我觉得这个这问题的答案其实已经暴露在原有的代码当中了,答案就在那个selectedEditTextPosition
和EditText
焦点设置的处理上了。那么我的想法是这样的:既然界面重绘会引发多个EditText
同时获取焦点,那么保证只有点击到的EditText
才获取焦点,也就是点击的EditText
所在的 position 和
selectedEditTextPosition
的值相同,同时,这两个值不相等时,对应的EditText
应该取消焦点。但是有个问题,这个条件要建立在selectedEditTextPosition
变量的值一定在界面重绘之前就应当被修改为点击的EditText
对应的position
才行,所以,更改了代码如下:
vh.editText.addTextChangedListener(mTextWatcher); vh.editText.setOnClickListener(this); vh.editText.setTag(position); // 保证每个时刻只有一个EditText能获取到焦点 if (selectedEditTextPosition != -1 && position == selectedEditTextPosition) { vh.editText.requestFocus(); } else { vh.editText.clearFocus(); }
增加了底下四行代码,判断position
和selectedEditTextPosition
的值是否一样来获取或者清除焦点。当运行此时的代码,按照前面的操作流程过一遍后,运行结果很令我惊讶,因为并没有什么卵用…,和原先的问题一模一样,貌似没有啥变化。仔细查看代码发现,傻逼了,上面发现了一问题,第一此点击EditText
时并没有触发click
事件那么selectedEditTextPosition
的值就不会记录当前点击了哪个EditText
,那么那几行根据position
来判断是否获取焦点的代码肯定是不起作用了 ,唉。。。
静下心来,吃块薯片压压惊,于是我又想到一定要触发某个事件让selectedEditTextPosition
这个值发生改变,嗯,必须要改变!了解事件分发原理的读者应该知道onClick
事件是在onTouchEvent
(还有个OnTouchListener.onTouch
)的up
事件后触发的,onClick
此时既然不起作用了,那么就只好用onTouchEvent
,不过再一想,onTouchEvent
要复写控件,所以只能用onTouch
事件了,于是代码继续改动如下:
vh.editText.setOnClickListener(this);
更改为:
vh.editText.setOnTouchListener(this);@Overridepublic boolean onTouch(View v, MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_UP) { EditText editText = (EditText) v; selectedEditTextPosition = (int) editText.getTag(); Log.d("MyAdapter", "clicked position: " + selectedEditTextPosition); } return false;}
于是继续跑代码测试,结果如下:
你妈嗨,框中框的log奇迹般的复活出现了。but!这还是有问题啊啊!第一次点击后,数据还是错乱的啊,焦点问题理论上已经解决了,相关代码涉及到selectedEditTextPosition
变量的值也能正确获取了,还是不行,fuck again!这尼玛坑真多啊。。。这心是静不下来了啊。。。数据错乱反正是存储导致的,反正肯定是焦点导致的,现在这个代码先设置了TextWatcher
,在对应获取焦点,会不会弄反了啊,或者说界面重绘时,是不是应该只让当前点击的EditText
获取焦点的时候,才给它加上TextWatcher
,失去焦点的EditText
不应该添加TextWatcher
, 这样即使界面重绘没有获取到焦点的EditText
即使重新绘制也不会触发数据存储回调。想法好像没问题, 那就这么干,既然设置TextWatcher
是在获得焦点和失去焦点时执行的,很容易想到再弄个OnFocusChangedListener
,于是接着改:
vh.editText.addTextChangedListener(mTextWatcher);vh.editText.setOnTouchListener(this); // 修改前vh.editText.setTag(position);
vh.editText.setOnTouchListener(this);vh.editText.setOnFocusChangeListener(this); // 修改后vh.editText.setTag(position);@Overridepublic void onFocusChange(View v, boolean hasFocus) { EditText editText = (EditText) v; if (hasFocus) { editText.addTextChangedListener(mTextWatcher); } else { editText.removeTextChangedListener(mTextWatcher); }}
继续执行代码,看结果:
我草,尽然成功了,第一次点击EditText 只回调了一次onTextChanged,同时,修改数据后,关闭输入法,界面重绘,数据也没有错乱。这是个奇迹!皇天不负有心人,在尝试了各种方案,各种猜想后,终于大功告成了。
哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈嗝。
结尾总结:问题的出现一定是有迹可循,代码是不会骗人的。笔者最后解决了问题,但是过程中难在猜想,因为对系统源码的不熟悉,猜想的方向其实也很难把握,所以,在遇到类似问题的时候,尽自己最大的努力去尝试吧。
PS:最后把结果丢给妹子的时候。。。
源码传送门
- ListView子View包含EditText后的各种问题分析及解决方案
- ListView包含EditText RatingBar出现的各种问题
- Listview的itemview中包含edittext控件时的解决方案
- Android开发ListView中包含EditText控件遇到的问题
- 关于ListView中EditText点击弹出软键盘及软键盘弹出后的焦点问题
- listview item子view的自适应高度后不显示
- 布局中上面一个子布局,中间一个listview,下面一个edittext,解决edittext点击后输入法遮盖布局的问题
- 包含listview和edittext的界面,软键盘打开时布局向上移的解决方案
- 关于ListView中包含EditText数据复用引起异常的解决方案
- C++头文件重复包含问题分析及解决方案
- [Email]各种问题的分析和解决方案
- listview 内包含了新的listview 子listview 点击失效问题
- ListView包含EditText处理
- listview内的edittext调出来键盘后,edittext失去焦点问题
- 探讨Android开发ListView的Item里包含EditText控件遇到的一些问题
- 购物车 ListView 包含了EditText的问题,最终解决换RecyclerView来做,哈哈哈哈
- listview相关问题—item中包含EditText的处理 (高上)
- 工作中遇到的listview相关问题(二)——item包含EditText
- spring boot的入门demo搭建记录
- git与SourceTree安装教程
- django ImproperlyConfigured异常
- 输入一组数据循环比较大小
- 多渠道打包
- ListView子View包含EditText后的各种问题分析及解决方案
- CSS发展史整理
- git命令rebase
- 一些干货——优秀Java程序员发福利啦!
- 面试题5. 从尾到头打印链表
- 实现类
- 自定义view系列(7)--SwitchView
- encodeURI与encodeURIComponent方法的区别 如果你使用的get方法提交表单肯定要考虑到输入项目的编码解码问题。 解决这个问题大家一般都使用encodeURI或者en
- JDBC_操纵MySQL最底层的方法封装详解