之前接触了ListView和Adapter,Adapter将数据源和View连接起来,实际应用中,我们要显示的数据往往有很多,而屏幕只有那么大,系统只能屏幕所能显示的内容,当我们滑动屏幕,会将旧的内容放入到缓冲池中,再从缓存池中拿出新的内容显示出来,这就是ListView的缓存机制,这一机制可以极大的节省系统资源。
BaseAdapter
BaseAdapter通常用于被扩展,扩展BaseAdapter可以对各项列表进行最大限度的定制。
我们可以用自己的类去继承BaseAdapter,然后实现getCount()、getItem()、getItemId()、getView()这四个方法,来完成对Adapter的定制。
getCount() 该方法返回ListView需要显示的数据量
getItem() 该方法返回指定索引(position)所对应的数据项
getItemId() 该方法返回对应的索引
getView() 该方法返回每一项所显示的内容
简单示例:
package cn.lixyz.baseadaptertest; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.AdapterView; import android.widget.ListView; import android.widget.Toast; import java.util.ArrayList; import java.util.List; public class MainActivity extends AppCompatActivity { List<ItemBean> itemBeanList; ListView listView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); itemBeanList = new ArrayList<ItemBean>(); for (int i = 0; i < 20; i++) { ItemBean ib = new ItemBean(R.drawable.icon, "标题" + i, "内容" + i); itemBeanList.add(ib); } listView = (ListView) findViewById(R.id.listView); listView.setAdapter(new MyAdapter(this, itemBeanList)); listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { Toast.makeText(MainActivity.this, "你选择的是:" + position, Toast.LENGTH_SHORT).show(); } }); } }
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".MainActivity"> <ListView android:id="@+id/listView" android:layout_width="match_parent" android:layout_height="match_parent" /> </LinearLayout>
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <ImageView android:id="@+id/iv_icon" android:layout_width="60dp" android:layout_height="60dp" android:layout_marginLeft="10dp" android:layout_marginTop="10dp" android:src="@drawable/icon" /> <TextView android:id="@+id/tv_name" android:layout_width="match_parent" android:layout_height="35dp" android:layout_marginTop="10dp" android:layout_toEndOf="@+id/iv_icon" android:gravity="center" android:text="标题" android:textSize="25dp" /> <TextView android:id="@+id/tv_desc" android:layout_width="match_parent" android:layout_height="25dp" android:layout_below="@+id/tv_name" android:layout_marginLeft="10dp" android:layout_toEndOf="@+id/iv_icon" android:gravity="center" android:text="内容内容内容内容" /> </RelativeLayout>
package cn.lixyz.baseadaptertest; /** * Created by LGB on 2015/9/9. */ public class ItemBean { public int ItemImageResid; public String ItemName; public String Itemdesc; public ItemBean(int ItemImageResid, String ItemName, String Itemdesc) { this.ItemImageResid = ItemImageResid; this.ItemName = ItemName; this.Itemdesc = Itemdesc; } }
package cn.lixyz.baseadaptertest; import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.ImageView; import android.widget.TextView; import java.util.List; /** * Created by LGB on 2015/9/9. */ public class MyAdapter extends BaseAdapter { private List<ItemBean> list; private LayoutInflater inflater; public MyAdapter(Context context, List<ItemBean> list) { this.list = list; inflater = LayoutInflater.from(context); } //该方法返回ListView需要显示的数据量 @Override public int getCount() { return list.size(); } //该方法返回指定索引(position)所对应的数据项 @Override public Object getItem(int position) { return list.get(position); } //该方法返回对应的索引 @Override public long getItemId(int position) { return 0; } // @Override public View getView(int position, View convertView, ViewGroup parent) { View view = inflater.inflate(R.layout.laytou_item, null); ImageView imageView = (ImageView) view.findViewById(R.id.iv_icon); TextView name = (TextView) view.findViewById(R.id.tv_name); TextView desc = (TextView) view.findViewById(R.id.tv_desc); imageView.setImageResource(list.get(position).ItemImageResid); name.setText(list.get(position).ItemName); desc.setText(list.get(position).Itemdesc); return view; } }
运行结果:
上面的代码中,我们可以看到一个ListView中的每一项都是我们直接使用Inflater新建并返回的,完全没有使用到ListView提供的缓存机制
View view = inflater.inflate(R.layout.laytou_item, null); ...... return view;
有关inflater的内容,可以看这里:<转> Android LayoutInflater详解 Android 中LayoutInflater的使用
我们稍对代码做一下修改:
package cn.lixyz.baseadaptertest; import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.ImageView; import android.widget.TextView; import java.util.List; /** * Created by LGB on 2015/9/9. */ public class MyAdapter extends BaseAdapter { private List<ItemBean> list; private LayoutInflater inflater; public MyAdapter(Context context, List<ItemBean> list) { this.list = list; inflater = LayoutInflater.from(context); } //该方法返回ListView需要显示的数据量 @Override public int getCount() { return list.size(); } //该方法返回指定索引(position)所对应的数据项 @Override public Object getItem(int position) { return list.get(position); } //该方法返回对应的索引 @Override public long getItemId(int position) { return 0; } //该方法返回每一项所显示的内容 @Override public View getView(int position, View convertView, ViewGroup parent) { if (convertView == null) { convertView = inflater.inflate(R.layout.laytou_item, null); } ImageView imageView = (ImageView) convertView.findViewById(R.id.iv_icon); TextView name = (TextView) convertView.findViewById(R.id.tv_name); TextView desc = (TextView) convertView.findViewById(R.id.tv_desc); imageView.setImageResource(list.get(position).ItemImageResid); name.setText(list.get(position).ItemName); desc.setText(list.get(position).Itemdesc); return convertView; } }
我们先判断要显示的view是否已经存在,如果不存在,才创建,如果存在,就直接返回,这样也就避免了多次重复创建。
现在避免了多次创建View对象了,但是每次调用getView的时候,仍然会多次使用findViewById来创建组件,那么有没有办法可以避免在这个问题上面浪费资源呢,这里就使用到了view的getTag和setTag,通常我们会创建一个ViewHolder类然后实例化,然后通过setTag的方法将这个ViewHolder对象和View连接起来
看代码:
package cn.lixyz.baseadaptertest; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.AdapterView; import android.widget.ListView; import android.widget.Toast; import java.util.ArrayList; import java.util.List; public class MainActivity extends AppCompatActivity { List<ItemBean> itemBeanList; ListView listView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); itemBeanList = new ArrayList<ItemBean>(); for (int i = 0; i < 20; i++) { ItemBean ib = new ItemBean(R.drawable.icon, "标题" + i, "内容" + i); itemBeanList.add(ib); } listView = (ListView) findViewById(R.id.listView); listView.setAdapter(new MyAdapter(this, itemBeanList)); listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { Toast.makeText(MainActivity.this, "你选择的是:" + position, Toast.LENGTH_SHORT).show(); } }); } }
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".MainActivity"> <ListView android:id="@+id/listView" android:layout_width="match_parent" android:layout_height="match_parent" /> </LinearLayout>
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <ImageView android:id="@+id/iv_icon" android:layout_width="60dp" android:layout_height="60dp" android:layout_marginLeft="10dp" android:layout_marginTop="10dp" /> <TextView android:id="@+id/tv_name" android:layout_width="match_parent" android:layout_height="35dp" android:layout_marginTop="10dp" android:layout_toEndOf="@+id/iv_icon" android:gravity="center" android:text="标题" android:textSize="25dp" /> <TextView android:id="@+id/tv_desc" android:layout_width="match_parent" android:layout_height="25dp" android:layout_below="@+id/tv_name" android:layout_marginLeft="10dp" android:layout_toEndOf="@+id/iv_icon" android:gravity="center" android:text="内容内容内容内容" /> </RelativeLayout>
package cn.lixyz.baseadaptertest;
/**
* Created by LGB on 2015/9/9.
*/
public class ItemBean {
public int ItemImageResid;
public String ItemName;
public String Itemdesc;
public ItemBean(int ItemImageResid, String ItemName, String Itemdesc) {
this.ItemImageResid = ItemImageResid;
this.ItemName = ItemName;
this.Itemdesc = Itemdesc;
}
}
package cn.lixyz.baseadaptertest; import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.ImageView; import android.widget.TextView; import java.util.List; /** * Created by LGB on 2015/9/9. */ public class MyAdapter extends BaseAdapter { private List<ItemBean> list; private LayoutInflater inflater; public MyAdapter(Context context, List<ItemBean> list) { this.list = list; inflater = LayoutInflater.from(context); } //该方法返回ListView需要显示的数据量 @Override public int getCount() { return list.size(); } //该方法返回指定索引(position)所对应的数据项 @Override public Object getItem(int position) { return list.get(position); } //该方法返回对应的索引 @Override public long getItemId(int position) { return position; } //该方法返回每一项所显示的内容 @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder vh = null; if (convertView == null) { vh = new ViewHolder(); convertView = inflater.inflate(R.layout.laytou_item, null); vh.imageView = (ImageView) convertView.findViewById(R.id.iv_icon); vh.name = (TextView) convertView.findViewById(R.id.tv_name); vh.desc = (TextView) convertView.findViewById(R.id.tv_desc); convertView.setTag(vh); } else { vh = (ViewHolder) convertView.getTag(); } vh.imageView.setImageResource(list.get(position).ItemImageResid); vh.name.setText(list.get(position).ItemName); vh.desc.setText(list.get(position).Itemdesc); return convertView; } //创建ViewHolder,避免重复的findViewById操作浪费资源 class ViewHolder { public ImageView imageView; public TextView name; public TextView desc; } }
运行结果是一样的
在上面的代码中,我们创建了一个ViewHolder对象,用来保存一个Item的内容,然后使用setTag()将View和ViewHolder对象连接起来,假如convertView不为空的话,直接使用getTag来获取View对象,然后再给这个对象的属性赋值(也就是给Item的组件赋值),这样就充分利用了ListView的缓存机制,避免重复findViewById造成资源的浪费
总结一下使用ListView缓存机制的思路
1)创建Bean对象,用户封装数据(ItemBean)
2)在构造方法中初始化用户映射的数据List
3)创建ViewHolder类,创建布局映射关系
4)判断convertView是否为空,如果为空,则创建,并设置tag,如果不为空,则通过getTag来去除ViewHolder
5)给ViewHolder中的控件设置数据