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

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

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android 开发ListView 是常用的列表控件,而 Adapter 则是 ListView 显示列表数据的适配器。当数据源改变时,我们需要调用 Adapter 的 notifyDataSetChanged() 方法来通知 ListView 更新数据。不过有时候,我们会发现调用 notifyDataSetChanged() 方法后,ListView 并没有更新数据,这通常是由以下几个原因造成的: 1. 数据源没有更新 在调用 notifyDataSetChanged() 方法之前,需要先确保数据源已经更新了。如果数据源没有更新,调用 notifyDataSetChanged() 方法也不会更新 ListView 显示的数据。 2. Adapter 对象没有重新设置 如果使用的是同一个 Adapter 对象,那么需要重新设置 Adapter 对象才能更新 ListView 显示的数据。可以通过 setAdapter() 方法重新设置 Adapter 对象。 3. ListView 没有重新绘制 当调用 notifyDataSetChanged() 方法后,ListView 并不会立即重新绘制,而是等到系统认为需要重新绘制时才会更新。可以通过调用 invalidate() 方法让 ListView 立即重新绘制。 4. 数据源和 Adapter 对象不匹配 如果数据源和 Adapter 对象不匹配,即数据源的数据类型和 Adapter 的数据类型不一致,调用 notifyDataSetChanged() 方法也无法更新 ListView 显示的数据。 综上所述,如果在 ListView 调用 notifyDataSetChanged() 方法无效,可以先检查数据源是否更新,是否重新设置 Adapter 对象,是否调用了 invalidate() 方法以及数据源和 Adapter 对象是否匹配。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值