ListView是个很便捷的控件,通过适配器可以很简单的做出一个列表,但也有一些不足的地方,比如在适配器如何根据不同位置提供不同视图则需要我们自己去处理.这里我分享一下我自己的方法.
首先要建立几个数据项,这里我使用的是工厂模式,代码如下:
package newideas.myapplication;
/**
* 数据模型,为了更直观的理解,简写了几种数据模型并都写在一个类中,
* 使用工厂的设计模式来生成数据模型.
* Created by wangyx19950207 on 15/11/4.
*/
public class DataFactory {
/*-----构造方法-----*/
/**
* 不可被实例
*/
private DataFactory(){}
/*-----生成数据方法-----*/
/**
* 生成一个TextView的标识项
* @param content
* @return
*/
public static TextViewItem generateTextViewItem(String content){
return new TextViewItem(content);
}
/**
* 生成一个Button的标识项
* @param content
* @return
*/
public static ButtonItem generateButtonItem(String content){
return new ButtonItem(content);
}
/**
* 生成一个CheckBox的标识项
* @param content
* @return
*/
public static CheckBoxItem generateCheckBoxItem(String content){
return new CheckBoxItem(content);
}
/*-----静态内部类-----*/
/**
* 作为生成TextView的标识
* 至于 DifferentViewAdapter.ListViewItem 再等下会有说明,可以不用去想他的作用
*/
public static class TextViewItem extends DifferentViewAdapter.ListViewItem{
/*-----属性成员-----*/
public final String m_Content;//文本内容
/*-----构造方法-----*/
/**
* 获得文本内容
* @param content
*/
private TextViewItem(String content)
{
m_Content = content;
}
}
/**
* 作为生成Button的标识
*/
public static class ButtonItem extends DifferentViewAdapter.ListViewItem{
/*-----属性成员-----*/
public final String m_Content;//文本内容
/*-----构造方法-----*/
/**
* 获得文本内容
* @param content
*/
private ButtonItem(String content)
{
m_Content = content;
}
}
/**
* 作为生成CheckBox的标识
*/
public static class CheckBoxItem extends DifferentViewAdapter.ListViewItem{
/*-----属性成员-----*/
public final String m_Content;//文本内容
/*-----构造方法-----*/
/**
* 获得文本内容
* @param content
*/
private CheckBoxItem(String content)
{
m_Content = content;
}
}
}
然后自定义一个适配器
package newideas.myapplication;
import android.content.Context;
import android.graphics.drawable.ColorDrawable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.LinearLayout;
import android.widget.TextView;
import static newideas.myapplication.DataFactory.*;
import java.util.List;
/**
* ListView设置的适配器传递的是ListAdapter接口,而BaseAdapter抽象类实现了这个接口的一些我们
* 基本不会用的方法,并将一些核心方法以抽象方法的形式交给我们来重写
* Created by wang19950207 on 15/11/4.
*/
public class DifferentViewAdapter extends BaseAdapter{
/*-----属性成员-----*/
private final Context m_Context;//上下文,主要是用来创建视图使用
private final List<ListViewItem> m_ListItems;//列表项目的全部数据
private final ViewCache m_ViewCache;//列表项目视图缓存池
/*-----构造函数-----*/
/**
* 获得上下文的引用
* 获得列表数据的引用
* 获得列表项目视图缓存池的引用
* @param context
* @param listViewItems
*/
public DifferentViewAdapter(Context context, List<ListViewItem> listViewItems)
{
m_Context = context;
m_ListItems = listViewItems;
m_ViewCache = ViewCache.getViewCache();
}
/*-----重写方法-----*/
/**
* 获得列表项目的总数
* 注意:这里如果返回0的话列表视图不会生成任何列表项
* 这里我们一般返回列表数据的总数
* @return
*/
@Override
public int getCount() {
return m_ListItems.size();
}
/**
* 获得指定位置的列表数据
* @param position
* @return
*/
@Override
public Object getItem(int position) {
return m_ListItems.get(position);
}
/**
* 返回指定位置的列表项目的ID
* 如果没有特殊需要一般返回位置或者0就好
* @param position
* @return
*/
@Override
public long getItemId(int position) {
//return 0;
return position;
}
/**
* 核心方法,当ListView通知适配器指定位置的列表项目将要出现时,
* 将会调用这个函数来获得该位置的列表项目视图.至于第二个convertView参数
* 在此次实例里不会使用,稍后会说明为何放弃使用它
* @param position
* @param convertView
* @param parent
* @return
*/
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view=null;
ListViewItem listViewItem=m_ListItems.get(position);
if(listViewItem instanceof TextViewItem)
{
TextViewItem item= (TextViewItem) listViewItem;
TextView textView= (TextView) m_ViewCache.getViewByResouce(
LayoutInflater.from(m_Context),R.layout.listview_text,"TextView");
textView.setText(item.m_Content);
view=textView;
}
else if(listViewItem instanceof ButtonItem)
{
ButtonItem item= (ButtonItem) listViewItem;
Button button= (Button) m_ViewCache.getViewByResouce(
LayoutInflater.from(m_Context),R.layout.listview_button,"Button");
button.setText(item.m_Content);
view=button;
}
else if(listViewItem instanceof CheckBoxItem)
{
CheckBoxItem item= (CheckBoxItem) listViewItem;
CheckBox checkBox= (CheckBox) m_ViewCache.getViewByResouce(
LayoutInflater.from(m_Context),R.layout.listview_checkbox,"CheckBox");
checkBox.setText(item.m_Content);
view=checkBox;
}
return view;
}
/*-----静态内部类-----*/
/**
* 列表数据的基类,是上面List<ListViewItem>的参数类型信息,
* 说实话用List<Object>也可以,只不过使用这个的好处是给这个List添加了
* 一个边界,意思是只能该类或该类的子类才可以添加至此List,为你的程序
* 添加了保障.一不小心废话了0 0
*/
public static class ListViewItem{
}
}
这里讲一下为什么不用getView的第二个参数:
我们要知道ListView有一个机制,就是当某一个视图变得不可视的时候(超出ListView范围外),ListView会移除该视图并保存视图的引用;当一个新的列表项即将出现时,ListView会查找保存的引用中是否有没有被使用的视图,如果有则会传递至getView的第二个参数成为可以直接使用的视图,无需再次实例化一个新的视图对象.但在这里,因为我们添加多种不同的视图,ListView无法判别传递的视图应用是否是你所需要的那种视图,这时如果进行强制类型转换会抛出ClassCastException(类型转换异常,例如你把一个TextView转换成Button,就会抛出该异常).
所以为了解决这个问题,我使用了一个视图缓存池,来实现类似的功能,代码如下:
package newideas.myapplication;
import android.content.res.Resources;
import android.view.LayoutInflater;
import android.view.View;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/**
* Created by wang19950207 on 15/11/4.
*/
public class ViewCache {
/*-----属性成员-----*/
private static ViewCache m_ViewCache;//单例对象
private ReferenceQueue<View> m_ReferenceQueue;//视图的引用队列
private Map<String,List<ViewSoftReference>> m_SoftMap;//软引用映射表
/*-----构造方法-----*/
/**
* 限制外部实例化
* 实例化软引用映射表
* 实例化视图的引用队列
*/
private ViewCache(){
m_SoftMap=new HashMap<>();
m_ReferenceQueue=new ReferenceQueue<>();
}
/*-----单例方法-----*/
/**
* 获得视图缓存池的单例
* @return
*/
public static ViewCache getViewCache()
{
if(m_ViewCache==null)
m_ViewCache=new ViewCache();
return m_ViewCache;
}
/*-----获取视图方法-----*/
/**
* 从视图缓存池内获取视图,如果该键值映射的列表内存在
* 没有被添加至视图组的视图,则不会创建新的视图,并返回该视图;
* 如果不存在,则会创建一个新的视图并缓存
* @param layoutInflater
* @param resid
* @param Key
* @return
*/
public View getViewByResouce(LayoutInflater layoutInflater,int resid,String Key)
{
clearCache();
View view=null;
List<ViewSoftReference> list=m_SoftMap.get(Key);
if(list!=null)
{
view=findViewOutOfViewGroup(list);
}
if(view==null)
{
view=layoutInflater.inflate(resid,null);
cacheView(Key,view);
}
return view;
}
/*-----内部处理方法-----*/
/**
* 找到没有被添加至视图组的视图并返回该视图,如果没有找到则返回null
* @param list
* @return
*/
private View findViewOutOfViewGroup(List<ViewSoftReference> list)
{
View view=null;
for(ViewSoftReference viewSoftReference:list)
{
if(!viewSoftReference.get().isShown())
{
view=viewSoftReference.get();
break;
}
}
return view;
}
/**
* 当视图被垃圾处理回收之后,进行缓存的清理工作
*/
private void clearCache()
{
ViewSoftReference viewSoftReference=null;
while((viewSoftReference= (ViewSoftReference) m_ReferenceQueue.poll())!=null)
{
List<ViewSoftReference> list=m_SoftMap.get(viewSoftReference.m_Key);
list.remove(viewSoftReference);
if(list.size()==0)
m_SoftMap.remove(viewSoftReference.m_Key);
}
}
/**
* 根据键值对视图进行缓存
* @param key
* @param view
*/
private void cacheView(String key,View view)
{
List<ViewSoftReference> list=m_SoftMap.get(key);
if(list==null)
{
list=new LinkedList<ViewSoftReference>();
m_SoftMap.put(key,list);
}
ViewSoftReference viewSoftReference=new ViewSoftReference(view,m_ReferenceQueue,key);
list.add(viewSoftReference);
}
/*-----静态内部类-----*/
private static class ViewSoftReference extends SoftReference<View>{
/*-----属性成员-----*/
private final String m_Key;//键值
/*-----构造方法-----*/
/**
* 将该视图加入至引用队列中进行监控
* @param r
* @param q
*/
public ViewSoftReference(View r, ReferenceQueue<? super View> q,String key) {
super(r, q);
m_Key=key;
}
}
}
使用视图缓存池的好处在于保存的是视图的软引用.当垃圾清理时,视图也会被清理掉,但可能会造成额外的开销
最后附上布局与Activity的代码
Activity:
package newideas.myapplication;
import android.app.Activity;
import android.os.Bundle;
import android.widget.ListView;
import static newideas.myapplication.DifferentViewAdapter.*;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
List<ListViewItem> list=new ArrayList<>();//这里使用ArrayList,毕竟ListView增删数据操作比较少
list.add(DataFactory.generateButtonItem("Button1"));
list.add(DataFactory.generateButtonItem("Button2"));
list.add(DataFactory.generateButtonItem("Button3"));
list.add(DataFactory.generateCheckBoxItem("CheckBox1"));
list.add(DataFactory.generateCheckBoxItem("CheckBox2"));
list.add(DataFactory.generateCheckBoxItem("CheckBox3"));
list.add(DataFactory.generateTextViewItem("Text1"));
list.add(DataFactory.generateTextViewItem("Text2"));
list.add(DataFactory.generateTextViewItem("Text3"));
list.add(DataFactory.generateButtonItem("Button4"));
list.add(DataFactory.generateButtonItem("Button5"));
list.add(DataFactory.generateButtonItem("Button6"));
list.add(DataFactory.generateCheckBoxItem("CheckBox4"));
list.add(DataFactory.generateCheckBoxItem("CheckBox5"));
list.add(DataFactory.generateCheckBoxItem("CheckBox6"));
list.add(DataFactory.generateTextViewItem("Text4"));
list.add(DataFactory.generateTextViewItem("Text5"));
list.add(DataFactory.generateTextViewItem("Text6"));
DifferentViewAdapter differentViewAdapter=new DifferentViewAdapter(this,list);
((ListView)findViewById(R.id.main_listview)).setAdapter(differentViewAdapter);
}
}
activity_main.xml:
<ListView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:id="@+id/main_listview"/>
listview_button.xml:
<?xml version="1.0" encoding="utf-8"?>
<Button xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</Button>
listview_checkbox.xml:
<?xml version="1.0" encoding="utf-8"?>
<CheckBox xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</CheckBox>
listview_text.xml:
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="100dp">
</TextView>