Android ListView優化之局部刷新(非notifyDataSetChanged()方式)

ListView是在Android開發中用得很是多的控件之一,而且這些列表還常常須要咱們去對listView的數據進行刷新操做,在這種狀況下,咱們每每都會去調用adapter的notifyDataSetChanged()方法對listView的界面從新進行繪製。衆所周知,notifyDataSetChanged()這個方法是Adapter的觀察者模式的體現,它的實現原理就是對咱們的數據源進行監聽,一旦咱們的數據源發生了變化,就會去調用getView()方法對整個界面上可見的Item進行刷新。可是,這同時也對不少本不須要刷新的Item也進行了刷新,這樣的效率無疑是很低的,當數據量很大的時候還有可能會出現卡頓或者圖片閃爍等問題。這對於用戶體驗上來講,也是很不友好的。java

在下文中,我是以一個小的Demo來介紹怎麼用非notifyDataSetChanged()的方法來對listView的界面進行刷新,並利用Item的點擊來模擬數據源的變化。android

很少說,直接看代碼:(佈局簡單,就不放了)app

1. 用notifyDataSetChanged()方式刷新界面ide

package com.example.zohar.androidtest.listView;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;

import com.example.zohar.androidtest.R;

import java.util.ArrayList;
import java.util.List;

import butterknife.Bind;
import butterknife.ButterKnife;

public class UpdateSingleListViewActivity extends AppCompatActivity {

    @Bind(R.id.list_view)
    ListView listView;

    private List<String> dataList = new ArrayList<>();
    private ListViewAdapter adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_update_single_list_view);
        ButterKnife.bind(this);

        initData();
        initView();
    }

    private void initData() {
        for (int i = 0; i < 20; i++) {
            dataList.add("第" + i + "個數據");
        }
    }

    private void initView() {
        adapter = new ListViewAdapter(this, dataList);
        listView.setAdapter(adapter);
        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                Log.d("OnItemClick", "點擊了第" + position + "項");
                dataList.set(position, "修改後的數據:position = " + position);
                adapter.notifyDataSetChanged();
            }
        });
    }
}
package com.example.zohar.androidtest.listView;

import android.content.Context;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ListView;
import android.widget.TextView;

import com.example.zohar.androidtest.R;

import java.util.List;

import butterknife.Bind;
import butterknife.ButterKnife;

public class ListViewAdapter extends BaseAdapter {
    private List<String> dataList;
    private LayoutInflater inflater;

    public ListViewAdapter(Context context, List<String> dataList) {
        this.dataList = dataList;
        inflater = LayoutInflater.from(context);
    }

    @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(int position, View convertView, ViewGroup parent) {
        ViewHolder holder;
        if (convertView == null) {
            convertView = inflater.inflate(R.layout.item_layout_update_single_list_view, null);
            holder = new ViewHolder(convertView);
            convertView.setTag(holder);
        } else {
            holder = (ViewHolder) convertView.getTag();
        }
        Log.d("TAG", "getView ======> position = " + position);
        holder.tvItem.setText(dataList.get(position));
        return convertView;
    }

    static class ViewHolder {
        @Bind(R.id.tv_item)
        TextView tvItem;

        ViewHolder(View view) {
            ButterKnife.bind(this, view);
        }
    }
}

咱們來看看打印的Log佈局

11-06 00:36:09.419 24736-24736/com.example.zohar.androidtest D/TAG: getView ======> position = 0
11-06 00:36:09.419 24736-24736/com.example.zohar.androidtest D/TAG: getView ======> position = 1
11-06 00:36:09.419 24736-24736/com.example.zohar.androidtest D/TAG: getView ======> position = 2
11-06 00:36:09.419 24736-24736/com.example.zohar.androidtest D/TAG: getView ======> position = 3
11-06 00:36:09.429 24736-24736/com.example.zohar.androidtest D/TAG: getView ======> position = 4
11-06 00:36:09.429 24736-24736/com.example.zohar.androidtest D/TAG: getView ======> position = 5
11-06 00:36:09.429 24736-24736/com.example.zohar.androidtest D/TAG: getView ======> position = 6
11-06 00:36:09.429 24736-24736/com.example.zohar.androidtest D/TAG: getView ======> position = 7
11-06 00:36:09.429 24736-24736/com.example.zohar.androidtest D/TAG: getView ======> position = 8
11-06 00:36:09.439 24736-24736/com.example.zohar.androidtest D/TAG: getView ======> position = 9
11-06 00:36:09.439 24736-24736/com.example.zohar.androidtest D/TAG: getView ======> position = 10
11-06 00:36:09.439 24736-24736/com.example.zohar.androidtest D/TAG: getView ======> position = 11
11-06 00:36:09.439 24736-24736/com.example.zohar.androidtest D/TAG: getView ======> position = 12
11-06 00:36:09.459 24736-24736/com.example.zohar.androidtest D/TAG: getView ======> position = 13
11-06 00:36:09.459 24736-24736/com.example.zohar.androidtest D/TAG: getView ======> position = 14
11-06 00:36:11.789 24736-24736/com.example.zohar.androidtest D/TAG: 點擊了第5項
11-06 00:36:11.839 24736-24736/com.example.zohar.androidtest D/TAG: getView ======> position = 0
11-06 00:36:11.839 24736-24736/com.example.zohar.androidtest D/TAG: getView ======> position = 1
11-06 00:36:11.839 24736-24736/com.example.zohar.androidtest D/TAG: getView ======> position = 2
11-06 00:36:11.839 24736-24736/com.example.zohar.androidtest D/TAG: getView ======> position = 3
11-06 00:36:11.839 24736-24736/com.example.zohar.androidtest D/TAG: getView ======> position = 4
11-06 00:36:11.839 24736-24736/com.example.zohar.androidtest D/TAG: getView ======> position = 5
11-06 00:36:11.839 24736-24736/com.example.zohar.androidtest D/TAG: getView ======> position = 6
11-06 00:36:11.839 24736-24736/com.example.zohar.androidtest D/TAG: getView ======> position = 7
11-06 00:36:11.839 24736-24736/com.example.zohar.androidtest D/TAG: getView ======> position = 8
11-06 00:36:11.839 24736-24736/com.example.zohar.androidtest D/TAG: getView ======> position = 9
11-06 00:36:11.839 24736-24736/com.example.zohar.androidtest D/TAG: getView ======> position = 10
11-06 00:36:11.839 24736-24736/com.example.zohar.androidtest D/TAG: getView ======> position = 11
11-06 00:36:11.839 24736-24736/com.example.zohar.androidtest D/TAG: getView ======> position = 12
11-06 00:36:11.839 24736-24736/com.example.zohar.androidtest D/TAG: getView ======> position = 13
11-06 00:36:11.839 24736-24736/com.example.zohar.androidtest D/TAG: getView ======> position = 14

