在Android开发中我们经常会用到listview的数据和界面刷新动作,我们每次可能会用到的都是Adapter.notifyDataSetChanged()方法。这个方法的原理是利用观察者模式对我们的数据源进行监听,当我们的数据源发生变化的时候,会调用Adapter的getView()方法进行整个界面的刷新。这样的话我们发现,getview()会调用多次,刷新了好多个不需要刷新的item,这样的话相对而言,降低了效率。但是,我们有的情况下是只需要对某个item的数据进行刷新就可以了。这样的话,当数据很多的时候,会提高效率。
有的人可能会说,没有必要去优化这个。怎么说呢,至少这样会让我们更深入的去了解listview的特性。
1.先看效果图
2.先看一般的Adapter.notifyDataSetChanged()方法刷新界面
- 1.主界面的布局文件activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/activity_main" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#FFFFFF" tools:context="cn.bluemobi.dylan.listviewupdate.MainActivity"> <ListView android:id="@+id/listview" android:divider="#666666" android:dividerHeight="1px" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </RelativeLayout>
2.listview中的item的布局文件item.xml
1.<?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"> <TextView android:text="万能适配器测试" android:layout_width="match_parent" android:layout_height="48dp" android:gravity="center" android:textSize="18sp" android:textColor="#000000" android:id="@+id/textView" /> </LinearLayout>
3.activity中的代码
这里面用到了万能ViewHolder,不了解可以去这里http://blog.csdn.net/linglongxin24/article/details/52808656 了解详情
同时也用到了万能适配器,不了解可以去这里http://blog.csdn.net/linglongxin24/article/details/52813227 了解详情
package cn.bluemobi.dylan.listviewupdate; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.view.View; import android.widget.AdapterView; import android.widget.ListView; import android.widget.TextView; import java.util.ArrayList; import java.util.List; import cn.bluemobi.dylan.listviewupdate.adapter.CommonAdapter; import cn.bluemobi.dylan.listviewupdate.adapter.CommonViewHolder; public class MainActivity extends AppCompatActivity { private ListView listView; private List<String> datas; private CommonAdapter commonAdapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); updateTest(); } /** * 一般的更新界面 */ private void updateTest() { setContentView(R.layout.activity_main); listView = (ListView) findViewById(R.id.listview); datas = new ArrayList<>(); for (int i = 0; i < 10; i++) { datas.add("万能适配器测试" + i); } final CommonAdapter commonAdapter = new CommonAdapter<String>(this, datas, R.layout.item) { @Override protected void convertView(View item, String s) { TextView textView = CommonViewHolder.get(item, R.id.textView); textView.setText(s); } }; listView.setAdapter(commonAdapter); listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { datas.set(position, "update 万能适配器测试" + position); commonAdapter.notifyDataSetChanged(); } }); } }
3.ListView局部刷新方法一:更新对应view的内容
这种方法先通过listView.getChildAt(position)拿到要更新的对应的item布局文件,然后再通过findViewById找到对应的控件进行设置。
1.package cn.bluemobi.dylan.listviewupdate; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.view.View; import android.widget.AdapterView; import android.widget.ListView; import android.widget.TextView; import java.util.ArrayList; import java.util.List; import cn.bluemobi.dylan.listviewupdate.adapter.CommonAdapter; import cn.bluemobi.dylan.listviewupdate.adapter.CommonViewHolder; public class MainActivity extends AppCompatActivity { private ListView listView; private List<String> datas; private CommonAdapter commonAdapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); updateOneTest(); } /** * 只是局部更新某个界面 */ private void updateOneTest() { setContentView(R.layout.activity_main); listView = (ListView) findViewById(R.id.listview); datas = new ArrayList<>(); for (int i = 0; i < 20; i++) { datas.add("万能适配器测试" + i); } commonAdapter = new CommonAdapter<String>(this, datas, R.layout.item) { @Override protected void convertView(View item, String s) { TextView textView = CommonViewHolder.get(item, R.id.textView); textView.setText(s); } }; listView.setAdapter(commonAdapter); listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { datas.set(position, "update 万能适配器测试" + position); updateSingle(position); } }); } /** * 第一种方法 更新对应view的内容 * * @param position 要更新的位置 */ private void updateSingle(int position) { /**第一个可见的位置**/ int firstVisiblePosition = listView.getFirstVisiblePosition(); /**最后一个可见的位置**/ int lastVisiblePosition = listView.getLastVisiblePosition(); /**在看见范围内才更新,不可见的滑动后自动会调用getView方法更新**/ if (position >= firstVisiblePosition && position <= lastVisiblePosition) { /**获取指定位置view对象**/ View view = listView.getChildAt(position - firstVisiblePosition); TextView textView = (TextView) view.findViewById(R.id.textView); textView.setText(datas.get(position)); } } }
4.ListView局部刷新方法二:通过ViewHolder去设置值
通过Item找出对应的ViewHolder,然后通过ViewHolder去设置值
1. /** * 第二种方法 找出对应的ViewHolder,通过ViewHolder去设置值 * * @param position 要更新的位置 */ private void updateOne(int position) { /**第一个可见的位置**/ int firstVisiblePosition = listView.getFirstVisiblePosition(); /**最后一个可见的位置**/ int lastVisiblePosition = listView.getLastVisiblePosition(); /**在看见范围内才更新,不可见的滑动后自动会调用getView方法更新**/ if (position >= firstVisiblePosition && position <= lastVisiblePosition) { /**获取指定位置view对象**/ View view = listView.getChildAt(position - firstVisiblePosition); /**通过ViewHolder找出缓存的对应控件**/ TextView textView = CommonViewHolder.get(view, R.id.textView); textView.setText(datas.get(position)); } }
5.ListView局部刷新方法三:调用一次getView()方法
这种方法是调用适配器对应的getView方法,用它里面的代码对界面进行刷新。这也是google在IO大会上推荐的做法
package cn.bluemobi.dylan.listviewupdate; import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.view.View; import android.widget.AdapterView; import android.widget.ListView; import android.widget.TextView; import java.util.ArrayList; import java.util.List; import cn.bluemobi.dylan.listviewupdate.adapter.CommonAdapter; import cn.bluemobi.dylan.listviewupdate.adapter.CommonViewHolder; public class MainActivity extends AppCompatActivity { private ListView listView; private List<String> datas; private CommonAdapter commonAdapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); updateOneTest(); } /** * 只是局部更新某个界面 */ private void updateOneTest() { setContentView(R.layout.activity_main); listView = (ListView) findViewById(R.id.listview); datas = new ArrayList<>(); for (int i = 0; i < 20; i++) { datas.add("万能适配器测试" + i); } commonAdapter = new CommonAdapter<String>(this, datas, R.layout.item) { @Override protected void convertView(View item, String s) { TextView textView = CommonViewHolder.get(item, R.id.textView); textView.setText(s); } }; listView.setAdapter(commonAdapter); listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { datas.set(position, "update 万能适配器测试" + position); updateItem(position); } }); } /** * 第三种方法 调用一次getView()方法;Google推荐的做法 * * @param position 要更新的位置 */ private void updateItem(int position) { /**第一个可见的位置**/ int firstVisiblePosition = listView.getFirstVisiblePosition(); /**最后一个可见的位置**/ int lastVisiblePosition = listView.getLastVisiblePosition(); /**在看见范围内才更新,不可见的滑动后自动会调用getView方法更新**/ if (position >= firstVisiblePosition && position <= lastVisiblePosition) { /**获取指定位置view对象**/ View view = listView.getChildAt(position - firstVisiblePosition); commonAdapter.getView(position, view, listView); } } }
6.最后封装在万能适配器当中
package cn.bluemobi.dylan.listviewupdate.adapter; 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 java.util.List; /** * Created by yuandl on 2016-10-13. * 万能适配器 */ public abstract class CommonAdapter<T> extends BaseAdapter { private Context context; private List<T> datas; private int layoutId; public CommonAdapter(Context context, List<T> datas, int layoutId) { this.context = context; this.datas = datas; this.layoutId = layoutId; } @Override public int getCount() { return datas == null ? 0 : datas.size(); } @Override public T 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) { if (convertView == null) { convertView = LayoutInflater.from(context).inflate(layoutId, null); } Log.d("listview", "---------getView()-----------"); T t = getItem(position); convertView(convertView, t); return convertView; } /** * 局部更新数据,调用一次getView()方法;Google推荐的做法 * * @param listView 要更新的listview * @param position 要更新的位置 */ public void notifyDataSetChanged(ListView listView, int position) { /**第一个可见的位置**/ int firstVisiblePosition = listView.getFirstVisiblePosition(); /**最后一个可见的位置**/ int lastVisiblePosition = listView.getLastVisiblePosition(); /**在看见范围内才更新,不可见的滑动后自动会调用getView方法更新**/ if (position >= firstVisiblePosition && position <= lastVisiblePosition) { /**获取指定位置view对象**/ View view = listView.getChildAt(position - firstVisiblePosition); getView(position, view, listView); } } /** * 需要去实现的对item中的view的设置操作 * * @param item * @param t */ protected abstract void convertView(View item, T t); }
这样的话,我们每次更新的时候只需要调用notifyDataSetChanged(ListView listView, int position),传入对应的要更新的listview和要更新的位置position即可
7.总结
这三种方法的核心就是找出你要更新Item的contentView.然后再去操作。因为ListView默认只会加载一屏的数据,所以要判断其可见范围。不可见的在滑动的时候getView会自动调用更新数据。最后要强调的一点就是关于布局优化,最好将item的高度设置为一个固定的值,这样能减少getView的调用次数。因为一个不确定的值,ListView会频繁调用多次getView去确定其高度和渲染。