ListView与带EditText的item冲突问题

本文探讨了在Android应用中ListView与EditText复用时产生的焦点与数据状态保存问题。作者分享了一个不复用convertView的解决方案,并提供了适配器代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

注意:在复用情况下冲突问题无法解决

网上所有的可能性与实现方式我都实验了一边,亲测当动态添加带有EditText的item时,如果使用了复用的方式,无论怎么处理ListView与EditText始终存在冲突。最后我的解决方式只有采用不复用的方式来处理,但滑动存在卡顿需要寻找其他实现方式,这里只是我比较蠢的一种解决方案。

问题

出现的问题主要是,焦点与数据状态保存问题。

分析

大家都知道,ListView的convertView其实始终只有第一屏的那几个,如果说你采用了复用的方式,滑动ListView则其他屏幕显示的item仍旧是第一屏的convertView,只是通过不停的在getView中反复赋值来让item显示相同或不同的效果。

这里需要做个解释,软键盘的伸缩,针对EditText的文字输入等都会造成ListView的重绘,getView方法不断执行,并且,ListView重绘会造成EditText的焦点变换,所以控制起来十分的棘手。

网上的操作是,针对EditText进行文字输入监听,通过保存输入完毕后的内容来重新赋值。有的使用的则是触摸监听来获取position等。这些方式,只要是不重绘的页面上都是正常显示的,也可正常保存数据。一旦复用,数据保存就会错乱,因为重绘操作会不断的走到这些监听里面,造成position的错乱导致存储的数据与position对不上。并且,我的需求是一个动态需求,如图所示:

0

点击新增一条的时候,新增一条输入框,新增的输入框获取到焦点并弹出键盘。点击删除的时候删除当前item并且保持别的EditText的数据状态不变。

实现起来真是巨麻烦,大概花了一天半的时间解决这个问题,但是还是没有达到最优化。但是我觉得如果你要复用的话那么这个问题应该不能解决。因为牵扯到EditText的焦点变化、文字输入以及键盘的弹出收起等都会导致重绘,你很难去把握这个焦点到底跳到哪里去了。

好那么我们只要看一个文件就好:适配器文件MainAdapter


public class MainAdapter extends BaseAdapter {

    private ArrayList<String> dataList;
    private EditTextWater watcher;
    private int add_tag = -1;//用于控制滑动状态输入法会弹起的bug

    public MainAdapter(ArrayList<String> dataList) {
        this.dataList = dataList;
    }

    /**
     * 手动新增一条数据
     *
     * @param sn
     * @param listView
     */
    public void addData(String sn, ListView listView) {
        add_tag = 1;
        dataList.add(sn);
        notifyDataSetChanged();
        listView.smoothScrollToPosition(getCount() - 1);//让ListView滚动到底部使新增的item可以看到
    }

    /**
     * 手动删除一条数据
     *
     * @param position
     */
    public void removeData(int position) {
        dataList.remove(position);
        notifyDataSetChanged();
    }

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

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

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

    @Override
    public View getView(final int position, View convertView, final ViewGroup parent) {
        final ViewHolder holder = new ViewHolder();
        convertView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_edittext, parent, false);
        holder.tvNum = (TextView) convertView.findViewById(R.id.tv_num);
        holder.etNewAdd = (EditText) convertView.findViewById(R.id.et_new_add);
        holder.tvChangeTitle = (TextView) convertView.findViewById(R.id.tv_change_title);
        convertView.setTag(holder);
        watcher = new EditTextWater(position);
        holder.etNewAdd.addTextChangedListener(watcher);
        holder.etNewAdd.setText(dataList.get(position));
        holder.tvNum.setText(position + 1 + "");
        holder.etNewAdd.setSelection(dataList.get(position).length());//设置光标问题
        holder.tvChangeTitle.setText(R.string.delete);
        holder.tvChangeTitle.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                holder.etNewAdd.removeTextChangedListener(watcher);
                removeData(position);
            }
        });
        /**
         * 当前这里手动获取焦点
         * 但是实际输入法没有在这里弹出
         * 因此如果需要弹出输入法的时候就需要手动写一个
         */
        if (position == getCount() - 1 && add_tag == 1) {
            add_tag = -1;
            holder.etNewAdd.requestFocus();
            holder.etNewAdd.post(new Runnable() {
                @Override
                public void run() {
                    InputMethodManager imm = (InputMethodManager) parent.getContext().getSystemService(INPUT_METHOD_SERVICE);
                    imm.showSoftInput(holder.etNewAdd, InputMethodManager.SHOW_IMPLICIT);
                }
            });
        } else {
            holder.etNewAdd.clearFocus();
        }
        return convertView;
    }

    /**
     * 重新文字监听器
     * 保存当前输入的信息以保证滑动的时候EditText显示的文字同步
     */
    class EditTextWater implements TextWatcher {

        private int position;

        public EditTextWater(int position) {
            this.position = position;
        }

        @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) {
            Log.i("usher", "afterTextChanged: " + position + "-" + s);
            dataList.set(position, s.toString());
        }
    }

    class ViewHolder {
        TextView tvNum;
        EditText etNewAdd;
        TextView tvChangeTitle;
    }
}

有几个地方比较难:
- 重写文字改变的监听,用于保存你最后一次输入的数据;
- 去除文字改变的监听,用于避免反复绘制item造成的position与数据不匹配;
- 针对新增的item的EditText中弹起键盘的监听与滑动时针对键盘不弹出的控制;

大家可以尝试使用复用convertView的方式来写ListView,你会发现,当绘制item超过一屏的时候,超出的部分中重绘的item中EditText所拿到的数据并不是上一次的数据了,因为反复绘制的时候导致了文字改变的监听始终执行,虽然有规律可循,但是我没有找到控制这一现象的方法。

最后贴上首页的代码:


public class MainActivity extends AppCompatActivity {

    private ListView lvMain;
    private MainAdapter adapter;
    private Button btn_add_new;
    private ArrayList<String> list;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_home);
        initView();
        initData();
    }

    private void initData() {
        list = getData();
        adapter = new MainAdapter(list);
        lvMain.setAdapter(adapter);
        btn_add_new.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                adapter.addData("", lvMain);
            }
        });
    }

    private void initView() {
        lvMain = (ListView) findViewById(R.id.lv_main);
        btn_add_new = (Button) findViewById(R.id.btn_add_new);
    }

    private ArrayList<String> getData() {
        ArrayList<String> list = new ArrayList<>();
        for (int i = 0; i < 2; i++) {
            list.add("input data from" + i);
        }
        return list;
    }
}

结语

以上方式的处理方法比较笨,同时还有另外一种处理方式,就是再ListView的外层套上一层ScrollView,然后重写ListView用于解决与ScrollView的冲突。但实际上这种处理方式与我所写的根本道理是一致的,都是再不断的新建item,并且友情提示,这种处理方式并不适用于大量的item,第一个较为明显的问题就是卡顿,因为你在滑动的时候,系统是再不停的新建与销毁,这需要较多的内存来处理,打开AS的内存可以看到这个页面的内存消耗还是不小的。当然如果有更优化的处理方式,欢迎留言。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值