首页 > 代码库 > ListView分栏--制作分栏音乐列表

ListView分栏--制作分栏音乐列表

之前我遇到过这样的需求,要求在ListView中按时间对数据分栏,当时的做法是在每个ListView的item中加入时间栏的布局,然后在代码中控制时间栏

的显示与隐藏。

但其实重写Adapter两个方法后就可以完成这个任务,当ListView中带有不同布局的时候,可以根据itemType来加载不同的布局。

int getItemViewType(int position) 返回指定position的itemView的viewType,用于加载不同布局。此方法必须返回0到getViewTypeCount()-1

的数字或者IGNORE_ITEM_VIEW_TYPE。

int getViewTypeCount() 返回你这个ListView有多少个不同的布局。

让我们先来看看两张分栏后的效果图:

     

这里按照歌曲名拼音的首字母分栏,把汉字转为拼音我用了Pinyin4j,例如"你好"可转为"NIHAO",由于这不是这篇文章的重点,不知道的可自行百度。

那么下面来看看怎么一步步地实现吧!

1.由图中列表可以看出,我们要显示歌曲名,歌手名,分栏需要用到歌曲名对应的汉字拼音,所以有了下面的MediaItem实体。

 1 public class MediaItem implements Serializable { 2     private static final long serialVersionUID = 1L; 3  4     private int id; // ID 5     private String songName; // 歌曲名 6     private String singerName; // 歌手名 7     private String sortKey; // 歌曲名的拼音(如"你好"-->"NIHAO") 8  9     public String getSongName() {10         return songName;11     }12 13     public void setSongName(String songName) {14         this.songName = songName;15     }16 17     public String getSingerName() {18         return singerName;19     }20 21     public void setSingerName(String singerName) {22         this.singerName = singerName;23     }24 25     public int getId() {26         return id;27     }28 29     public void setId(int id) {30         this.id = id;31     }32 33     public String getSortKey() {34         return sortKey;35     }36 37     public void setSortKey(String sortKey) {38         this.sortKey = sortKey;39     }40 }

2.接下来就是需要从数据库中查出歌曲信息,使用android提供的uri:MediaStore.Audio.Media.EXTERNAL_CONTENT_URI查询我们需要的字段,

然后封装成MediaItem实体,用于绑定ListView。如果查询时间超过500毫秒,则显示加载的progressBar。

布局比较简单,就是一个ListView和一个ProgressBar,我就不贴出来了。

 1 public class MediaActivity extends Activity { 2     private static final String TAG = "MediaActivity"; 3  4     private static final int MSG_SHOW_PROGRESS_BAR = 100; 5  6     private List<MediaItem> mList; 7     private MediaAdapter mAdapter; 8     private ListView mListView; 9     private ProgressBar mProgressBar;10 11     @Override12     protected void onCreate(Bundle savedInstanceState) {13         super.onCreate(savedInstanceState);14         setContentView(R.layout.activity_media);15 16         initViews();17         // 查询音乐列表18         getData();19     }20 21     private void initViews() {22         mListView = (ListView) findViewById(R.id.media_list);23         mProgressBar = (ProgressBar) findViewById(R.id.progress_bar);24         ViewCompat.setOverScrollMode(mListView, ViewCompat.OVER_SCROLL_NEVER);25     }26 27     private void getData() {28         mList = new ArrayList<MediaItem>();29         // 如果500毫秒内加载完成,则不显示ProgressBar30         mHander.sendEmptyMessageDelayed(MSG_SHOW_PROGRESS_BAR, 500);31         // 使用AsyncTask查询音乐列表,并构造实体列表MediaItem32         new AsyncTask<Void, Void, List<MediaItem>>() {33 34             @Override35             protected List<MediaItem> doInBackground(Void... params) {36                 Log.v(LogUtils.TAG, "AsyncTask doInBackground");37                 Uri uri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;38                 String[] projection = { MediaStore.Audio.Media._ID, // ID39                         MediaStore.Audio.Media.TITLE, // 显示的歌曲名40                         MediaStore.Audio.Media.ARTIST // 艺术家41                 };42                 Cursor cursor = getContentResolver().query(uri, projection, null, null, MediaStore.Audio.Media.DEFAULT_SORT_ORDER);43                 if (cursor != null) {44                     try {45                         while (cursor.moveToNext()) {46                             MediaItem item = new MediaItem();47                             String title = cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.TITLE));48                             item.setId(cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media._ID)));49                             item.setSongName(title);50                             item.setSingerName(cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media.ARTIST)));51                             // 汉字转拼音52                             item.setSortKey(PinYinUtils.getPinYin(title));53                             mList.add(item);54                         }55                     } catch (Exception e) {56                         Log.e(TAG, "get cursor data error!");57                     } finally {58                         cursor.close();59                     }60                 }61                 return mList;62             }63 64             @Override65             protected void onPostExecute(List<MediaItem> result) {66                 Log.v(LogUtils.TAG, "AsyncTask onPostExecute result.size=" + result.size());67                 mHander.removeMessages(MSG_SHOW_PROGRESS_BAR);68                 mAdapter = new MediaAdapter(MediaActivity.this, mList);69                 mListView.setAdapter(mAdapter);70                 mListView.setVisibility(View.VISIBLE);71                 if (mProgressBar.getVisibility() == View.VISIBLE) {72                     mProgressBar.setVisibility(View.GONE);73                 }74             }75         }.execute();76     }77 78     // 用于显示ProgressBar79     Handler mHander = new Handler(new Callback() {80         @Override81         public boolean handleMessage(Message msg) {82             switch (msg.what) {83             case MSG_SHOW_PROGRESS_BAR:84                 mProgressBar.setVisibility(View.VISIBLE);85                 break;86 87             default:88                 break;89             }90             return false;91         }92     });93 94 }

3.接下来就是比较重要的Adapter,分栏的任务在这里完成。首先注意adapter绑定的数据源是一个List<TypeItem>,TypeItem对数据做了一层

封装,它包含itemType,就是在getItemViewType()需要返回的参数。而通过generateItems()方法把传入的List<MediaItem>构造成

List<TypeItem>,再加入itemType的同时如果发现MediaItem中歌曲名的拼音首字母不一样,就插入一个分组的头部。然后在getView()中就可以

根据itemType来区分不同的布局,由于具有两个不同的布局,所以定义ViewHolder基类,分栏布局HeaderViewHolder和歌曲列表MediaViewHolder

都继承自ViewHolder,用于缓存视图,然后就可以根据不同的ViewHolder实例来绑定数据(在ListView中有多个布局的时候都可以使用此方法)。

  1 package com.yangy.test.adapter;  2   3 import java.util.ArrayList;  4 import java.util.List;  5   6 import android.content.Context;  7 import android.view.LayoutInflater;  8 import android.view.View;  9 import android.view.ViewGroup; 10 import android.widget.BaseAdapter; 11 import android.widget.TextView; 12  13 import com.yangy.test.model.MediaItem; 14 import com.yy.gallerytest.activity.R; 15  16 public class MediaAdapter extends BaseAdapter { 17      18     private static final int VIEW_TYPE_COUNT = 2; // 有几种不同的布局 19     private static final int VIEW_TYPE_HEADER = 0; // 分组的头部 20     private static final int VIEW_TYPE_ITEM = 1; // 音乐列表item 21  22     private LayoutInflater mInflater; 23     private List<TypeItem> items; 24  25     public MediaAdapter(Context context, List<MediaItem> items) { 26         mInflater = LayoutInflater.from(context); 27         this.items = generateItems(items); 28     } 29  30     /** 31      * 实体基类,包含itemType,以便区分不同的布局,子类需要的其他数据可自行指定 32      */ 33     class TypeItem { 34         int itemType; 35  36         public TypeItem(int itemType) { 37             this.itemType = itemType; 38         } 39     } 40  41     /** 42      * 音乐列表实体,指定itemType为VIEW_TYPE_ITEM 包含MediaItem实体 43      */ 44     class MediaTypeItem extends TypeItem { 45         MediaItem mediaItem; 46  47         public MediaTypeItem(MediaItem mediaItem) { 48             super(VIEW_TYPE_ITEM); 49             this.mediaItem = mediaItem; 50         } 51     } 52  53     /** 54      * 头布局实体,指定itemType为VIEW_TYPE_HEADER 包含分组中头部的字母 55      */ 56     class HeaderTypeItem extends TypeItem { 57         char header; 58  59         public HeaderTypeItem(char header) { 60             super(VIEW_TYPE_HEADER); 61             this.header = header; 62         } 63     } 64  65     /** 66      * 根据传入的mediaItem list,构造带有header的TypeItem 67      *  68      * @param mediaItems 69      *            音乐列表实体 70      * @return 包含itemType的实体 71      */ 72     private List<TypeItem> generateItems(List<MediaItem> mediaItems) { 73         List<TypeItem> items = new ArrayList<TypeItem>(); 74         int size = mediaItems == null ? 0 : mediaItems.size(); 75         char currIndex; 76         char preIndex = ‘{‘; 77         for (int i = 0; i < size; i++) { 78             currIndex = mediaItems.get(i).getSortKey().charAt(0); 79             // 是第一个item或者两个数据的拼音首字母不相等则插入头部 80             if (i == 0 || currIndex != preIndex) { 81                 items.add(new HeaderTypeItem(currIndex)); 82             } 83             items.add(new MediaTypeItem(mediaItems.get(i))); 84             preIndex = currIndex; 85         } 86         return items; 87     } 88  89     /** 90      * ViewHolder基类,itemView用于查找子view 91      */ 92     class ViewHolder { 93         View itemView; 94  95         public ViewHolder(View itemView) { 96             if (itemView == null) { 97                 throw new IllegalArgumentException("itemView can not be null!"); 98             } 99             this.itemView = itemView;100         }101     }102 103     /**104      * 音乐列表ViewHolder105      */106     class MediaViewHolder extends ViewHolder {107         TextView songName;108         TextView singerName;109 110         public MediaViewHolder(View view) {111             super(view);112             songName = (TextView) view.findViewById(R.id.song_name);113             singerName = (TextView) view.findViewById(R.id.singer_name);114         }115     }116 117     /**118      * 头部ViewHolder119      */120     class HeaderViewHolder extends ViewHolder {121         TextView header;122 123         public HeaderViewHolder(View view) {124             super(view);125             header = (TextView) view.findViewById(R.id.header);126         }127     }128 129     @Override130     public View getView(int postion, View convertView, ViewGroup parent) {131         TypeItem item = items.get(postion);132         ViewHolder viewHolder;133         if (convertView == null) {134             // 根据不同的viewType,初始化不同的布局135             switch (getItemViewType(postion)) {136             case VIEW_TYPE_HEADER:137                 viewHolder = new HeaderViewHolder(mInflater.inflate(R.layout.media_header_item, null));138                 break;139             case VIEW_TYPE_ITEM:140                 viewHolder = new MediaViewHolder(mInflater.inflate(R.layout.media_item, null));141                 break;142 143             default:144                 throw new IllegalArgumentException("invalid view type : " + getItemViewType(postion));145             }146 147             // 缓存header与item视图148             convertView = viewHolder.itemView;149             convertView.setTag(viewHolder);150         } else {151             viewHolder = (ViewHolder) convertView.getTag();152         }153 154         // 根据初始化的不同布局,绑定数据155         if (viewHolder instanceof HeaderViewHolder) {156             ((HeaderViewHolder) viewHolder).header.setText(String.valueOf(((HeaderTypeItem) item).header));157         } else if (viewHolder instanceof MediaViewHolder) {158             onBindMediaItem((MediaViewHolder) viewHolder, ((MediaTypeItem) item).mediaItem);159         }160         return convertView;161     }162     163     private void onBindMediaItem(MediaViewHolder viewHolder, MediaItem mediaItem) {164         viewHolder.songName.setText(mediaItem.getSongName());165         viewHolder.singerName.setText(mediaItem.getSingerName());166     }167 168     @Override169     public int getItemViewType(int position) {170         if (items != null) {171             return items.get(position).itemType;172         }173         return super.getItemViewType(position);174     }175 176     @Override177     public int getViewTypeCount() {178         return VIEW_TYPE_COUNT;179     }180 181     @Override182     public int getCount() {183         return items != null ? items.size() : 0;184     }185 186     @Override187     public Object getItem(int position) {188         if (items != null && position > 0 && position < items.size()) {189             return items.get(position);190         }191         return null;192     }193 194     @Override195     public long getItemId(int postion) {196         return postion;197     }198 }

至此,一个带有分栏的音乐列表制作完成。

存在的问题:

查询音乐列表是使用的排序方式为DEFAULT_SORT_ORDER,歌曲名为英文或特殊字符的歌曲会排在中文歌曲之后,而以上的分栏依赖歌曲的

排序,所以会出现先对中文分组,再对英文分组的情况。我的想法是可以通过建立一张数据库表(包含使用pinYin4j生成的sortKey字段),将歌曲

信息读入,然后查询时根据sortKey排序,这样中文和英文就能正确分组了(有时间再去实现一下^_^)。

ListView分栏--制作分栏音乐列表