【休止符是音乐中无声的节奏进行,恰似此时无声胜有声;而音符是音乐中有声的进行,是唱奏出来的,但相对应的几分音符与几分休止符的时值是相同的】
--- 《五线谱基础教程》
一 自定义Adapter的抽象基类
Android项目开发中经常会用到ListView、GridView等需要和Adapter配合使用的控件,对于界面界面较多,且用到多个不同布局的ListView等时,自然而然的就会面临需要定义多个Adapter类的情况。
一般为了提高灵活性,我们都会让自定义的Adapter继承自BaseAdapter,并重写其中的四个抽象函数,典型的类结构如下:
public class MyBaseAdapter extends BaseAdapter {
@Override
public int getCount() {
return 0;
}
@Override
public Object getItem(int position) {
return null;
}
@Override
public long getItemId(int position) {
return 0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
return null;
}
}
但仅用上面的代码是不能实现所需功能的,因为Adapter的本意就是给View提供数据的,因此,需要在上面的代码基础上进一步完善,首先由于数据的类型千变万化,因此采用泛型表示类型;其次,数据一般都会有很多条,因此,需要一个容器来存储数据,这里采用常见的ArrayList;最后,Adapter还需要维护一个到View的引用,并且有可能需要用到Android的上下文Context,在Jamendo这个项目中,View类对应的是ListView,Context就使用ListView所在的Activity,因此,得出下面通用的封装了BaseAdapter的抽象类,其中getView函数留给子类来实现。
public abstract class ArrayListAdapter<T> extends BaseAdapter {
protected ArrayList<T> mList; // 数据容器
protected Activity mContext; // Android上下文环境
protected ListView mListView; // 使用这个Adapter的ListView
public ArrayListAdapter(Activity context) {
mContext = context;
}
@Override
public int getCount() {
if (mList != null) {
return mList.size();
} else {
return 0;
}
}
@Override
public Object getItem(int position) {
return mList == null ? null : mList.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
// 留给子类实现,因为不同ListView的ListItem布局不一样
@Override
abstract public View getView(int position, View convertView,
ViewGroup parent);
// 改变了Adapter中的数据,需要刷新ListView
public void setList(ArrayList<T> list) {
this.mList = list;
notifyDataSetChanged();
}
public void setList(T[] list) {
ArrayList<T> arrayList = new ArrayList<T>(list.length);
for (T t : list) {
arrayList.add(t);
}
setList(arrayList);
}
public ArrayList<T> getList() {
return mList;
}
public ListView getListView() {
return mListView;
}
public void setListView(ListView listView) {
mListView = listView;
}
}
二 Jamendo中Adapter的体系及实现
ArrayListAdapter的子类都是用于ListView的,各个子类实现父类的抽象函数getView,在该函数中实现列表项的布局,并返回列表项对应的视图。本文就不细讲每个Adapter对应的视图,后面分析各个界面时会涉及到,这里只以DownloadJobAdapter作为例子说明ArrayListAdapter的子类的通用实现方式。
如下图所示是下载管理页面,列表中第一项是下载完成后的视图,第二项是正在下载视图,从中可以看出我们的DownloadJobAdapter的getView函数返回的View中起码包含:显示歌曲名称的TextView、显示专辑名称的TextView、表示下载进度的TextView以及一个进度条。
看DownloadJobAdapter类中定义的静态内部类ViewHolder可以看出,下载页面的列表项确实就是上述四个控件组成:
static class ViewHolder {
TextView songName;
TextView songArtistAlbum;
TextView songProgressText;
ProgressBar progressBar;
}
getView函数一般都会使用ViewHolder模式来优化性能,避免进行不必要的重复工作。由于Android为我们缓存了convertView,所以首先判断入参ConvertView是否为空,为空说明从来没有创建过,需要创建这个View;否则说明这个是之前构建的View的缓存,由于布局资源不变,对应的控件资源可以复用。而这些控件资源就是使用ViewHolder来存储的,方便使用convertView的setTag函数将它作为一个整体存储在convertView中。
可以注意到,setTag函数还有一个重载的函数,这两个函数定义如下:
public void setTag(final Object tag) {
mTag = tag;
}
public void setTag(int key, final Object tag) {
// If the package id is 0x00 or 0x01, it's either an undefined package
// or a framework id
if ((key >>> 24) < 2) {
throw new IllegalArgumentException("The key must be an application-specific "
+ "resource id.");
}
setTagInternal(this, key, tag);
}
private static void setTagInternal(View view, int key, Object tag) {
SparseArray<Object> tags = null;
synchronized (sTagsLock) {
if (sTags == null) {
sTags = new WeakHashMap<View, SparseArray<Object>>();
} else {
tags = sTags.get(view);
}
}
if (tags == null) {
tags = new SparseArray<Object>(2);
synchronized (sTagsLock) {
sTags.put(view, tags);
}
}
tags.put(key, tag);
}
可以看到,setTag(Object)将对象存在View类的成员变量mTag中;而setTag(int, Object)则使用WeakHashMap来建立View和要存储的对象之间的弱引用关系。如果需要在View中存放多个对象时,可以使用第二个重载函数,需要注意一点时,setTag(int, Object)中的int类型的参数必须定义在res/values/strings.xml文件中,格式如下:
<resources>
<item type="id" name="wen" />
<item type="id" name="asce" />
</resources>
在代码中引用如下:
// 设置tag时
convertView.setTag(R.id.wen, "wen1885");
convertView.setTag(R.id.asce, "asce1885");
...
// 获取tag时
String wen = convertView.getTag(R.id.wen);
String asce = convertView.getTag(R.id.asce);
最后,看下DownloadJobAdapter类的getView函数关键代码如下:
ViewHolder holder = null;
if (convertView == null) {
LayoutInflater inflater = mContext.getLayoutInflater();
convertView = inflater.inflate(R.layout.download_row, null);
holder = new ViewHolder();
holder.songName = (TextView) convertView.findViewById(R.id.TrackRowName);
holder.songArtistAlbum = (TextView) convertView.findViewById(R.id.TrackRowArtistAlbum);
holder.songProgressText = (TextView) convertView.findViewById(R.id.TrackRowProgress);
holder.progressBar = (ProgressBar) convertView.findViewById(R.id.ProgressBar);
// 将控件资源作为整体存放在convertView中
convertView.setTag(holder);
mContext.registerForContextMenu(convertView);
} else {
holder = (ViewHolder) convertView.getTag();
}