解決ListView中包含EditText數據混亂原理

要求:屏幕中顯示一個listview,其中每一個item都有一個editText,在任一editText上輸入內容,快速上下滑動,保證數據不混亂。

這是一道面試題,初看沒什麼,應該會很簡單,但實際解決起來沒那麼簡單,先上解決代碼。

package com.zhiren.mytestok;

import android.content.Context;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.EditText;

import java.util.List;

/**
 * Created by Administrator on 2017/2/20.
 */

public class MyAdapters extends BaseAdapter {
    private Context context;
    //    private String[] str;
    private List list;

    public MyAdapters(Context context, List list) {
        this.context = context;
        this.list = list;
    }

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

    @Override
    public Object getItem(int position) {
        return null;
    }

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

    @Override
    public View getView(final int position, View convertView, ViewGroup parent) {
        final ViewHolder viewHolder;
        if (convertView == null) {
            convertView = View.inflate(context, R.layout.item_mian, null);
            viewHolder = new ViewHolder(convertView);


            convertView.setTag(viewHolder);
        } else {
            viewHolder = (ViewHolder) convertView.getTag();
        }

        Bean bean = (Bean) list.get(position);
        Log.e("TAG", viewHolder.text + ":" + position);
        viewHolder.text.setTag(position);
        viewHolder.text.clearFocus();


        viewHolder.text.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) {
                int pos = (int) viewHolder.text.getTag();
                Bean b  = (Bean) list.get(pos);
                b.setName(s + "");
            }

            @Override
            public void afterTextChanged(Editable s) {

            }
        });

        if (!TextUtils.isEmpty(bean.getName())) {
            viewHolder.text.setText(bean.getName());
        } else {
            viewHolder.text.setText("");
        }

        return convertView;
    }

    public class ViewHolder {
        private EditText text;

        public ViewHolder(View v) {
            text = (EditText) v.findViewById(R.id.et_item);
        }
    }
}

解釋:wKiom1ir0-HRh-Q9AACx4z3Czt8488.jpg-wh_50

通過打印log可以看到,屏幕中最多可以顯示7行item,下標依次爲:0、1、2、3、4、5、6.

其中下標爲0的item的地址值是3098e6e5

通過向上滑動,當出現第8行item(下標爲7)的時候,下標爲0的item已經完全看不到了,根據谷歌設計原理,下標爲0的item複用到了剛剛出現的下標爲7的item上,其中可以看到下標爲7的item的地址值也是3098e6e5,以上可以證明。這些都是大家知道的,重點看下面:

爲什麼要寫

viewHolder.text.setTag(position);

這一行意義重大,在滑動的過程中,動態的將item與position進行綁定,如圖:

wKioL1ir1taizi86AABKX-swbb4328.png-wh_50

那麼這麼做的意義是什麼呢,可以看到item中的editText有一個監聽事件:每當editText內容變化的時候都會將editText上的內容保存至集合中,那麼保存到集合的哪一個下標中呢?看這行

int pos = (int) viewHolder.text.getTag();

被點擊的那行item根據getTag()獲取了最近與它綁定的那個position,還以地址爲3098e6e5的item爲例,那麼pos的值此時應該是0還是7呢?這與當前listview滑動的位置有關,如果當前屏幕能看到下標爲7的item,那麼此時pos就必定爲7,不可能爲0,第一:item只能動態與一個position相綁定,第二,綁定是動態變化的,當前屏幕能看到的是下標爲7的item,自然item與下標7綁定就不能再與下標0綁定了。那麼就得到pos爲7,集合就會將當前editText的內容保存到下標爲7的對象中。那麼無論再怎麼上下滑動,只有當positon爲7的時候才能從集合中獲取那條保存的數據,其他position都不可以,其他item也同理。



如果正常寫會出現什麼呢?

例如:監聽器裏不getTag()

viewHolder.text.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) {
//                int pos = (int) viewHolder.text.getTag();
//                Bean b  = (Bean) list.get(pos);
//                b.setName(s + "");
                Bean b = (Bean) list.get(position);
                b.setName(s + "");
                Log.e("TAG", "" + position);
            }

            @Override
            public void afterTextChanged(Editable s) {

            }
        });


運行app後,首先上下滑動listView到最後再滑動到開頭(我設置了長度爲51),讓每一個item充分複用,此時再次在下標爲0的item中輸入內容,打印數據如下:

wKiom1ir2-TzjSiTAADBY-sl4YQ323.jpg-wh_50

可以看到,我只在一個item的editText中輸入了1個字,按道理來說應該只觸發下標爲0的那個item,並將下標爲0的item上的數據保存到集合,但事實上卻觸發了這麼多item中的editText的監聽,這是爲什麼呢?

原因是在上下快速滑動的過程中,下標爲0的item出現了大量的複用情況,例如第0、7、14、21行的item都複用了這一個item,而適配器中的getView()方法經過了多次的執行,每次執行完畢後一些無用的資源就被回收了,但是item的editText是保存在viewHolder中的,並沒有被回收,但是多次的執行getView()方法,每一次都讓Item中的這個editText在對應的positon下設置了一次監聽,那麼多次設置監聽,對應的是不同的位置(position),當觸發監聽的時候,自然會多處響應,導致了數據顯示的混亂。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章