小心!Listview结合EditText使用实例中遇到的那些坑

作者:曾博文 时间:2022-07-08 18:39:34 

前几天一同学项目中的某个功能需要ListView+EditText来实现,希望我给他写个Demo,自己就随手写了一个小的Demo。后来想了想觉得这个功能其实挺常用的,而且期间也踩了几个坑,就整理了一下,希望能够帮到大家。好了,废话不多说了,接着就贴代码。

小心!Listview结合EditText使用实例中遇到的那些坑

一、编写布局文件
1.activity的布局activity_main


<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.example.mytestdemo.MainActivity" >

<ListView
android:id="@+id/list_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>

</RelativeLayout>

只有一个ListView,所以就不多说了。
2.item的布局edittext_item


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/item_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="20dp"
android:paddingBottom="20dp"
android:orientation="horizontal" >
<TextView
android:id="@+id/text_view"
android:layout_width="wrap_content"
android:layout_gravity="center_vertical"
android:gravity="center_vertical"
android:layout_height="50dp"/>
<EditText
android:id="@+id/edit_text"
android:layout_gravity="center_vertical"
android:layout_marginLeft="30dp"
android:layout_width="match_parent"
android:layout_height="100dp"
android:gravity="top"
android:padding="5dp"
android:background="@drawable/shape_edittext"/>

</LinearLayout>

一个水平的LinearLayout,里面有一个TextView和一个EditText。
为了稍微好看那么一点,所以给EditText加了一个圆角矩形背景。

3.EditText的圆角矩形背景shape_edittext


<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" >
<solid android:color="#FFFFFFFF"/>
<stroke android:width="1dp"
android:color="#000000"/>
<corners android:radius="5dp"/>
</shape>

OK,布局代码已经贴完了,接下来就看看咱们的逻辑代码吧。

二、编写MainActivity类


public class MainActivity extends Activity {
private static final String TAG = "zbw";
private static final int DATA_CAPACITY = 20;

private ListView mListView;
private List<String> mList = new ArrayList<String>(DATA_CAPACITY);
private MyAdapter mAdapter;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mListView = (ListView) findViewById(R.id.list_view);

//填充数据
for(int i = 0; i < DATA_CAPACITY; i++) {
mList.add("" + i);
}

//设置Adapter
mAdapter = new MyAdapter(this, mList);
mListView.setAdapter(mAdapter);
}
}

可以看到MainActivity的代码逻辑页比较简单,主要操作就是生成了一个长度为20的List,然后将其作为数据源扔进Adapter里面。好了,接下来就让我们一起来看一下最后的Adapter类。

三、编写MyAdapter类
好了,终于到了重头戏,接下来咱们就一步步的编写Adapter来解决ListView和EditText的各种冲突。
1.最普通的Adapter
首先咱们先按照以往的经验写一个最普通的Adapter,看一下会出现哪些问题:


public class MyAdapter extends BaseAdapter {
private ViewHolder mViewHolder;
private LayoutInflater mLayoutInflater;
private List<String> mList;

public MyAdapter(Context context, List<String> list) {
mLayoutInflater = LayoutInflater.from(context);
mList = list;
}

@Override
public int getCount() {
return mList.size();
}

@Override
public Object getItem(int position) {
return mList.get(position);
}

@Override
public long getItemId(int position) {
return position;
}

@Override
public View getView(final int position, View convertView, ViewGroup parent) {
if (convertView == null) {
mViewHolder = new ViewHolder();
convertView = mLayoutInflater.inflate(R.layout.edittext_item, null);
mViewHolder.mTextView = (TextView) convertView.findViewById(R.id.text_view);
mViewHolder.mEditText = (EditText) convertView.findViewById(R.id.edit_text);
convertView.setTag(mViewHolder);
} else {
mViewHolder = (ViewHolder) convertView.getTag();
}

if (position <= 9) {
mViewHolder.mTextView.setText("0" + (position));
} else {
mViewHolder.mTextView.setText("" + (position));
}
mViewHolder.mEditText.setText(mList.get(position));
return convertView;
}

static final class ViewHolder {
TextView mTextView;
EditText mEditText;
}
}

代码如上,相信大家都写过无数遍这样类似的代码,让我们一起看一下这段代码会出什么问题。运行效果如图所示:

小心!Listview结合EditText使用实例中遇到的那些坑

操作a:点击“0”,光标定位到“0”,弹出软键盘,“0”处的光标丢失;
操作b:再次点击“0”,光标重新定位到“0”;
之后我又重复了一遍此步骤,不过点击的是“1”的位置。
那么为什么会出现这种效果呢?点击“0”的时候大家看的可能不是太明显,但点击“1”的时候大家应该能明显看出来ListView移动了一下。没错,正如大家所知道的那样,软键盘弹出的时候会重新绘制界面,因此ListView进行了一次重新绘制,重新走了一边getView方法,生成了一个新的EditText,而之前展示光标的EditText被销毁,所以才造成了EditText的焦点丢失。既然我们已经知道了这个问题的原因,那么接下来我们就来解决掉它吧。

