1. 程式人生 > >Android RecyclerView 中多Item的EditText焦點混亂,資料錯亂的解決方法(更新)

Android RecyclerView 中多Item的EditText焦點混亂,資料錯亂的解決方法(更新)

最近在做畢業設計,使用了RecyclerView來做了以下個人資料的編輯介面。RecyclerView中包含了多種不同的Item,例如圖片裡面的 姓名,公司,部門,職位,郵箱的Item 裡面都包含一個EditText。
這裡寫圖片描述

RecyclerView中EditText的監聽處理

這個介面的主要邏輯是監聽每個EditText的輸入,然後儲存到個人物件裡,最後儲存到伺服器上。RecyclerView中Item裡EditText的監聽可以按如下程式碼實現:
首先定義個回撥介面:

public interface onTextChangeListener {
    void onTextChanged(int
pos,String str); }

在RecyclerView的自定義Adapter中新增一個如上自定義的介面成員變數mTextListener,然後在onBindViewHolder裡面來新增Item中EditText的輸入監聽,即EditText.addTextChangedListener(),然後在監聽事件回撥裡的afterTextChanged 方法中 呼叫 mTextListener.onTextChanged 來達到介面回撥的目的。

public class EditListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>{
    private
Context mContext; private LayoutInflater mInflater; private ArrayList<EditListBean> mList; private onTextChangeListener mTextListener; public EditListAdapter(Context context, ArrayList<EditListBean> list){ this.mContext=context; mInflater=LayoutInflater.from
(mContext); this.mList=list; } //設定自定義介面成員變數 public void setOnTextChangeListener(onTextChangeListener onTextChangeListener){ this.mTextListener=onTextChangeListener; } //建立ViewHolder @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { RecyclerView.ViewHolder holder=new itemHolder2(mInflater.inflate(R.layout.edit_recyclerview_item2,parent,false)); return holder; } //繫結ViewHolder @Override public void onBindViewHolder(final RecyclerView.ViewHolder holder, final int position) { final itemHolder2 holder2= (itemHolder2) holder; holder2.title.setText(mList.get(position).getKey()); holder2.input.setText((String) mList.get(position).getValue()); holder2.input.setHint(mList.get(position).getHint()); //新增EditText的監聽事件 holder2.input.addTextChangedListener(new TextWatcher() { @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) { //通過介面回撥將資料傳遞到Activity中 mTextListener.onTextChanged(position,holder2.input.getText().toString()); } }); //內部類 ViewHolder class itemHolder2 extends RecyclerView.ViewHolder{ TextView title; EditText input; public itemHolder2(View itemView) { super(itemView); title= (TextView) itemView.findViewById(R.id.EditActivity_item2_title); input= (EditText) itemView.findViewById(R.id.EditActivity_item2_text); } } @Override public int getItemCount() { return mList.size(); } @Override public int getItemViewType(int position){ return mList.get(position).getType(); } }

Activity中的程式碼實現,在mAdapter中新增匿名內部類來監聽EditText中的介面回撥資料:

mAdapter.setOnTextChangeListener(new onTextChangeListener() {
            @Override
            public void onTextChanged(int pos, String str) {
            //在這裡拿到RecyclerView中Item的position和EditText中的變化
                mPedit.setList(pos,str);
                Log.e(String.format("%d is ---->",pos),str);
            }
        });

OK 上面的監聽事件處理完了 我們來試試效果怎麼樣。首先點選姓名裡面的EditText,在馬化騰後面輸入哈哈哈哈 然後收起鍵盤, 可以發現竟然下面幾個Item的EditText也觸發了回撥事件了,也就是我編輯了Item2的EditText,然後Item7 , 9 , 11的EditText也觸發了onTextChanged的監聽事件了。如圖,出現了監聽事件錯亂的重大的Bug。
這裡寫圖片描述

Recycler中 多Item中EditText焦點混亂的網上方法

2017年12月11號更新下,現在很多網上的解決方法是使用EditText的TAG來解決問題,EditText中有setTag和getTag可以儲存Object物件作為標籤,然後把textWatcher作為標籤設定TAG來作為標記表明這個item的editText是否已經設定textChange監聽了。在Adapter的onBindViewHolder中每次都先判斷editText的Tag是否有textWatcher物件,有的話就呼叫removeTextChangedListener來移除由於檢視複用之前繫結的textWatcher,然後就設定editText的內容setText,最後再給editText設定監聽器addTextChangedListener,並把這個textWatcher加入到editText的TAG標籤中。總結來說就是在介面卡裡先移除被複用事件,再新增新事件。

