现在的位置: 首页 > 综合 > 正文

开源Jamendo在线音乐播放器源码(四)

2013年03月10日 ⁄ 综合 ⁄ 共 11953字 ⁄ 字号 评论关闭

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

抱歉!评论已关闭.