2.解决焦点丢失的问题
解决思路:既然焦点丢失是因为ListView的重绘导致的,那我们就可以定义一个变量mTouchItemPosition来记录用户触碰的EditText的位置,然后在getView方法中去判断当前的position是否和用户触碰的位置相等,如果相等则让其获得焦点,否则清除焦点。而mTouchItemPosition的值可以在EditText的OnTouch事件中获取。
代码实现:


//定义成员变量mTouchItemPosition,用来记录手指触摸的EditText的位置
private int mTouchItemPosition = -1;
...
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
mViewHolder = new ViewHolder();
convertView = mLayoutInflater.inflate(R.layout.edittext_item, null);
mViewHolder.mTextView = (TextView) convertView.findViewById(R.id.text_view);
mViewHolder.mEditText = (EditText) convertView.findViewById(R.id.edit_text);

mViewHolder.mEditText.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent event) {
 //注意,此处必须使用getTag的方式,不能将position定义为final,写成mTouchItemPosition = position
 mTouchItemPosition = (Integer) view.getTag();
 return false;
}
});

convertView.setTag(mViewHolder);
} else {
mViewHolder = (ViewHolder) convertView.getTag();
}

if (position <= 9) {
mViewHolder.mTextView.setText("0" + (position));
} else {
mViewHolder.mTextView.setText("" + (position));
}
mViewHolder.mEditText.setText(mList.get(position));

mViewHolder.mEditText.setTag(position);

if (mTouchItemPosition == position) {
mViewHolder.mEditText.requestFocus();
mViewHolder.mEditText.setSelection(mViewHolder.mEditText.getText().length());
} else {
mViewHolder.mEditText.clearFocus();
}

return convertView;
}

让我们重新运行看一下效果:

小心!Listview结合EditText使用实例中遇到的那些坑

可以看到焦点丢失这个问题已经被我们解决了。接下来就让我们给EditText增加保存数据的功能。

3.添加保存数据的功能
首先让我们来分析一下怎么保存EditText中的数据。其实保存数据比较简单,我们只需要做两步就可以了,第一步我们需要拿到EditText变化之后的数据;第二步我们将这些数据替换掉之前的就大功告成了。
让我们再次对MyAdapter类进行修改,而用于TextWatcher的afterTextChanged方法中获取不到当前position,所以我们需要新建一个内部类MyTextWatcher实现TextWatcher接口并持有一个position,其次在ViewHolder中需要持有一个MyTextWatcher的引用来动态更新其position的值,代码如下:


@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
mViewHolder = new ViewHolder();
convertView = mLayoutInflater.inflate(R.layout.edittext_item, null);
mViewHolder.mTextView = (TextView) convertView.findViewById(R.id.text_view);
mViewHolder.mEditText = (EditText) convertView.findViewById(R.id.edit_text);

mViewHolder.mEditText.setOnTouchListener(new OnTouchListener() {

@Override
public boolean onTouch(View view, MotionEvent event) {
 //注意,此处必须使用getTag的方式,不能将position定义为final,写成mTouchItemPosition = position
 mTouchItemPosition = (Integer) view.getTag();
 return false;
}
});

// 让ViewHolder持有一个TextWathcer,动态更新position来防治数据错乱;不能将position定义成final直接使用,必须动态更新
mViewHolder.mTextWatcher = new MyTextWatcher();
mViewHolder.mEditText.addTextChangedListener(mViewHolder.mTextWatcher);
mViewHolder.updatePosition(position);

convertView.setTag(mViewHolder);
} else {
mViewHolder = (ViewHolder) convertView.getTag();
//动态更新TextWathcer的position
mViewHolder.updatePosition(position);
}

if (position <= 9) {
mViewHolder.mTextView.setText("0" + (position));
} else {
mViewHolder.mTextView.setText("" + (position));
}
mViewHolder.mEditText.setText(mList.get(position));

mViewHolder.mEditText.setTag(position);

if (mTouchItemPosition == position) {
mViewHolder.mEditText.requestFocus();
mViewHolder.mEditText.setSelection(mViewHolder.mEditText.getText().length());
} else {
mViewHolder.mEditText.clearFocus();
}

return convertView;
}

static final class ViewHolder {
TextView mTextView;
EditText mEditText;
MyTextWatcher mTextWatcher;

//动态更新TextWathcer的position
public void updatePosition(int position) {
mTextWatcher.updatePosition(position);
}
}

class MyTextWatcher implements TextWatcher {
//由于TextWatcher的afterTextChanged中拿不到对应的position值,所以自己创建一个子类
private int mPosition;

public void updatePosition(int position) {
mPosition = position;
}

@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {

}

@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {

}

@Override
public void afterTextChanged(Editable s) {
mList.set(mPosition, s.toString());
}
};

现在保存数据的问题也已经完成了,接下来让我们看最后一个滚动冲突的问题。

