上文中我們介紹了com.teleca.jamendo.util.FixedViewFlipper的用法以及作用,現在我們再介紹ListView中的內容,相關布局如下:

<android.gesture.GestureOverlayView
xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/gestures"
android:layout_width="fill_parent" android:layout_height="fill_parent"
android:gestureStrokeType="multiple"
android:eventsInterceptionEnabled="false" android:orientation="vertical">
<ListView android:id="@+id/HomeListView"
android:layout_width="fill_parent" android:layout_height="fill_parent"
android:divider="#000" />
</android.gesture.GestureOverlayView>
我們會看到手勢容器android.gesture.GestureOverlayView中只有一個ListView,那么跟上圖多個ListView組是怎么對應的呢?答案是一個ListView可分不同組,不同組是通過Adapter實現的。這個跟通訊錄中聯系人列表中分組是一樣的。
在學習這個之前,我們先來看看自定義的抽象基類ArrayListAdapter<T>

public abstract class ArrayListAdapter<T> extends BaseAdapter{
/**
* @uml.property name="mList"
* @uml.associationEnd multiplicity="(0 -1)" elementType="com.teleca.jamendo.api.Album"
*/
protected ArrayList<T> mList;
/**
* @uml.property name="mContext"
* @uml.associationEnd multiplicity="(0 -1)" elementType="com.teleca.jamendo.adapter.AlbumAdapter$ViewHolder"
*/
protected Activity mContext;
/**
* @uml.property name="mListView"
* @uml.associationEnd
*/
protected ListView mListView;
public ArrayListAdapter(Activity context){
this.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;
}
@Override
abstract public View getView(int position, View convertView, ViewGroup parent);
public void setList(ArrayList<T> list){
this.mList = list;
notifyDataSetChanged();
}
public ArrayList<T> getList(){
return mList;
}
public void setList(T[] list){
ArrayList<T> arrayList = new ArrayList<T>(list.length);
for (T t : list) {
arrayList.add(t);
}
setList(arrayList);
}
public ListView getListView(){
return mListView;
}
public void setListView(ListView listView){
mListView = listView;
}
}
因為我們只需要傳遞實體對象到Adapter中,因此可以定義一個抽象的泛型類ArrayListAdapter<T>,並繼承BaseAdapter,因為BaseAdapter提供了更高的靈活性。類模型圖如下:
這里定義了三個保護屬性,
protected ArrayList<T> mList;
protected Activity mContext;
protected ListView mListView;
同時重寫了相關的方法,並在添加進泛型類列表並刷新。
public void setList(ArrayList<T> list){
this.mList = list;
notifyDataSetChanged();
}
在
@Override
abstract public View getView(int position, View convertView, ViewGroup parent);中並未重寫,因為不同UI需求,因此交予子類實現。
然后我們再來看看作為分組ListView的Adapter是怎么寫的。

public class PurpleAdapter extends ArrayListAdapter<PurpleEntry> {
public PurpleAdapter(Activity context) {
super(context);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View row=convertView;
ViewHolder holder;
if (row==null) {
LayoutInflater inflater = mContext.getLayoutInflater();
row=inflater.inflate(R.layout.purple_row, null);
holder = new ViewHolder();
holder.image = (ImageView)row.findViewById(R.id.PurpleImageView);
holder.text = (TextView)row.findViewById(R.id.PurpleRowTextView);
row.setTag(holder);
}
else{
holder = (ViewHolder) row.getTag();
}
if(mList.get(position).getText() != null){
holder.text.setText(mList.get(position).getText());
} else if(mList.get(position).getTextId() != null){
holder.text.setText(mList.get(position).getTextId());
}
if(mList.get(position).getDrawable() != null){
holder.image.setImageResource(mList.get(position).getDrawable());
} else {
holder.image.setVisibility(View.GONE);
}
return row;
}
/**
* Class implementing holder pattern,
* performance boost
*
* @author Lukasz Wisniewski
*/
static class ViewHolder {
ImageView image;
TextView text;
}
}
因為基本的邏輯已經封裝在基類的ArrayListAdapter<T>中,在這里這需要繪制ListView 項即可,我們看到它是是實例化了一個布局文件purple_row,我們再來看看這個布局文件是怎么做的。

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent" android:orientation="horizontal"
android:layout_height="wrap_content" android:background="@drawable/purple_entry_bg"
android:gravity="left|center_vertical" android:minHeight="60dip"
android:paddingRight="20dip" android:paddingLeft="10dip">
<com.teleca.jamendo.widget.RemoteImageView
android:id="@+id/PurpleImageView" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:paddingRight="10dip"></com.teleca.jamendo.widget.RemoteImageView>
<TextView android:id="@+id/PurpleRowTextView"
android:layout_height="wrap_content" android:layout_width="fill_parent"
android:layout_weight="1" android:textSize="20dip"
android:textColor="@drawable/purple_entry_color"></TextView>
<ImageView android:id="@+id/PurpleRowArrow"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:src="@drawable/arrow"></ImageView>
</LinearLayout>
很明顯他是由2張圖片一個文本橫排布局,作為一個ListView項的,但是這里先要注意一點,其中一個圖片類使用的是自定義的com.teleca.jamendo.widget.RemoteImageView類,而不是我們的ImageView類,為什么呢?因為這個是從網絡上下載下來的圖片作為專輯圖片,因此需要緩存,避免浪費流量,於是自定義個類主要用於緩存,com.teleca.jamendo.widget.RemoteImageView類已經做了緩存的封裝。這個以后再慢慢講解。
子類實現了父類規定的抽象方法public View getView(int position, View convertView, ViewGroup parent) ,當然這個方法是解析我們的ListView項,同時設置相對應的圖片以及文字說明。這里需要注意的是它把ViewHolder緩存在Tag中,避免重復性渲染ListView項,一定程度上進行了優化。
既然又了上面的講解,那么我們就來看看如何為ListView添加多組的分欄。
我們可以定義一個BaseAdapter,並在里面定義接受不同的BaseAdapter,然后將多個BaseAdapter合並為一個,再提供給ListView.。代碼如下:

public class SeparatedListAdapter extends BaseAdapter {
/**
* @uml.property name="sections"
* @uml.associationEnd qualifier="section:java.lang.String android.widget.Adapter"
*/
public final Map<String,Adapter> sections = new LinkedHashMap<String,Adapter>();
/**
* @uml.property name="headers"
* @uml.associationEnd multiplicity="(0 -1)" elementType="java.lang.String"
*/
public final ArrayAdapter<String> headers;
public final static int TYPE_SECTION_HEADER = 0;
public SeparatedListAdapter(Context context) {
headers = new ArrayAdapter<String>(context, R.layout.list_header);
}
public void addSection(String section, Adapter adapter) {
this.headers.add(section);
this.sections.put(section, adapter);
}
public Object getItem(int position) {
for(Object section : this.sections.keySet()) {
Adapter adapter = sections.get(section);
int size = adapter.getCount() + 1;
// check if position inside this section
if(position == 0) return section;
if(position < size) return adapter.getItem(position - 1);
// otherwise jump into next section
position -= size;
}
return null;
}
public int getCount() {
// total together all sections, plus one for each section header
int total = 0;
for(Adapter adapter : this.sections.values())
total += adapter.getCount() + 1;
return total;
}
public int getViewTypeCount() {
// assume that headers count as one, then total all sections
int total = 1;
for(Adapter adapter : this.sections.values())
total += adapter.getViewTypeCount();
return total;
}
public int getItemViewType(int position) {
int type = 1;
for(Object section : this.sections.keySet()) {
Adapter adapter = sections.get(section);
int size = adapter.getCount() + 1;
// check if position inside this section
if(position == 0) return TYPE_SECTION_HEADER;
if(position < size) return type + adapter.getItemViewType(position - 1);
// otherwise jump into next section
position -= size;
type += adapter.getViewTypeCount();
}
return -1;
}
public boolean areAllItemsSelectable() {
return false;
}
public boolean isEnabled(int position) {
return (getItemViewType(position) != TYPE_SECTION_HEADER);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
int sectionnum = 0;
for(Object section : this.sections.keySet()) {
Adapter adapter = sections.get(section);
int size = adapter.getCount() + 1;
// check if position inside this section
if(position == 0) return headers.getView(sectionnum, convertView, parent);
if(position < size) return adapter.getView(position - 1, convertView, parent);
// otherwise jump into next section
position -= size;
sectionnum++;
}
return null;
}
@Override
public long getItemId(int position) {
return position;
}
}
從代碼以及類結構圖我們可以知道
public final ArrayAdapter<String> headers;是用來標明不同的組,
public final Map<String,Adapter> sections = new LinkedHashMap<String,Adapter>(); 用來存貯不同Adapter
當然它還提供了public void addSection(String section, Adapter adapter)方法來添加Adpater,這樣就可以擴展成多組的ListView了。
不過最重要的還是getView方法,這里才是繪制不同組的實現邏輯。根據不同adapter返回不同的ListView項,同時返回了分組說明。
介紹完最重要的SeparatedListAdapter后,我們再來看看它的使用。
我們一旦進入主界面是怎么顯示分組ListView的呢?
看看這段代碼:
@Override
protected void onResume() {
fillHomeListView();
boolean gesturesEnabled = PreferenceManager.getDefaultSharedPreferences(this).getBoolean("gestures", true);
mGestureOverlayView.setEnabled(gesturesEnabled);
super.onResume();
}
它重寫恢復這個方法中填充了ListView同時根據Preference設置是否啟用手勢。
接下啦看看fillHomeListView();方法

