在日常开发中,ListView 是我们常用的控件,也是遇到坑比较多的一个控件。在之前的项目中,有这样的一个布局需求,在 ListView 的 item 中包含有 EditText,第一个问题就是焦点问题,会发现 edittext 获取不到焦点。
1.焦点问题
比如我们有如下的代码:
1 2 3 4 5 6 7 8 9 10 11 12
| activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/activity_main" android:layout_width="match_parent" android:layout_height="match_parent"> <ListView android:id="@+id/listView" android:layout_width="match_parent" android:layout_height="match_parent"/> </RelativeLayout>
|
####MainActivity.java
1 2 3 4 5 6 7 8 9 10 11
| public class MainActivity extends Activity { ListView mListView; MyAdapter mMyAdapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mListView = (ListView) findViewById(R.id.listView); mMyAdapter = new MyAdapter(this); mListView.setAdapter(mMyAdapter); }}
|
####MyAdapter.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| public class MyAdapter extends BaseAdapter { private Context mContext; public MyAdapter(Context context) { this.mContext = context; } @Override public int getCount() { return 20; } @Override public Object getItem(int position) { return null; } @Override public long getItemId(int position) { return 0; } @Override public View getView(int position, View convertView, ViewGroup parent) { EditText editText; if (convertView == null) { editText = new EditText(mContext); convertView = editText; } else { editText = (EditText) convertView; } System.out.println("current pos:" + position); return convertView; }}
|
当你运行上述简单的代码后发现 EditText 是无法获取焦点的,导致无法输入任何东东,那么原因何在呢?
####其实,是 listview 先于子 item 抢占了焦点,那么我们首先想到的就是让 listview 失去焦点,让子 item 获取焦点(当然,listview 的 onitem 相关监听事件会失效)。
mListView.setFocusable(false);
这是再运行发现键盘弹出了,可是 editText 获取到焦点然后又失去了,需要你手动再次点击才能获取到,然后才能输入。
而且当你输入完毕,关闭软键盘,发现输入的东西不见了,自动清空。这又产生了两个问题。
第一个问题是 listview 每次调用 getview 都会使 EditText 失去焦点,第二个问题归结于下面要讲的 listview 的 item 复用产生的问题。
第一种方式行不通,查询相关资料发现,可以通过给 listview 的 item 的根布局设置 descendantFocusability 属性。
1 2 3 4
| #####android:descendantFocusability 属性有三个值: beforeDescendants:viewgroup 会优先其子类控件而获取到焦点 afterDescendants:viewgroup 只有当其子类控件不需要获取焦点时才获取焦点 blocksDescendants:viewgroup 会覆盖子类控件而直接获得焦点
|
####那么我们修改 adapter 中的 getView 方法
1 2 3 4 5 6 7 8 9 10 11 12 13
| @Override public View getView(int position, View convertView, ViewGroup parent) { EditText editText; if (convertView == null) { convertView = LayoutInflater.from(mContext).inflate(R.layout.list_edittext, parent, false); editText = (EditText) convertView.findViewById(R.id.editText); convertView.setTag(editText); } else { editText = (EditText) convertView.getTag(); } System.out.println("current pos:" + position); return convertView; }
|
####list_edittext.xml
1 2 3 4 5 6 7 8 9 10 11
| <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:descendantFocusability="beforeDescendants" android:orientation="vertical"> <EditText android:id="@+id/editText" android:layout_width="match_parent" android:layout_height="wrap_content"/> </LinearLayout>
|
发现还是无效果,其实我们少了一句关键的代码,就是给相应的 activity 设置 windowSoftInputMode= adjustPan
即可。。
##终上所述,我认为的解决方案就是给 ListView 或者 ListView 的 item 的根布局添加 android:descendantFocusability="beforeDescendants"
,然后设置相应的 activity 的 windowSoftInputMode 属性为 adjustPan 。
##2.数据问题
解决完焦点问题后,另一个问题就是 edittext 的数据问题了。当我们在当前屏幕的 edittext 中输入东东后,往下滑,发现下面的 edittext 自动输入了我们输入过得东东,这明显是我们不愿意看到的。
其实这是由于 getView 方法的复用 view 导致的,加入你在 position=0 的 edittext 中输入了内容,当你往下滑时,当 position 为 0 的 view 完全消失时,该 view 会被加入到 mActiveViews[]中,当下方的 item 检测到由可用的 view,则从该数组中取出,所以下方的 edittext 的内容会跟上面你输入的一样,其实就是同一个 edittext。关于 listview 源码级解析详见链接
#####解决方案——保存 edittext 的内容
修改 adapter 代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| //新增一个数组用于保存 edittext 的内容 private SparseArray<String> mStringSparseArray;
@Override public View getView(final int position, View convertView, ViewGroup parent) { EditTextHolder editTextHolder; if (convertView == null) { convertView = LayoutInflater.from(mContext).inflate(R.layout.list_edittext, parent, false);
editTextHolder = new EditTextHolder(); editTextHolder.mEditText = (EditText) convertView.findViewById(R.id.editText);
editTextHolder.mMyTextWatcher = new MyTextWatcher(position, mStringSparseArray); //给edittext设置watcher editTextHolder.mEditText.addTextChangedListener(editTextHolder.mMyTextWatcher);
convertView.setTag(editTextHolder);
} else { editTextHolder = (EditTextHolder) convertView.getTag(); //由于复用了 edittext,导致他的 watcher 里的 position 还是之前的 positiono,所以需要通知 //watcher 更新 positon,才能保存正确的 positon 的值 editTextHolder.updatePosition(position); } System.out.println(position); editTextHolder.mEditText.setText(mStringSparseArray.get(position)); return convertView; } static class EditTextHolder { EditText mEditText; MyTextWatcher mMyTextWatcher; public void updatePosition(int position) { mMyTextWatcher.updatePosition(position); } }
static class MyTextWatcher implements TextWatcher { private int position; private SparseArray<String> sparseArray; //更新 postion public void updatePosition(int position) { this.position = position; } public MyTextWatcher(int position, SparseArray<String> sparseArray) { this.position = position; this.sparseArray = sparseArray; } @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) { //保存 edittext 的值 sparseArray.put(position, s.toString()); } }
|
运行代码,发现 edittext 数据错乱问题解决,此方法同样适用于 checkbox 错乱等问题。