【休止符是音乐中无声的节奏进行,恰似此时无声胜有声;而音符是音乐中有声的进行,是唱奏出来的,但相对应的几分音符与几分休止符的时值是相同的】
--- 《五线谱基础教程》
一 自定义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(); }