Android音乐播放器开发(6)—ListView组件创建歌曲播放列表(内含原理分析)
1. 說明
源碼已同步到Gitee倉庫,GitHub倉庫,覺得還不錯的話幫忙點(diǎn)個“star”吧,非常感謝。
以往的文章
-
服務(wù)端:Android音樂播放器開發(fā)–服務(wù)端
-
登錄:Android音樂播放器開發(fā)–登錄
-
注冊:Android音樂播放器開發(fā)–注冊
-
修改密碼:Android音樂播放器開發(fā)–修改密碼
-
播放:Android音樂播放器開發(fā)–播放
(適用于平時做個小課設(shè)的小伙伴們)
本部分內(nèi)容實(shí)現(xiàn)效果如下:
2. 界面設(shè)計
界面主體是一個ListView,它以列表的形式展示內(nèi)容,當(dāng)數(shù)據(jù)量足夠多時會出現(xiàn)滾動條,并且能夠根據(jù)數(shù)據(jù)量自適應(yīng)屏幕;而ListView的元素都是歌曲對應(yīng)的基本信息。
標(biāo)題欄仿照登錄時介紹的main_title_bar.xml新建了一個表頭文件,命名為title_bar.xml
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="56dp"android:background="@color/colorPrimary"><ImageButtonandroid:id="@+id/ib_title_back"android:src="@drawable/go_back_selector"android:background="@null"android:layout_marginLeft="10dp"android:layout_width="50dp"android:layout_height="50dp"android:layout_alignParentLeft="true"android:layout_centerVertical="true"/><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="播放列表"android:layout_centerVertical="true"android:layout_centerHorizontal="true"android:textColor="#fff"android:textSize="20sp"android:id="@+id/tv_title"/> </RelativeLayout>這一部分只加了一個ListView組件(activity_music_list.xml)
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"tools:context="cn.sjcup.musicplayer.activity.MusicListActivity"><include layout="@layout/title_bar"></include><FrameLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"><ListViewandroid:layout_width="match_parent"android:layout_height="match_parent"android:id="@+id/lv_music" /></FrameLayout></LinearLayout>這部分是ListView顯示的元素布局。(activity_item.xml)
Item顯示了歌曲的基本信息,包括歌曲封面、歌名和演唱信息,另外加了一個標(biāo)記,用于標(biāo)識當(dāng)前所播的歌曲。
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="65dp"><com.loopj.android.image.SmartImageViewandroid:id="@+id/siv_img"android:layout_width="80dp"android:layout_height="60dp"android:layout_alignParentLeft="true"android:layout_marginBottom="5dp"android:layout_marginLeft="5dp"android:layout_marginTop="5dp"android:scaleType="centerCrop"android:src="@mipmap/ic_launcher"></com.loopj.android.image.SmartImageView><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:id="@+id/tv_name"android:layout_marginLeft="5dp"android:layout_marginTop="10dp"android:layout_toRightOf="@id/siv_img"android:ellipsize="end"android:maxLength="20"android:singleLine="true"android:text="歌曲"android:textColor="#000000"android:textSize="18sp"/><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:id="@+id/tv_author"android:layout_below="@id/tv_name"android:layout_marginLeft="5dp"android:layout_marginTop="5dp"android:layout_toRightOf="@id/siv_img"android:ellipsize="end"android:maxLength="16"android:singleLine="true"android:text="作者"android:textColor="#99000000"android:textSize="14sp"/><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:id="@+id/tv_type"android:layout_alignParentBottom="true"android:layout_alignParentRight="true"android:layout_marginBottom="5dp"android:layout_marginRight="10dp"android:text="播放中"android:textColor="#99000000"android:textSize="12sp"/></RelativeLayout>3. 功能設(shè)計
3.1定義變量
//控件 private ImageButton mBack; private ListView mMusicList; private TextView mState;//獲取到的數(shù)據(jù) private JSONArray musicList; private JSONObject musicInfo;//獲取工具類實(shí)例化對象(這個工具類很重要,在播放界面一文的第五部分提到) private MusicPlayUtil musicPlayUtil = MusicPlayUtil.getInstance();3.2 初始化界面
在初始化時,分為了界面初始化和事件初始化
@Override protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_music_list);//初始化界面initView();//設(shè)置相關(guān)事件initEvent(); }初始化界面,綁定界面控件,將歌曲信息填充到ListView中
//初始化界面 private void initView() {mBack = findViewById(R.id.ib_title_back);mMusicList = findViewById(R.id.lv_music);fillData(); //填充數(shù)據(jù) }數(shù)據(jù)填充使用setAdapter方法,這是ListView本身自帶的方法,通過名稱可以看出,Adapter意為適配器,我們將適配器填充到ListView中。參數(shù)是實(shí)例化后的MusicAdapter對象。MusicAdapter對象繼承了BaseAdapter,并重寫了四個方法。
四個方法分別為:
- int getCount() 填充的item個數(shù)
- Object getItem(int position) 指定索引對應(yīng)的item數(shù)據(jù)項
- long getItemId(int position) 指定索引對應(yīng)item的id值
- View getView(final int position, View convertView, ViewGroup parent) 填充每個item的可視內(nèi)容并返回
我們可以簡單的理解為,適配器先從getCount里確定填充的數(shù)量,然后循環(huán)執(zhí)行getView方法將條目一個一個繪制出來,所以必須重寫的是getCount和getView方法。而getItem和getItemId是調(diào)用某些函數(shù)才會觸發(fā)的方法,如果不需要使用時可以暫時不修改。
具體的介紹可以參考—>博客
下面是填充數(shù)據(jù)的這一部分整體的程序,稍后會拆開介紹。
//填充數(shù)據(jù) private void fillData() {mMusicList.setAdapter(new MusicAdapter()); }private class MusicAdapter extends BaseAdapter {@Overridepublic int getCount() {return musicPlayUtil.getMusicNum();}@Overridepublic View getView(int position, View convertView, ViewGroup parent) {ViewHolder holder; //ViewHolder是定義的局部定義類,作為信息傳遞的載體musicList = musicPlayUtil.getMusicList();try{musicInfo = musicList.getJSONObject(position);}catch (Exception e){e.printStackTrace();}if (convertView == null){convertView = LayoutInflater.from(getApplicationContext()).inflate(R.layout.activity_item,parent,false);holder = new ViewHolder();holder.siv = convertView.findViewById(R.id.siv_img);holder.tv_name = convertView.findViewById(R.id.tv_name);holder.tv_author = convertView.findViewById(R.id.tv_author);holder.tv_type = convertView.findViewById(R.id.tv_type);convertView.setTag(holder);}else {holder = (ViewHolder) convertView.getTag();}holder.siv.setImageUrl(IMG+musicInfo.optString("img"), R.mipmap.ic_launcher,R.mipmap.ic_launcher);holder.tv_name.setText(musicInfo.optString("name"));holder.tv_author.setText(musicInfo.optString("author"));holder.tv_type.setText("");if (musicPlayUtil.getMusicId()==position){holder.tv_type.setText("播放中");holder.tv_type.setTextColor(Color.RED);mState=holder.tv_type;}return convertView;}@Overridepublic Object getItem(int position) {return null;}@Overridepublic long getItemId(int position) {return 0;}class ViewHolder{TextView tv_name;TextView tv_author;TextView tv_type;SmartImageView siv;} }分別講解一下。
因為需要頻繁的傳遞信息,這里定義了一個局部內(nèi)部類,分別對應(yīng)了item布局的4個控件(拓展一下:內(nèi)部類分為靜態(tài)內(nèi)部類、成員內(nèi)部類、局部內(nèi)部類、匿名內(nèi)部類。感興趣的小伙伴可以去復(fù)習(xí)一下)
class ViewHolder{TextView tv_name;TextView tv_author;TextView tv_type;SmartImageView siv; }getCount方法,返回的是填充的item的數(shù)量,在本項目中,就是歌曲的數(shù)量。歌曲數(shù)量已經(jīng)在MainActivity中獲取,這里可以通過工具類(重申一遍,這個工具類在播放界面這篇文章的第五部分有講到)直接獲取到這個值。
@Override public int getCount() {return musicPlayUtil.getMusicNum(); }getView方法,返回的是填充的view信息,這一部分是本文最難理解的。參數(shù)position相當(dāng)于一個標(biāo)識,用來表示進(jìn)行操作的是哪一個item,從0開始計數(shù);convertView就是待返回的view信息,初始化時為null,我們要加工的也就是這個對象。
首先,定義了一個類holder,使用時需要實(shí)例化一個ViewHolder(上面定義的局部內(nèi)部類)作為信息傳遞的載體,也就是填充得item數(shù)量決定了ViewHolder的實(shí)例化對象的數(shù)量。所有歌曲信息可以通過上面定義的工具類獲取。然后通過position獲取到對應(yīng)的歌曲信息(JSONArray里的object也是從0開始計數(shù)的,我們可以認(rèn)為是一一對應(yīng)的)。
下面我們做個測試,檢測一下什么時候調(diào)用getView方法。
------------------------------------------------------------Test---------------------------------------------------------
我們在getView方法中輸出position,也就是每次調(diào)用getView方法時都會輸出當(dāng)前的position。
這時我們打開音樂列表,可以看到顯示了11首歌曲
輸出顯示調(diào)用了11次getView方法,position對應(yīng)了0–>10
然后向上滑,加載其它的歌曲,上滑的同時,其它內(nèi)容通過getView方法被加載,position對應(yīng)11->16
現(xiàn)在我們想一個問題,此時下滑,也就是希望顯示之前的歌曲信息,getView會被重新調(diào)用嗎?(1. 第一種想法當(dāng)然是認(rèn)為之前的歌曲信息已經(jīng)加載出來了,當(dāng)然不會再調(diào)用了;2. 第二種想法就是會調(diào)用吧)
事實(shí)是加載之前的信息會重新調(diào)用getView
經(jīng)過簡單的測試,我們可以得出一個小結(jié)論,界面中可以顯示出多少個item,就會調(diào)用多少次getView方法加載信息,也就是哪個item進(jìn)入可是范圍,就會調(diào)用getView方法信息。前面介紹道,理論上有多少條信息就應(yīng)該有對應(yīng)相應(yīng)數(shù)量的item,但是考慮到手機(jī)的內(nèi)存是有限的,如果數(shù)量過多,占用內(nèi)存就會過大,實(shí)際上的操作只會初始化特定數(shù)量的item(依屏幕長度而定),在滑動過程中,item重復(fù)使用,使用getView方法不斷賦予新的信息。
------------------------------------------------------------ENDTest---------------------------------------------------------
關(guān)于setTag和getTag的使用。在setTag之前,convertView需要找到xml中定義的layout,這里是activity_item,相當(dāng)于綁定了一個目標(biāo)item,然后使用findViewById綁定item中的組件,再對每個組件賦予不同的值。前面介紹道,上滑又下滑后,前面加載的信息會再次使用getView方法“繪制”一遍,但已經(jīng)綁定的組件沒有必要再綁定一次,因為這個過程比較消耗資源,因此在一次使用findViewById綁定后,調(diào)用setTag保存起來,再次加載時,同于同樣的信息,直接使用getTag獲取到已綁定組件的ViewHolder,而無需重復(fù)綁定組件。
if (convertView == null){convertView = LayoutInflater.from(getApplicationContext()).inflate(R.layout.activity_item,parent,false);holder = new ViewHolder();holder.siv = convertView.findViewById(R.id.siv_img);holder.tv_name = convertView.findViewById(R.id.tv_name);holder.tv_author = convertView.findViewById(R.id.tv_author);holder.tv_type = convertView.findViewById(R.id.tv_type);convertView.setTag(holder); }else {holder = (ViewHolder) convertView.getTag(); }對于初學(xué)者來說,這個比較難理解,建議多看幾篇文章。以上很多內(nèi)容都是本人自己的理解,如果有不對的地方敬請指出。
@Override public View getView(int position, View convertView, ViewGroup parent) {ViewHolder holder; //ViewHolder是定義的局部內(nèi)部類,作為信息傳遞的載體musicList = musicPlayUtil.getMusicList();try{musicInfo = musicList.getJSONObject(position);}catch (Exception e){e.printStackTrace();}if (convertView == null){convertView = LayoutInflater.from(getApplicationContext()).inflate(R.layout.activity_item,parent,false);holder = new ViewHolder();holder.siv = convertView.findViewById(R.id.siv_img);holder.tv_name = convertView.findViewById(R.id.tv_name);holder.tv_author = convertView.findViewById(R.id.tv_author);holder.tv_type = convertView.findViewById(R.id.tv_type);convertView.setTag(holder);}else {holder = (ViewHolder) convertView.getTag();}holder.siv.setImageUrl(IMG+musicInfo.optString("img"), R.mipmap.ic_launcher,R.mipmap.ic_launcher);holder.tv_name.setText(musicInfo.optString("name"));holder.tv_author.setText(musicInfo.optString("author"));holder.tv_type.setText("");if (musicPlayUtil.getMusicId()==position){holder.tv_type.setText("播放中");holder.tv_type.setTextColor(Color.RED);mState=holder.tv_type;}return convertView; }3.3定義事件
在這個界面,我們希望實(shí)現(xiàn)兩個點(diǎn)擊事件,1. 點(diǎn)擊“返回”按鈕,可以返回播放界面;2. 點(diǎn)擊一個歌曲信息,可以播放對應(yīng)的歌曲。
//初始化事件 private void initEvent() {mMusicList.setOnItemClickListener(new AdapterView.OnItemClickListener() {@Overridepublic void onItemClick(AdapterView<?> parent, View view, int position, long id) {if (mState == null) {mState = view.findViewById(R.id.tv_type);mState.setText("播放中");mState.setTextColor(Color.RED);musicPlayUtil.setMusicId(position);musicPlayUtil.setMusicView(MainActivity.IsPlay.play);finish();} else {mState.setText("");mState = view.findViewById(R.id.tv_type);mState.setText("播放中");mState.setTextColor(Color.RED);musicPlayUtil.setMusicId(position);musicPlayUtil.setMusicView(MainActivity.IsPlay.play);finish();}}});mBack.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {MusicListActivity.this.finish();}}); }第一個事件,返回播放界面比較簡單,這里不再贅述。
關(guān)于第二個事件,定義的變量mState表示的是歌曲的播放狀態(tài),如果在播放狀態(tài),就顯示“播放中”(如下圖),否則不顯示內(nèi)容。
第一次點(diǎn)擊時,mState是null,并沒有被賦值,view就是被點(diǎn)擊的item,第一步就是綁定被點(diǎn)擊的item的組件,更改它的內(nèi)容,響應(yīng)點(diǎn)擊事件。然后調(diào)用工具類中的方法修改歌曲id,這里的position和前面介紹的可以認(rèn)為是一樣的,由于position從0開始,和我定義的歌曲id是一樣的,這里直接把它當(dāng)作歌曲id了,如果不一致,這里需要再加一些處理,找到對應(yīng)的歌曲id,再調(diào)用setMusicView播放對應(yīng)的歌曲。至于finish方法,是做出點(diǎn)擊事件后關(guān)閉播放列表界面,返回播放界面,如果你認(rèn)為沒有必要可以不加。
mState如果不是null,那么之前是已經(jīng)綁定過別的item了,修改之前綁定的播放狀態(tài)為空字符串,表示沒有播放,再重新綁定新的item,后面內(nèi)容就與上面相似了,不再贅述。
4. 測試
測試成功,放一個測試的動圖
總結(jié)
以上是生活随笔為你收集整理的Android音乐播放器开发(6)—ListView组件创建歌曲播放列表(内含原理分析)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Chrome 自动化交互利器:用 tam
- 下一篇: python爬虫网站接口的使用——将网页