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

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

一、编写布局文件
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;
 }
}

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

操作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;
 }

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

可以看到焦点丢失这个问题已经被我们解决了。接下来就让我们给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);
 }

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

(0)

相关推荐

  • Android 根据EditText搜索框ListView动态显示数据

    根据EditText搜索框ListView动态显示数据是根据需求来的,觉得这之中涉及的东西可能比较的有意思,所以动手来写一写,希望对大家有点帮助. 首先,我们来分析下整个过程: 1.建立一个layout,包含一个EditText搜索框和一个ListView 2.创建一个数据集mData,用于ListView的Adapter的创建 3.添加EditText的文本改变的监听器 4.利用notifyDataSetChanged()动态更新ListView 第一步:创建一个搜索框 这个还是比较容易的,这

  • edittext + listview 实现搜索listview中的内容方法(推荐)

    主要原理:是在主界面有两个空间,一个是EditText,一个是ListView,ListView是放在EditText下面的,然后自定义建立一个adapter适配器, 这个适配器要继承Filterable这个接口,并实现这个接口的两个方法,一个是过滤方法,一个是过滤后的方法,一般我们是在过滤方法里面实现过滤, 在过滤后的方法里面调用notifyDataSetChanged();也就是使适配器更新数据,这里还有用到一个pinyin4j-2.5.0.jar的架包,这个主要是汉字转拼 音的功能 Mai

  • Android编程使用ListView实现数据列表显示的方法

    本文实例讲述了Android编程使用ListView实现数据列表显示的方法.分享给大家供大家参考,具体如下: 要将数据库中的数据列表显示在屏幕上,我们要使用ListView这个控件,当用户从数据库中取出数据时,要将数据绑定到显示控件上,如何绑定呢,我们需要创建适配器进行绑定,创建适配器有两种方式: 第一种是用SimpleAdapter创建(要求绑定的数据是List<HashMap<String, Object>>数据类型) 第二种是用SimpleCursorAdapter创建(要求

  • Android ListView用EditText实现搜索功能效果

    前言 最近在开发一个IM项目的时候有一个需求就是,好友搜索功能.即在EditText中输入好友名字,ListView列表中动态展示刷选的好友列表.我把这个功能抽取出来了,先贴一下效果图: 分析 在查阅资料以后,发现其实Android中已经帮我们实现了这个功能,如果你的ListView使用的是系统的ArrayAdapter,那么恭喜你,下面的事情就很简单了,你只需要调用下面的代码就可以实现了: searchEdittext.addTextChangedListener(new TextWatche

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

    前几天一同学项目中的某个功能需要ListView+EditText来实现,希望我给他写个Demo,自己就随手写了一个小的Demo.后来想了想觉得这个功能其实挺常用的,而且期间也踩了几个坑,就整理了一下,希望能够帮到大家.好了,废话不多说了,接着就贴代码. 一.编写布局文件 1.activity的布局activity_main <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xml

  • C#中WPF ListView绑定数据的实例详解

    C#中WPF ListView绑定数据的实例详解 WPF中ListView用来显示数据十分方便, 我们可以将它分成几个列,每一个列用来显示一条数据,但是又是在一方之中. 那么怎样实现这样的效果的呢,这就要用绑定了. 我们先来看一看他的xmal代码 <ListView Name="receiveList" Grid.Row="0"> <ListView.View> <GridView> <GridView.Columns>

  • Android编程之ListView和EditText发布帖子隐藏软键盘功能详解

    本文实例讲述了Android编程之ListView和EditText发布帖子隐藏软键盘功能.分享给大家供大家参考,具体如下: 在Android开发中,手动调用软件盘的隐藏和显示有时候也是非常常见的需求. EditText控件实现了点击打开软键盘输入功能,but why ? 为什么EditText可以点击弹出keyboard,而TextView却不可以,EditText继承TextView做了哪些修改呢?关于这些问题得查看相关具体代码如何实现可以参考,看似简单的控件其实系统封装实现的很复杂.这里告

  • C# WPF ListView控件的实例详解

    C# WPF ListView控件的实例详解 C#的WPF作为现在微软主流的桌面程序开发平台,相比过去的MFC时代,有了非常多的不同.本人刚从MFC平台转过来,以为可以轻松上手,哪知碰到了很多问题,十分不解.不得不乖乖回去看了本书,再继续回到边左边边学的路上.在这边也推荐<深入浅出WPF>这本书,拿来上手还是极好的. 由于WPF以数据驱动UI的设计理念,很多控件用起来都与之前平台的相差很多,ListView控件算是有代表性的,这是进化的成果.关于该控件的应该,很多参考了这篇博文,如觉本人记述不

  • 详解Vue 实例中的生命周期钩子

    Vue 框架的入口就是 Vue 实例,其实就是框架中的 view model ,它包含页面中的业务处理逻辑.数据模型等,它的生命周期中有多个事件钩子,让我们在控制整个Vue实例的过程时更容易形成好的逻辑. Vue 实例 在文档中经常会使用 vm 这个变量名表示 Vue 实例,在实例化 Vue 时,需要传入一个选项对象,它可以包含数据(data).模板(template).挂载元素(el).方法(methods).生命周期钩子(lifecyclehook)等选项. Vue 实例化的选项 需要注意的

  • Vue实例中生命周期created和mounted的区别详解

    前言 本文主要跟大家介绍了关于Vue实例中生命周期created和mounted区别的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧. 生命周期先上图 什么是生命周期 Vue实例有一个完整的生命周期,也就是从开始创建.初始化数据.编译模板.挂载Dom.渲染→更新→渲染.卸载等一系列过程,我们称这是Vue的生命周期.通俗说就是Vue实例从创建到销毁的过程,就是生命周期. 在Vue的整个生命周期中,它提供了一系列的事件,可以让我们在事件触发时注册js方法,可以让我们用自己注

  • 浅谈C++ 类的实例中 内存分配详解

    一个类,有成员变量:静态与非静态之分:而成员函数有三种:静态的.非静态的.虚的. 那么这些个东西在内存中到底是如何分配的呢? 以一个例子来说明: #include"iostream.h" class CObject { public: static int a; CObject(); ~CObject(); void Fun(); private: int m_count; int m_index; }; VoidCObject::Fun(){ cout<<"Fu

  • vue实例中data使用return包裹的方法

    在简单的vue实例中看到的Vue实例中data属性是如下方式展示的: let app= newVue({ el:"#app", data:{ msg:'' }, methods:{ } }) 在使用组件化的项目中使用的是如下形式: export default{ data(){ return { showLogin:true, // def_act: '/A_VUE', msg: 'hello vue', user:'', homeContent: false, } }, method

  • 在实例中重学JavaScript事件循环

    单线程的JS 众所周知js是一门单线程语言,即同一时间只能做一件事.为什么js是单线程的呢,主要与它的用途有关. 作为浏览器脚本语言,js的主要用途是和用户互动&操作DOM,我们并不想并行的操作DOM.如果不是单线程的话,我们一个线程在给DOM节点上添加内容,另一个线程却删除了这个节点,到底该以哪个为准呢? 所以为了避免复杂性,从一诞生,JavaScript 就是单线程. 事件循环(event loop) JS是一门单线程语言,意味着代码要一行一行的执行.所有任务都要排队,前一个任务结束,才会执

  • Golang时间处理中容易踩的坑分析解决

    目录 简介 类型 时区 小心有坑 时间解析的使用场景 时间操作 获取当前时间 时区设置 时间格式化(时间类型转字符串) 时间类型转时间戳 时间戳转时间类型 时间字符串转时间类型 时间计算 获取时间类型具体内容 时间加减 时间间隔(耗时) 时间取整(向上取整向下取整) 拓展 json时间转换 简介 在各个语言之中都有时间类型的处理,因为这个地球是圆的(我仿佛在讲废话),有多个时区,每个时区的时间不一样,在程序中有必要存在一种方式,或者说一种类型存储时间,还可以通过一系列的方法转换成不同国家的时间.

随机推荐