Android:ListView中的EditText使用(解决EditText焦点丢失、自动调键盘、保存数据以及滚动冲突的问题)

 

在项目中有时会遇到在一个ListView中item中含有EditText和其他控件的一些列问题,这里就针对ListView中EditText焦点丢失、自动调键盘、保存数据以及滚动冲突的问题简单的介绍并总结一下,希望可以帮助大家:

一.相关布局文件和类文件:

1.activity布局



    

    

    

2.item的布局edittext_item



    
    

3.EditText的圆角矩形背景shape_edittext



    
    
    

4.MainActivity

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

    private ListView mListView;
    private List mList = new ArrayList(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);
    }
}

5.MyAdapter

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

    public MyAdapter(Context context, List 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;
    }
}

     (在edittext中输入完内容后,隐藏软键盘时,原来输入到edittext中的内容消失了就可以使用下面的方法解决)点击ListView中的某一条目的时候应该能明显看出来ListView移动了一下。软键盘弹出的时候会重新绘制界面,因此ListView进行了一次重新绘制,重新走了一边getView方法,生成了一个新的EditText,而之前展示光标的EditText被销毁,所以才造成了EditText的焦点丢失。

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

6.解决焦点问题处理:

 //定义成员变量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中的数据。其实保存数据比较简单,我们只需要做两步就可以了,第一步我们需要拿到EditText变化之后的数据;第二步我们将这些数据替换掉之前的就大功告成了。 
再次对MyAdapter类进行修改,而用于TextWatcher的afterTextChanged方法中获取不到当前position,所以我们需要新建一个内部类MyTextWatcher实现TextWatcher接口并持有一个position,其次在ViewHolder中需要持有一个MyTextWatcher的引用来动态更新其position的值:

7.保存数据处理:

@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());
        }
    };

8.解决滚动冲突问题:

@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);
    }

9.EditText自动调起键盘问题:

1).当一个ListView的item中含有抢占焦点的控件的时候,我们通常在item的布局中加上这样一句: 
android:descendantFocusability=”blocksDescendants” 
防止抢占焦点,大多数情况下都能够解决问题,但是如果是EditText的时候,就很坑了,你会发现软件盘都不能够弹出,更别说输入文字了,要解决这个问题,就是让点击EditText的时候,item最外层不拦截点击事件就ok了,点解item的时候,在拦截点击事件也同样ok了:

mHolder.edtInput.setOnTouchListener(new View.OnTouchListener() {

            @Override
            public boolean onTouch(View v, MotionEvent event) {
                ((ViewGroup) v.getParent())
                        .setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
                return false;
            }
        });

这个是EditText的时候的处理 
setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); 
这个很关键,而且是在OnTouchListener中执行的,因为在Android中View的触摸事件传递中,View的OnTouchListener调用是早于其他事件的,而且返回值false,代表目前事件没有被消耗,继续向下传递,不影响后续事件的处理(这里不影响EditText其他事件的处理)

convertView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                ((ViewGroup) v)
                        .setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
                Toast.makeText(mContext, "点击了" + mDatas.get(position), Toast.LENGTH_SHORT).show();
            }
        });

下面来看看适配器的主要逻辑:

public class NormalAdapter extends ArrayAdapter {
    private List mDatas;
    private Context mContext;

    public NormalAdapter(Context context, List objects) {
        super(context, R.layout.activity_list, objects);
        mDatas = objects;
        mContext = context;

    }

    @Override
    public View getView(final int position, View convertView,
            final ViewGroup parent) {
        Log.e("tag", parent.toString());
        Holder mHolder = null;
        if (convertView == null) {
            convertView = LayoutInflater.from(mContext).inflate(
                    R.layout.item_group, null);
            mHolder = new Holder();
            mHolder.tvTitle = (TextView) convertView
                    .findViewById(R.id.tvGroupTitle);
            mHolder.edtInput = (EditText) convertView
                    .findViewById(R.id.edtGroupContent);
            convertView.setTag(mHolder);
        } else {
            mHolder = (Holder) convertView.getTag();
        }
        mHolder.tvTitle.setText(mDatas.get(position));
        mHolder.edtInput.setOnTouchListener(new View.OnTouchListener() {

            @Override
            public boolean onTouch(View v, MotionEvent event) {
                ((ViewGroup) v.getParent())
                        .setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
                return false;
            }
        });
       convertView.setOnTouchListener(new View.OnTouchListener() {
                @Override
                public boolean onTouch(View v, MotionEvent event) {
                    ((ViewGroup) v)
                     .setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
                    Toast.makeText(mContext, "点击了" + mDatas.get(position), Toast.LENGTH_SHORT).show();
                    return false;
                }
            });
        return convertView;
    }

