欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

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

程序员文章站 2024-03-02 21:24:46
前几天一同学项目中的某个功能需要listview+edittext来实现,希望我给他写个demo,自己就随手写了一个小的demo。后来想了想觉得这个功能其实挺常用的,而且期...

前几天一同学项目中的某个功能需要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);
 }

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。