 //避免複用
        EditText editText =  helper.getView(R.id.et_comment);
        if (editText.getTag() instanceof TextWatcher) {
            editText.removeTextChangedListener((TextWatcher) editText.getTag());
        }
        helper.setText(R.id.et_comment, item.content);
        TextWatcher watcher = new TextWatcher() {
            @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) {
                item.content = s.length() > 0 ? s.toString() : "";
            }
        };
        editText.addTextChangedListener(watcher);
        editText.setTag(watcher);

這種方式並不優雅,而且還要進行型別判斷和強制型別轉型,對效能有一定的開銷,下面來介紹真正的優雅解決EditText焦點混亂的解決方法:

## Recycler中 多Item中EditText焦點混亂,資料錯亂的正確優雅的解決方法 !!##

首先要知道EditText焦點錯亂的問題就是RecyclerView的檢視回收與複用問題,例如在位置0有item1檢視中的editText已經綁定了textWatcher1監聽器,但是又沒有移除監聽器,在位置6時又複用了item1檢視,editText又再次綁定了textWatcher2監聽器,此時item1檢視中的editText已經綁定了2個textWatcher監聽器了,當在位置6進行輸入資料時,必定會導致位置0的資料變化了,令位置0的資料跟位置6的資料一模一樣,也就是資料錯亂了。原因是editText中的addTextChangedListener是可以繫結多個監聽器的,來看看它的實現:

    public void addTextChangedListener(TextWatcher watcher) {
        if (mListeners == null) {
            mListeners = new ArrayList<TextWatcher>();
        }

        mListeners.add(watcher);
    }

可以看到mListeners 是一個ArrayList陣列,當多次呼叫addTextChangedListener時,一個editText可以繫結多個textWatcher監聽器,所以要記住在每次呼叫addTextChangedListener完之後必須呼叫removeTextChangedListener來接觸繫結。

那呼叫這兩個方法的時間節點是在哪裡的呢?首先我們要知道如果一個editText要獲取輸入值那它就首先必須要獲取到焦點了,沒錯,答案就是設定setOnFocusChangeListener監聽器來判斷焦點的變化從而設定addTextChangedListener和removeTextChangedListener。

程式碼如下:

                final itemHolder2 holder2= (itemHolder2) holder;
                //holder2.input為EditTextView
                holder2.input.setText((String) mList.get(position).getValue());
                holder2.input.setHint(mList.get(position).getHint());

                final TextWatcher textWatcher=new TextWatcher() {
                    @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.e("textWatcher",holder.getAdapterPosition()+"");
                        if(holder2.input.hasFocus()){//判斷當前EditText是否有焦點在
                            //通過介面回撥將資料傳遞到Activity中
                            mTextListener.onTextChanged(position,holder2.input.getText().toString());
                        }
                    }
                };
                //設定EditText的焦點監聽器判斷焦點變化,當有焦點時addTextChangedListener,失去焦點時removeTextChangedListener
                holder2.input.setOnFocusChangeListener(new View.OnFocusChangeListener() {
                    @Override
                    public void onFocusChange(View v, boolean hasFocus) {
                        if(hasFocus){
                            holder2.input.addTextChangedListener(textWatcher);
                            Log.e("addTextChanged",position+"");
                        }else{
                            holder2.input.removeTextChangedListener(textWatcher);
                            Log.e("removeTextChanged",position+"");
                        }
                    }
                });

通過設定setOnFocusChangeListener來監聽焦點就能完美的解決了RecyclerView中EditText資料監聽textWatcher導致的資料錯亂問題了。目前來說是最優雅的一種方式了,也不需要用到Tag了。