    private static final class Holder {
        TextView tvTitle;
        EditText edtInput;
    }

}

2).在开发过程中,我们经常会碰到Activity中包含EditText控件时会自动弹出虚拟键盘的情形,其实这是由于EditText自动获得焦点的缘故,只要让EditText失去焦点就行了,解决办法如下:

a.在Manifest.xml文件中相应的Activity下添加如下代码即可:

android:windowSoftInputMode=”stateHidden”

b.让EditText失去焦点,用EditText的clearFocus即可:

EditText edit = (EditText)findViewById(R.id.edit); 
edit.clearFocus();

c.强制隐藏Android输入法窗口

EditText edit = (EditText)findViewById(R.id.edit); 
InputMethodManager imm = 
(InputMethodManager)getSystemService(INPUT_METHOD_SERVICE); 
imm.hideSoftInputFromWindow(edit.getWindowToken(), 0);

d.EditText始终不弹出虚拟键盘

EditText edit = (EditText)findViewById(R.id.edit); 
edit.setInputType(InputType.TYPE_NULL);

但有时,我们确实是想让EditText自动获得焦点并弹出软键盘,在设置了EditText自动获得焦点后,软件盘不会弹出。注意:此时是由于刚跳到一个新的界面,界面未加载完全而无法弹出软键盘。此时应该适当的延迟弹出软键盘,如500毫秒(保证界面的数据加载完成,如果500毫秒仍未弹出,则延长至1000毫秒)。可以在EditText后面加上一段代码,实例代码如下:

Timer timer = new Timer();  
timer.schedule(new TimerTask() {  

public void run() {  
    InputMethodManager inputManager = (InputMethodManager) editText.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);  
    inputManager.showSoftInput(editText, 0);  
}  

}, 500); 

也可以给activity配置加入属性android:windowSoftInputMode=”adjustResize”。

3).设置EditText自动获取焦点并弹出输入法

// 获取编辑框焦点

editText.setFocusable(true);

//打开软键盘

InputMethodManager imm = (InputMethodManager)MainActivity.this.getSystemService(Context.INPUT_METHOD_SERVICE);

imm.toggleSoftInput(0, InputMethodManager.HIDE_NOT_ALWAYS);

//关闭软键盘

imm.hideSoftInputFromWindow(editText.getWindowToken(), 0);

4).方法四:

InputMethodManager imm = (InputMethodManager) getApplicationContext().getSystemService(Context.INPUT_METHOD_SERVICE);
                imm.toggleSoftInput(0, InputMethodManager.HIDE_NOT_ALWAYS);
private void listenerSoftInput() {
        final View activityRootView = findViewById(R.id.baojia);
        activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                int heightDiff = activityRootView.getRootView()
                        .getHeight() - activityRootView.getHeight();
                if (heightDiff > 100) { // 如果高度差超过100像素,就很有可能是有软键盘...
                    Log.e("软键盘弹出了", "软键盘弹出");
                    confim.setVisibility(View.GONE);
                } else if (heightDiff < 100) {
                    Log.e("软键盘弹出了", "软键盘没有弹出");
                    confim.setVisibility(View.VISIBLE);
                    //new Thread(Confim).start();
                }
            }
        });
    }

10.ListView被键盘遮挡的问题处理:

用ListView或者其它显示大量Items的控件实时跟踪或者查看信息,并且希望最新的条目可以自动滚动到可视范围内。通过设置控件transcriptMode属性可以将Android平台的控件(支持ScrollBar)自动滑动到最底部。

1).方法一:

  android:transcriptMode="alwaysScroll"  

2).方法二:

  

 

你可能感兴趣的:(Android控件的功能)