安卓Adapter中getView方法重復執行及索引錯位問題解決打賞

Android開發中,經常會用到ListView,當然,也會用到Adapter為ListView綁定數據。通常,我們采用繼承BaseAdapter并實現getView等方法的方式為ListView渲染數據,ListView中每個元素都會去調用getView做渲染。但是,如果ListView要展示數據非常龐大,創建成百上千個View是不可取的,于是乎,Android為我們提供了一套Recycler機制,可以重復利用已創建的View,降低對象開銷。

原理如下:

在一個完整的ListView第一次出現時,每個Item都是null的,getView的時候會跑到需要inflate一個Item的代碼段,假設整個view只能最多顯示10個item,那么當滑動到第11個Item的時候,第一個item會放入"recycler",如果第11個Item和放入"Recycler"的item的view一致,那么就會使用"Recycler"里面的Item來顯示,從而不用再重復inflate一次。

圖示如下:

adapter-recycler

這不是今天的重點,重點是在具體開發中,發現getView被重復調用,按照網上說法,getView執行的次數和你的getCount沒有直接的關系,getCount和你ListView里面的條目數量(行數量)有關系 ,getView方法執行次數取決于你屏幕上顯示幾個條目,比如你有100行 ,但是你一屏只能顯示5行,那么啟動程序的時候 系統調用5次getView方法,當你把listView往下拉的時候會顯示出其他未顯示的行,這樣系統就會調用getView方法,每顯示一個新的行就調用一次getView,所以你要是不停的上下滑動listVew那getView理論上是可以調用任意次數的。新手通常遇到的重復是在這5個范圍內重復,此問題在于,ListView沒有取到實際的高度,無法確定取多少View來填充ListView,也就是運行getView的具體運行次數,所以要嘗試性的探測單個Item的高度,然后再去渲染,所以,我們需要做的是,為ListView設置高度為fill_parent(有文章表示,可以通過判斷parent.getChildCount是否等于當前position來確定是否有效position,實際使用效果不佳),問題解決,但是在列表有輸入框等情況下,激活輸入,彈出鍵盤,有些輸入法會出現把窗體壓縮的情況,于是,Adapter的getView又會重新執行,這里就不可避免了。

上面已提及,getView多次調用實際上是不可避免的,但我們只需保證每次渲染索引對應數據,一切問題就迎刃而解,這就是我們的第二個問題,position錯位,因為免不了在ListView中使用EditView等,而多變的需求又致使我們需要在getView里面為元素綁定事件,而getView又會重復調用,所以,我們如果不做判斷直接在getView里面綁定事件,那么可想而知,操作幾次后可能會有n個事件在同一個View上面,為了解決這樣的問題,我們通常在判斷View為空的情況inflate,同時綁定事件,這樣看似完美,實則存在著潛在錯誤,如果是綁定click等事件,每次回調都會傳入當前View,也就沒什么問題,但如果是類似TextWatcher事件,回調不知道當前View,由于java語言特性,內部類傳入值必須為final,這樣我們在回調里面直接拿到的始終是最后一個初始化的View對應的position,很難與當前Item所對應數據進行交互等。

為了解決這個問題,我們通過實現TextWatcher接口,同時保存當前View所對應對象或者要操作的對象,代碼如下:

class MyTextWatcher implements TextWatcher {
    private ViewHolder viewHold;

    public MyTextWatcher(ViewHolder viewHold) {
        this.viewHold = viewHold;
    }

    @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) {
        //當前索引
        Object position = viewHold.editText.getTag();
    }
}

getView內這樣寫:

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

        viewHolder = new ViewHolder();
        viewHolder.editText = (EditText) convertView.findViewById(R.id.edit_text);
        viewHolder.editText.addTextChangedListener(new MyTextWatcher(viewHold));

        convertView.setTag(viewHold);
    } else {
        viewHolder = (ViewHolder) convertView.getTag();
    }
    viewHolder.editText.setTag(position);
    return convertView;
}

其中,上面兩個方法用到的ViewHolder也是為了保存中間變量創建的實體類,如下:

class ViewHolder {
    EditText editText;
}

這里為了說明方法,簡化了對應代碼,實際使用中你可能需要保存其他信息等,可以在getView內多傳入信息到ViewHolder。

安卓Adapter中getView方法重復執行及索引錯位問題解決
文章《安卓Adapter中getView方法重復執行及索引錯位問題解決》二維碼
  • 微信打賞
  • 支付寶打賞

已有4條評論

  1. cnplus

    又遇見一個大神!先膜拜一下!

    2016-12-20 17:51 回復
  2. 運營-子客

    厲害

    2016-11-11 15:57 回復
  3. 買家秀

    技術大牛只能崇拜!

    2016-10-28 19:07 回復
  4. 怎么炒股新手入門

    新年新氣象,希望這里越來越好!

    2016-10-27 23:42 回復

(必填)

(必填)

(可選)

黑龙江22选5开奖