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

最后都变了- 提交于 2019-12-06 08:58:22

ListView是在Android开发中用得非常多的控件之一,并且这些列表还经常需要我们去对listView的数据进行刷新操作,在这种情况下,我们往往都会去调用adapter的notifyDataSetChanged()方法对listView的界面重新进行绘制。众所周知,notifyDataSetChanged()这个方法是Adapter的观察者模式的体现,它的实现原理就是对我们的数据源进行监听,一旦我们的数据源发生了变化,就会去调用getView()方法对整个界面上可见的Item进行刷新。但是,这同时也对很多本不需要刷新的Item也进行了刷新,这样的效率无疑是很低的,当数据量很大的时候还有可能会出现卡顿或者图片闪烁等问题。这对于用户体验上来说,也是很不友好的。

在下文中,我是以一个小的Demo来介绍怎么用非notifyDataSetChanged()的方法来对listView的界面进行刷新,并利用Item的点击来模拟数据源的变化。

不多说,直接看代码:(布局简单,就不放了)

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

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的操作都是多余的。

2. 通过直接调用getView()方法来刷新对应item的界面。

既然我们看到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:如有发现本文内容错误或不足之处的,欢迎指正。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!