• 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     @Override
    12     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毫秒内加载完成,则不显示ProgressBar
    30         mHander.sendEmptyMessageDelayed(MSG_SHOW_PROGRESS_BAR, 500);
    31         // 使用AsyncTask查询音乐列表,并构造实体列表MediaItem
    32         new AsyncTask<Void, Void, List<MediaItem>>() {
    33 
    34             @Override
    35             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, // ID
    39                         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             @Override
    65             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     // 用于显示ProgressBar
    79     Handler mHander = new Handler(new Callback() {
    80         @Override
    81         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      * 音乐列表ViewHolder
    105      */
    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      * 头部ViewHolder
    119      */
    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     @Override
    130     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     @Override
    169     public int getItemViewType(int position) {
    170         if (items != null) {
    171             return items.get(position).itemType;
    172         }
    173         return super.getItemViewType(position);
    174     }
    175 
    176     @Override
    177     public int getViewTypeCount() {
    178         return VIEW_TYPE_COUNT;
    179     }
    180 
    181     @Override
    182     public int getCount() {
    183         return items != null ? items.size() : 0;
    184     }
    185 
    186     @Override
    187     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     @Override
    195     public long getItemId(int postion) {
    196         return postion;
    197     }
    198 }

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

    存在的问题:

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

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

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

  • 相关阅读:
    Git 简易手册
    【Swift】UILabel 设置内边距
    【iOS】屏幕适配之NSLayoutConstraint
    【Swift】TTTAttributedLabel使用小记
    【iOS】Alamofire库在iOS7下设置Head无效的问题
    【iOS】在Swift中使用JSONModel
    【读书笔记】100个Switf必备tips
    【读书笔记】长尾理论
    【读书笔记】结网
    嗯,农民伯伯的2014就是这些
  • 原文地址:https://www.cnblogs.com/virtual-young/p/4124773.html
Copyright © 2020-2023  润新知