开源Jamendo在线音乐播放器源码(四)
上文中我们介绍了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方面的已经介绍完毕了,接下来就是菜单部分。