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