開源Jamendo在線音樂播放器源碼(四)


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

View Code
<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>

View Code
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是怎么寫的。

View Code
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,我們再來看看這個布局文件是怎么做的。

View Code
<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.。代碼如下:

View Code
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();方法

View Code
/**
* 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方面的已經介紹完畢了,接下來就是菜單部分。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM