上文中我们介绍了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"); } } };
关于ListView方面的已经介绍完毕了,接下来就是菜单部分。