ListView和EditText使用解决方案

来源:互联网 发布:nginx 404 配置 编辑:程序博客网 时间:2024/05/16 09:46

ListView的复用对于EditText的坑有不少,比如焦点丢失、值乱窜、滚动问题。本文通过两种方案来解决:

一、老老实实使用ListView,然后把坑踩平。

1、焦点问题

该问题主要体现在于,点击EditText的时候键盘弹出,但是输入却没有任何反应,需要再点击一次才能输入数据。产生的原因在于弹出键盘的时候触发了ListView的刷新,导致本来获取了焦点的EditText又失去了焦点。这个坑我曾在4.4机器上踩平过,5.0之后焦点的获取机制不一样,在我的项目中后改为了方案二来实现,因此没有仔细研究。

//Android 4.4 代码 private int touchPosition = -1@Overridepublic View getView(final int position, View convertView, ViewGroup parent) {        ViewHolder holder = ViewHolder.get(context, convertView, null, R.layout.layout_item, position);        final Bean bean = getItem(position);        ((TextView) holder.getView(R.id.tv_id)).setText(bean.id + "");        EditText etName = holder.getView(R.id.et_name);        etName.setOnTouchListener(new View.OnTouchListener() {            @Override            public boolean onTouch(View v, MotionEvent event) {                touchPosition = position;                return false;            }        });        etName.setText(bean.name);        if (touchPosition == position) {            etName.requestFocus();            etName.setSelection(etName.length());        } else etName.clearFocus();        return holder.getRootView();    }

思路简单粗暴,也就是给编辑框增加一个onTouch监听,当ListView发生刷新的时候重新设置焦点,但这个方案在5.0机器及以上失效。

package com.wastrel.edittext;import android.content.Context;import android.util.SparseArray;import android.view.LayoutInflater;import android.view.View;import android.view.ViewGroup;/** * 创建一个viewHolder */public class ViewHolder {    public static final String TAG = "ViewHolder";    private SparseArray<View> childViews;    private View rootView;    private int position;    private Object tag;    public void setTag(Object tag) {        this.tag = tag;    }    public Object getTag() {        return tag;    }    /**     * 生成一个adapter的ViewHolder     *     * @param context     * @param parent     * @param layoutId     * @param position     */    private ViewHolder(Context context, ViewGroup parent, int layoutId, int position) {        this.rootView = LayoutInflater.from(context).inflate(layoutId, parent, false);        this.childViews = new SparseArray<>();        this.position = position;        rootView.setTag(this);    }    /**     * 获取一个viewHolder     *     * @param context     * @param parent     * @param layoutId     * @param attachToRoot 是否添加到parent中     */    public ViewHolder(Context context, ViewGroup parent, int layoutId, boolean attachToRoot) {        this.rootView = LayoutInflater.from(context).inflate(layoutId, parent, attachToRoot);        this.childViews = new SparseArray<>();        rootView.setTag(this);    }    /**     * 获得一个viewHolder     *     * @param context     * @param convertView     * @param parent     * @param layoutId     * @param position     * @return     */    public static ViewHolder get(Context context, View convertView, ViewGroup parent, int layoutId, int position) {        ViewHolder holder;        if (null == convertView) {            holder = new ViewHolder(context, parent, layoutId, position);        } else {            holder = (ViewHolder) convertView.getTag();        }        return holder;    }    /**     * 获取根容器     *     * @return     */    public View getRootView() {        return this.rootView;    }    /**     * 获取容器中的某个控件     *     * @param id     * @param <T>     * @return     */    @SuppressWarnings("unchecked")    public <T extends View> T getView(int id) {       View view =childViews.get(id);        if (view!=null)        {            return (T)view;        }        view=rootView.findViewById(id);        if (null == view) {            throw new IllegalArgumentException("没有找到id为" + rootView.getContext().getResources().getResourceEntryName(id) + "的控件");        } else {            childViews.put(id, view);            return (T) view;        }    }}

2、值乱窜的问题

