編輯:關於android開發
之前我遇到過這樣的需求,要求在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排序,這樣中文和英文就能正確分組了(有時間再去實現一下^_^)。
Android自定義標題TitleView,androidtitleview
Android自定義標題TitleView,androidtitleview Android開發過程中,經常遇到一個項目需要重復的定義相同樣式的標題欄,And
Android 6.0: 動態權限管理的解決方案
Android 6.0: 動態權限管理的解決方案 Android 6.0版本(Api 23)推出了很多新的特性, 大幅提升了用戶體驗, 同時也為程序員帶來新的負擔. 動態
簡單登錄案例(SharedPreferences存儲賬戶信息)&聯網請求圖片並下載到SD卡(文件外部存儲),sharedpreferences
簡單登錄案例(SharedPreferences存儲賬戶信息)&聯網請求圖片並下載到SD卡(文件外部存儲),sharedpreferences 新人剛學習And
安卓菜單的實現,各種添加菜單的方法。,安卓菜單
安卓菜單的實現,各種添加菜單的方法。,安卓菜單(一)選項菜單 1、簡單的創建菜單: 1 @Override 2 public boolean onCrea