4.解决滚动冲突的问题
其实这个问题我在完美解决EditText和ScrollView的滚动冲突(上)和 完美解决EditText和ScrollView的滚动冲突(下)这两篇博客中详细的讲过,原理都是一样的,所以这儿就不多说了,直接将原来的代码拷过来就可以了。感兴趣的同学可以去看一下之前的两篇。


@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
mViewHolder = new ViewHolder();
convertView = mLayoutInflater.inflate(R.layout.edittext_item, null);
mViewHolder.mTextView = (TextView) convertView.findViewById(R.id.text_view);
mViewHolder.mEditText = (EditText) convertView.findViewById(R.id.edit_text);

mViewHolder.mEditText.setOnTouchListener(new OnTouchListener() {

@Override
public boolean onTouch(View view, MotionEvent event) {
 //注意,此处必须使用getTag的方式,不能将position定义为final,写成mTouchItemPosition = position
 mTouchItemPosition = (Integer) view.getTag();

//触摸的是EditText并且当前EditText可以滚动则将事件交给EditText处理;否则将事件交由其父类处理
 if ((view.getId() == R.id.edit_text && canVerticalScroll((EditText)view))) {
 view.getParent().requestDisallowInterceptTouchEvent(true);
 if (event.getAction() == MotionEvent.ACTION_UP) {
 view.getParent().requestDisallowInterceptTouchEvent(false);
 }
 }
 return false;
}
});

// 让ViewHolder持有一个TextWathcer,动态更新position来防治数据错乱;不能将position定义成final直接使用,必须动态更新
mViewHolder.mTextWatcher = new MyTextWatcher();
mViewHolder.mEditText.addTextChangedListener(mViewHolder.mTextWatcher);
mViewHolder.updatePosition(position);

convertView.setTag(mViewHolder);
} else {
mViewHolder = (ViewHolder) convertView.getTag();
//动态更新TextWathcer的position
mViewHolder.updatePosition(position);
}

if (position <= 9) {
mViewHolder.mTextView.setText("0" + (position));
} else {
mViewHolder.mTextView.setText("" + (position));
}
mViewHolder.mEditText.setText(mList.get(position));

mViewHolder.mEditText.setTag(position);

if (mTouchItemPosition == position) {
mViewHolder.mEditText.requestFocus();
mViewHolder.mEditText.setSelection(mViewHolder.mEditText.getText().length());
} else {
mViewHolder.mEditText.clearFocus();
}

return convertView;
}

/**
* EditText竖直方向是否可以滚动
* @param editText 需要判断的EditText
* @return true:可以滚动 false:不可以滚动
*/
private boolean canVerticalScroll(EditText editText) {
//滚动的距离
int scrollY = editText.getScrollY();
//控件内容的总高度
int scrollRange = editText.getLayout().getHeight();
//控件实际显示的高度
int scrollExtent = editText.getHeight() - editText.getCompoundPaddingTop() -editText.getCompoundPaddingBottom();
//控件内容总高度与实际显示高度的差值
int scrollDifference = scrollRange - scrollExtent;

if(scrollDifference == 0) {
return false;
}

return (scrollY > 0) || (scrollY < scrollDifference - 1);
}

标签:Listview,EditText
0
投稿

猜你喜欢

  • 详解Asp.Net MVC的Bundle捆绑

    2021-09-10 05:21:38
  • 详解Spring Boot 项目部署到heroku爬坑

    2021-05-28 06:21:07
  • Templates实战之更优雅实现自定义View构造方法详解

    2021-12-22 13:22:40
  • C#判断字符串是否存在字母及字符串中字符的替换实例

    2022-04-15 03:49:48
  • java Socket实现网页版在线聊天

    2022-10-19 12:13:42
  • Java实现RedisUtils操作五大集合(增删改查)

    2023-07-13 06:33:14
  • IDEA下Maven的pom文件导入依赖出现Auto build completed with errors的问题

    2023-04-06 07:23:31
  • Java中数组在内存中存放原理的讲解

    2022-12-10 03:48:35
  • java开发微信分享接口的步骤

    2021-08-22 12:30:59
  • Java数据结构学习之树

    2022-01-19 23:40:58
  • C#.net实现在Winform中从internet下载文件的方法

    2023-09-13 18:04:15
  • C#中Dictionary泛型集合7种常见的用法

    2021-08-07 04:47:17
  • WinForm子窗体访问父窗体控件的实现方法

    2021-10-12 17:32:21
  • java利用java.net.URLConnection发送HTTP请求的方法详解

    2022-10-18 14:02:58
  • @Autowired注解注入的xxxMapper报错问题及解决

    2022-10-01 10:31:02
  • javascript 在线文本编辑器实现代码

    2023-11-24 23:07:24
  • Ireport的安装与使用教程

    2021-08-08 00:16:43
  • java多线程中执行多个程序的实例分析

    2023-03-11 18:21:58
  • java 利用反射获取内部类静态成员变量的值操作

    2023-03-28 21:30:04
  • 一文带你搞懂Java中的递归

    2022-10-08 07:34:04
  • asp之家 软件编程 m.aspxhome.com