/**
* Fills ListView with clickable menu items
*/
private void fillHomeListView(){
mBrowseJamendoPurpleAdapter = new PurpleAdapter(this);
mMyLibraryPurpleAdapter = new PurpleAdapter(this);
ArrayList<PurpleEntry> browseListEntry = new ArrayList<PurpleEntry>();
ArrayList<PurpleEntry> libraryListEntry = new ArrayList<PurpleEntry>();
// BROWSE JAMENDO
browseListEntry.add(new PurpleEntry(R.drawable.list_search, R.string.search, new PurpleListener(){
@Override
public void performAction() {
SearchActivity.launch(HomeActivity.this);
}
}));
browseListEntry.add(new PurpleEntry(R.drawable.list_radio, R.string.radio, new PurpleListener(){
@Override
public void performAction() {
RadioActivity.launch(HomeActivity.this);
}
}));
browseListEntry.add(new PurpleEntry(R.drawable.list_top, R.string.most_listened, new PurpleListener(){
@Override
public void performAction() {
new Top100Task(HomeActivity.this, R.string.loading_top100, R.string.top100_fail).execute();
}
}));
// MY LIBRARY
libraryListEntry.add(new PurpleEntry(R.drawable.list_playlist, R.string.playlists, new PurpleListener(){
@Override
public void performAction() {
BrowsePlaylistActivity.launch(HomeActivity.this, Mode.Normal);
}
}));
// check if we have personalized client then add starred albums
final String userName = PreferenceManager.getDefaultSharedPreferences(this).getString("user_name", null);
if(userName != null && userName.length() > 0){
libraryListEntry.add(new PurpleEntry(R.drawable.list_cd, R.string.albums, new PurpleListener(){
@Override
public void performAction() {
StarredAlbumsActivity.launch(HomeActivity.this, userName);
}
}));
}
/* following needs jamendo authorization (not documented yet on the wiki)
* listEntry.add(new PurpleEntry(R.drawable.list_mail, "Inbox"));
*/
// show this list item only if the SD Card is present
if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){
libraryListEntry.add(new PurpleEntry(R.drawable.list_download, R.string.download, new PurpleListener(){
@Override
public void performAction() {
DownloadActivity.launch(HomeActivity.this);
}
}));
}
// listEntry.add(new PurpleEntry(R.drawable.list_star, R.string.favorites, new PurpleListener(){
//
// @Override
// public void performAction() {
// Playlist playlist = new DatabaseImpl(HomeActivity.this).getFavorites();
// JamendroidApplication.getInstance().getPlayerEngine().openPlaylist(playlist);
// PlaylistActivity.launch(HomeActivity.this, true);
// }
//
// }));
// attach list data to adapters
mBrowseJamendoPurpleAdapter.setList(browseListEntry);
mMyLibraryPurpleAdapter.setList(libraryListEntry);
// separate adapters on one list
SeparatedListAdapter separatedAdapter = new SeparatedListAdapter(this);
separatedAdapter.addSection(getString(R.string.browse_jamendo), mBrowseJamendoPurpleAdapter);
separatedAdapter.addSection(getString(R.string.my_library), mMyLibraryPurpleAdapter);
mHomeListView.setAdapter(separatedAdapter);
mHomeListView.setOnItemClickListener(mHomeItemClickListener);
}
雖然很長,但都是做重復性的東西,即是添加PurpleEntry實體項,當然這個實體項中還有監聽器,是為了再點擊ListView項時候觸發而根據不同的PurpleEntry對象執行不同的方法。
核心的東西也是只有幾行代碼而已。
// separate adapters on one list
SeparatedListAdapter separatedAdapter = new SeparatedListAdapter(this);
separatedAdapter.addSection(getString(R.string.browse_jamendo), mBrowseJamendoPurpleAdapter);
separatedAdapter.addSection(getString(R.string.my_library), mMyLibraryPurpleAdapter);
mHomeListView.setAdapter(separatedAdapter);
mHomeListView.setOnItemClickListener(mHomeItemClickListener);
定義一個SeparatedListAdapter適配器作為主Adapter然后向Map中添加不同的子adapter,最后綁定這個SeparatedListAdapter到ListView中,同時設置ListView的項點擊事件監聽。
我們再來看看這個監聽吧。
/**
* Launches menu actions
* @uml.property name="mHomeItemClickListener"
* @uml.associationEnd multiplicity="(1 1)"
*/
private OnItemClickListener mHomeItemClickListener = new OnItemClickListener(){
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int index,
long time) {
try{
PurpleListener listener = ((PurpleEntry)adapterView.getAdapter().getItem(index)).getListener();
if(listener != null){
listener.performAction();
}
}catch (ClassCastException e) {
Log.w(TAG, "Unexpected position number was occurred");
}
}
};
我們可以看到在這個監聽器中獲得實體PurpleEntry的PurpleListener接口,並執行接口定義的方法。這就是讓相關的實體對象處理它自身的內容了。分離了實現。
關於ListView方面的已經介紹完畢了,接下來就是菜單部分。