解决这个问题很容易联想到使用TextWatcher。对每个EditText增加一个TextWatcher在用户输入的时候去更新数据源得值,下次刷新的时候在set回去即可。这里为了节省篇幅,仅贴出关键代码。

 @Overridepublic View getView(final int position, View convertView, ViewGroup parent) {        ViewHolder holder = ViewHolder.get(context, convertView, null, R.layout.layout_item, position);        final Bean bean = getItem(position);        ((TextView) holder.getView(R.id.tv_id)).setText(bean.id + "");        EditText etName = holder.getView(R.id.et_name);        //这段代码主要是确保EditText只持有一个TextWatcher,因为如果每次都使用add会导致EditText持有很多TextWatcher,一旦文字发生变化,将会触发其所有的TextWatcher,这样一来不但没有解决问题,反而使问题更加严重。        MyTextWatcher textWatcher = (MyTextWatcher) etName.getTag();        if (textWatcher == null) {            textWatcher = new MyTextWatcher();            etName.addTextChangedListener(textWatcher);            etName.setTag(textWatcher);        }        //修正当前EditText应该绑定的对象。        textWatcher.update(bean);        etName.setText(bean.name);        return holder.getRootView();    }class MyTextWatcher implements TextWatcher {        private Bean bean;        public void update(Bean bean) {            this.bean = bean;        }        @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) {            bean.name = s.toString();        }    }

二、使用ScrollVIew+LinearLayout替代ListView

为什么要使用LinearLayout来替代ListVIew?

ListView与EditText组合会频繁的引起ListView刷新,在弹出键盘的过程中,ListView会刷新3-4次,当EditText编辑框变化时也会触发刷新,浪费性能。而且EditText的各种状态在刷新过程中会出现乱窜或丢失,颇为头疼的焦点问题。如果使用LinearLayout这些问题都将迎刃而解。

但是问题来了LinearLayout可没有ListView的Adapter好使啊!下面我们可以给LinearLayout模拟一个Adapter出来,方便使用。见代码:

package com.wastrel.edittext;import android.content.Context;import android.view.View;import android.view.ViewGroup;import android.widget.LinearLayout;import java.util.List;import java.util.Stack;/** * 基础adapter<br/> * 所有子类必须实现{@link #convert(ViewHolder, Object, int)}<br/> */public abstract class BaseLinearLayoutAdapter<T> extends android.widget.BaseAdapter {    public List<T> data;    public Context context;    private int layoutId;    Stack<View> detachViews = new Stack<>();    public LinearLayout container;    public BaseLinearLayoutAdapter(Context context, List<T> data, LinearLayout container, int layoutId) {        this.context = context;        this.data = data;        this.container = container;        container.removeAllViews();        this.layoutId = layoutId;    }    @Override    public int getCount() {        return null == data ? 0 : data.size();    }    @Override    public T getItem(int position) {        return null == data ? null : data.get(position);    }    @Override    public long getItemId(int position) {        return 0;    }    public void setList(List<T> data) {        this.data = data;        notifyDataSetChanged();    }    @Override    public View getView(int position, View convertView, ViewGroup parent) {        ViewHolder holder = ViewHolder.get(context, convertView, parent, layoutId, position);         T t =  getItem(position);        convert(holder, t, position);        return holder.getRootView();    }    /**     * 实现数据赋值     *     * @param holder     * @param item     * @param position     */    public abstract void convert(ViewHolder holder, T item, int position);   //重新notifyDataSetChanged()来满足LinearLayout。    @Override    public void notifyDataSetChanged() {        //获取当前容器里面还有多少个可用View        int viewCount = container.getChildCount();        int size = getCount();        for (int i = 0; i < size; i++) {            if (i < viewCount) {                //需要显示的个数小于当前容器里面的个数的时候,直接取出来重新赋值即可。                getView(i, container.getChildAt(i), container);            } else {                //当大于的时候先从缓存里面取,如果没有执行getView(i,null,container)去创建一个。                View v = null;                if (detachViews.size() > 0) {                    v = detachViews.get(0);                    detachViews.remove(0);                }                v = getView(i, v, container);                container.addView(v);            }        }        //把容器里没有用到的View取出来放到缓存中。        if (viewCount > size) {            for (int i = viewCount - 1; i >= size; i--) {                detachViews.add(container.getChildAt(i));                container.removeViewAt(i);            }        }    }}

上面这个Adapter简单的重写了notifyDataSetChange()来模拟ListView刷新的过程。

使用

布局应满足:如果使用ScrollView则LinearLayout的方向应该是纵向的,如果使用HorizontalScrollView则LinearLayout的方向应该是横向的。

<!--纵向的时候--><ScrollView    android:layout_width="match_parent"    android:layout_height="wrap_content">    <LinearLayout        android:layout_width="match_parent"        android:orientation="vertical"        android:layout_height="match_parent"/></ScrollView>
public class LinearAdapter extends BaseLinearLayoutAdapter<Bean> {    public LinearAdapter(Context context, List<Bean> data, LinearLayout container, int layoutId) {        super(context, data, container, layoutId);    }    @Override    public void convert(ViewHolder holder, Bean bean, int position) {        ((TextView) holder.getView(R.id.tv_id)).setText(bean.id + "");        EditText etName = holder.getView(R.id.et_name);        MyTextWatcher textWatcher = (MyTextWatcher) etName.getTag();        if (textWatcher == null) {            textWatcher = new MyTextWatcher();            etName.addTextChangedListener(textWatcher);            etName.setTag(textWatcher);        }        textWatcher.update(bean);        etName.setText(bean.name);    }    class MyTextWatcher implements TextWatcher {        private Bean bean;        public void update(Bean bean) {            this.bean = bean;        }        @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) {            bean.name = s.toString();        }    }}
 LinearAdapter adapter = new LinearAdapter(this, beans, listView, R.layout.layout_item);adapter.notifyDataSetChanged();

数据集变化的时候调用Adapter的notifyDataSetChanged()就好了。

上述方案适用于Item条数不多,并且用户可动态添加和删除条目的情况。动态删减的过程中任然避免不了通过TextWatcher来快速保存数据。但是此方案不会存在焦点问题,列表也不会反复刷新。如果条目固定的输入直接用for循环就好了。然后把ViewHolder缓存起来,就可以解决大部分问题。

1 0
原创粉丝点击