能夠看到,在我點擊了第5項item時,個人界面上可見的0~14項這15個item全都被刷新了,但實際上,我只須要它對第5項的item進行刷新就能夠的,這至關於我有15分之14的操做都是多餘的。this

2. 經過直接調用getView()方法來刷新對應item的界面。code

既然咱們看到Log中打印的是屢次調用getView()方法來對界面進行刷新,那麼咱們能夠想一想,可否直接經過position這一參數來直接調用對應的getView()方法來達到相同的效果呢?對象

咱們看到adapte的 getView(int position, View convertVie, ViewGroup parent) 中有三個參數,其中position是序號,convertView就是咱們item中的子View,parent是咱們須要刷新界面的控件。事件

因而,咱們將item的點擊事件方法修改成:圖片

listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                Log.d("TAG", "點擊了第" + position + "項");
                dataList.set(position, "修改後的數據:position = " + position);
                View item = listView.getChildAt(position);
                adapter.getView(position, item, listView);
           }
        });

此時,咱們在來看看Log,會發現,我點擊某一個item的時候,adapter只會去調用對應position的getView()方法來對界面進行刷新了

11-06 01:03:42.269 24736-24736/com.example.zohar.androidtest D/TAG: 點擊了第13項
11-06 01:03:42.269 24736-24736/com.example.zohar.androidtest D/TAG: getView ======> position = 13

可是當咱們看界面的時候,卻發現一個問題,就是當我可見的item不是從第一條數據開始時,我點擊的item的position是13,修改的確實position是18的item。

這是由於當item變成不可見時會回收掉對應的convertView的緣由,所以此時調用position爲13的getView()方法時,更新的是可見的序號爲13的item,也就是整個listView中的序號爲18的item。此時,咱們就須要去計算出對應item真正的position。

listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                Log.d("TAG", "點擊了第" + position + "項");
                dataList.set(position, "修改後的數據:position = " + position);
                notifyDataSetChanged(position, listView);
            }
        });
private void notifyDataSetChanged(int position, ListView listView) {
        int firstVisiblePosition = listView.getFirstVisiblePosition();//得到可見的第一個item的position
        int lastVisiblePosition = listView.getLastVisiblePosition();//得到可見的最後一個item的position
        if (position >= firstVisiblePosition && position <= lastVisiblePosition) {
            View view = listView.getChildAt(position - firstVisiblePosition);
            adapter.getView(position, view, listView);
        }
    }

至此,咱們便獲得了正確的結果,利用adapter的getView()方法,讓咱們在刷新界面的時候只須要去刷新須要刷新的item。

3. 接下來,假如咱們一個item中的控件較多,而咱們又只須要刷新其中的某一個控件,要怎麼辦呢?

經過Debug,咱們能夠看到在咱們經過listView.getChildAt(position)獲得的view的tag屬性是有值的,並且這個值其實就是adapter的getView()方法返回的子View的數據對象ViewHolder。

那麼,咱們的 notifyDataSetChanged(int position, ListView listView) 方法就能夠改成以下形勢,直接經過view.getTag() 方法獲得ViewHolder對象,而後就能夠修改咱們所但願的控件了。

private void notifyDataSetChanged(int position, ListView listView) {
        int firstVisiblePosition = listView.getFirstVisiblePosition();
        int lastVisiblePosition = listView.getLastVisiblePosition();
        if (position >= firstVisiblePosition && position <= lastVisiblePosition) {
            ListViewAdapter.ViewHolder holder = (ListViewAdapter.ViewHolder) listView.getChildAt(position - firstVisiblePosition).getTag();
            holder.tvItem.setText(dataList.get(position));
        }
    }

PS:若有發現本文內容錯誤或不足之處的,歡迎指正。

相關文章
相關標籤/搜索