ListView 局部刷新

概述

在使用ListView的时候,我们都会对列表数据进行更新,当数据变化时,我们会调用adapter的notifyDataSetChange方法去刷新列表。但是,该刷新方法是使整个列表都更新一遍(调用了adapter的getView方法)。而,我们往往只是更新了item中的某一项数据,如果刷新整个列表是不是显得太过于浪费了,特别是对于列表中有图片要显示的情况下,就会造成每次notifyDataSetChange图片会闪烁抖动。本文意在解决该问题,让数据更新只在局部。

解决方案

  • 情景再现

下面例子的效果是,列表数据显示的是一系列数字,点击列表的item项时,该列表数字加1。那么一般的代码实现如下:
定义一个adapter

MyAdapter.java

public class MyAdapter extends BaseAdapter{
    private Context context;
    private List<Integer> datas;

    public MyAdapter(Context context,List<Integer> datas){
        this.context = context;
        this.datas = datas;
    }

    @Override
    public int getCount() {
        if(datas != null){
            return datas.size();
        }
        return 0;
    }

    @Override
    public Integer getItem(int position) {
        return datas.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 = LayoutInflater.from(context).inflate(R.layout.item_adapter,parent,false);
            holder = new ViewHolder();
            holder.textView = (TextView) convertView.findViewById(R.id.textview);
            convertView.setTag(holder);
        }else {
            holder = (ViewHolder) convertView.getTag();
        }
        holder.textView.setText(String.valueOf(getItem(position)));
        return convertView;
    }

    private class ViewHolder{
        TextView textView;
    }
}

item 布局这里只是简单的一个TextView
item_adapter.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">
    <TextView
        android:id="@+id/textview"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:minHeight="45dp"
        android:textSize="18sp"
        android:gravity="center"
        android:textColor="@android:color/black"
        />
</LinearLayout>

activity的布局activity_listview.xlm

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">
    <ListView
        android:id="@+id/listview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
</LinearLayout>

数据显示

public class ListViewTest extends Activity{
    ListView mListView;
    MyAdapter adapter;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_listview);
        mListView = (ListView) findViewById(R.id.listview);

        final List<Integer> datas = new ArrayList<Integer>();
        for (int i=1;i<10;i++){
            datas.add(i);
        }

        adapter = new MyAdapter(this,datas);

        mListView.setAdapter(adapter);
        mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                int data = datas.get(position);
                data = data+1;
                datas.set(position,data);
                adapter.notifyDataSetChanged();
            }
        });
    }
}

这里主要是给ListView设置了setOnItemClickListener事件,然后进行+1处理,最后调用adapter的notifyDataSetChanged刷新数据。

好了,以上是我们一般的传统做法,下面我们看下如何实现ListView的局部刷新。

  • ListView局部刷新实现

在adapter里定义一个方法notifyDataSetChangedAt(View view,int position),然后在该方法处理逻辑
例如本例子如下:

public void notifyDataSetChangedAt(View view,int position) {
      if(view != null){
          int data = datas.get(position);
          data = data+1;
          datas.set(position,data);
          TextView textView = (TextView) view.findViewById(R.id.textview);
          textView.setText(String.valueOf(data));
      }
 }

然后在外部调用,实现调用,这里有两种场景,实现方法不同。

  • 第一种,在当前ListView点击item进行刷新
    这种场景是属于比较常规的,实现起来也是比较简单的,如下:
mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
     @Override
     public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
         /*int data = datas.get(position);
         data = data+1;
         datas.set(position,data);
         adapter.notifyDataSetChanged();*/
         adapter.notifyDataSetChangedAt(view,position);
     }
});

只要在ListView的setOnItemClickListener调用adapter的notifyDataSetChangedAt即可

  • 第二种,在‘详情页’进行操作后,需要刷新ListView

第二种比较复杂一点,和业务关系紧密,一般业务场景是这样的:比如微博,在微博详情页,我点赞了,那么微博的列表相应的item的赞数要加1,这种场景的复杂在于,业务操作点与ListView脱离,不知要更新哪个item,但是,一般我们的业务module都有自己的一个id,通过该id我们就可以计算出item的位置。

修改一下adapter的数据类型,增加module

public class MyModule {
    private int id;
    private int count;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getCount() {
        return count;
    }

    public void setCount(int count) {
        this.count = count;
    }
}

然后修改MyAdapter的数据类型为MyModule

public class MyAdapter extends BaseAdapter{
    private Context context;
    private List<MyModule> datas;

    public MyAdapter(Context context,List<MyModule> datas){
        this.context = context;
        this.datas = datas;
    }

    @Override
    public int getCount() {
        if(datas != null){
            return datas.size();
        }
        return 0;
    }

    @Override
    public MyModule getItem(int position) {
        return datas.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 = LayoutInflater.from(context).inflate(R.layout.item_adapter,parent,false);
            holder = new ViewHolder();
            holder.textView = (TextView) convertView.findViewById(R.id.textview);
            convertView.setTag(holder);
        }else {
            holder = (ViewHolder) convertView.getTag();
        }
        holder.textView.setText(String.valueOf(getItem(position).getCount()));
        return convertView;
    }

    private class ViewHolder{
        TextView textView;
    }

    /**
     * 局部刷新
     */
    public void notifyDataSetChangedAt(View view,int position) {
        if(view != null){
            MyModule data = datas.get(position);
            data.setCount(data.getCount()+1);
            datas.set(position,data);
            TextView textView = (TextView) view.findViewById(R.id.textview);
            textView.setText(String.valueOf(data.getCount()));
        }
    }
}

修改activity

public class ListViewTest extends Activity{
    ListView mListView;
    MyAdapter adapter;
    List<MyModule> datas = new ArrayList<MyModule>();
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_listview);
        mListView = (ListView) findViewById(R.id.listview);

        for (int i=0;i<10;i++){
            MyModule module = new MyModule();
            module.setId(i);
            module.setCount(i+1);
            datas.add(module);
        }

        adapter = new MyAdapter(this,datas);

        mListView.setAdapter(adapter);
        mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                /*int data = datas.get(position);
                data = data+1;
                datas.set(position,data);
                adapter.notifyDataSetChanged();*/
                //adapter.notifyDataSetChangedAt(view,position);
                onListViewItemClick(datas.get(position));
            }
        });
    }

    private void onListViewItemClick(MyModule myModule){
        //获取屏幕中ListView可见部分的第一个item位置索引
        final int firstPos = mListView.getFirstVisiblePosition();
        //循环遍历
        for (int i = 0; i < datas.size(); i++) {
            MyModule module = datas.get(i);
            if (myModule != null && myModule.getId() == module.getId()) {
                View v = mListView.getChildAt(i - firstPos);
                adapter.notifyDataSetChangedAt(v, i);
                break;
            }
        }
    }
}

我这里没有‘详情页’,通过ListView的ItemClickListener事件模拟了。最重要的方法是onListViewItemClick。思路:一般我们点击item进详情时,该item是处于在屏幕可见的,所以为了减少遍历次数,我们计算了ListView的第一个可见item的位置,然后通过比较是否是同一个module id实现局部刷新。

注意:一般使用场景是,详情页进行操作并更新module之后,通过setResult将module传给onActivityResult,然后调用onListViewItemClick(MyModule myModule)

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值