最简单的音乐播放器,实现播放器基本功能
首先,这个播放器不是我想的,我是根据这个作者的博客教程来写的咯。。。也算是自己想啦,附上链接 http://blog.csdn.net/wwj_748/article/details/8899699
第一篇写的这些内容很简单,跟链接作者写的几乎一样。
他写的比较复杂,有些实现方式也不一样,所以自己写个博客记录一下...O(∩_∩)O~
这个播放器很简单,如果你是什么Api都不知道,看了这个两天就写完了,你可以边看边查相关的API是干嘛用的,如果之前都了解了相关的API,那么一天就能编完。
做一个播放器的大致思路是什么呢?播放器当然是要有播放的功能咯,这是我在废话。这个播放器是不带下载功能的,所以肯定是从你手机中获取已经存在的音乐。于是第一步就是获取手机中的音乐信息,这个跟播放器的界面没什么联系,那你可以把它写成一个工具类,叫做GetMusicListUtil,专门用来获取手机音乐信息的类。 上代码:
1 public class GetMusicListUtil { 2 3 /** 4 * 从手机中得到所有的音乐,放在list中保存 5 * 6 * @param context 7 * @return 8 * @author Yang 9 */ 10 public static List<Mp3Info> getMusicInfos(Context context) 11 { 12 Cursor cursor = context.getContentResolver().query( 13 MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, null, null, null, MediaStore.Audio.Media.DEFAULT_SORT_ORDER); 14 List<Mp3Info> musicInfos = new ArrayList<Mp3Info>(); 15 for(int i=0; i<cursor.getCount(); i++) 16 { 17 cursor.moveToNext(); 18 Mp3Info music = new Mp3Info(); 19 long musicId = cursor.getLong(cursor 20 .getColumnIndex(MediaStore.Audio.Media._ID)); 21 String musicTitle = cursor.getString(cursor 22 .getColumnIndex(MediaStore.Audio.Media.TITLE)); 23 String musicArtist = cursor.getString(cursor 24 .getColumnIndex(MediaStore.Audio.Media.ARTIST)); 25 long musicTime = cursor.getLong(cursor 26 .getColumnIndex(MediaStore.Audio.Media.DURATION)); //时长 27 long musicSize = cursor.getLong(cursor 28 .getColumnIndex(MediaStore.Audio.Media.SIZE)); //文件大小 29 String musicUrl = cursor.getString(cursor 30 .getColumnIndex(MediaStore.Audio.Media.DATA)); //文件路径 31 int isMusic = cursor.getInt(cursor 32 .getColumnIndex(MediaStore.Audio.Media.IS_MUSIC)); 33 //只把音乐添加到集合中 34 if(isMusic != 0) 35 { 36 music.setId(musicId); 37 music.setArtist(musicArtist); 38 music.setDuration(musicTime); 39 music.setSize(musicSize); 40 music.setUrl(musicUrl); 41 music.setTitle(musicTitle); 42 musicInfos.add(music); 43 } 44 45 } 46 return musicInfos; 47 } 48 /** 49 * 将因音乐的时长由毫秒转换成分:秒的格式 50 * 51 * @param time 52 * @return 53 * @author Yang 54 */ 55 public static String formatTime(long time) { 56 String min = time / (1000 * 60) + ""; 57 String sec = time % (1000 * 60) + ""; 58 if (min.length() < 2) { 59 min = "0" + time / (1000 * 60) + ""; 60 } else { 61 min = time / (1000 * 60) + ""; 62 } 63 if (sec.length() == 4) { 64 sec = "0" + (time % (1000 * 60)) + ""; 65 } else if (sec.length() == 3) { 66 sec = "00" + (time % (1000 * 60)) + ""; 67 } else if (sec.length() == 2) { 68 sec = "000" + (time % (1000 * 60)) + ""; 69 } else if (sec.length() == 1) { 70 sec = "0000" + (time % (1000 * 60)) + ""; 71 } 72 return min + ":" + sec.trim().substring(0, 2); 73 } 74 75 /** 76 * 将List<Mp3Info>转换成List<HashMap<String,String>>的格式 77 * 为了给SimpleAdapter填充数据 78 * 79 * @param musicInfos 80 * @return 81 * @author Yang 82 * 83 */ 84 public static List<HashMap<String,String>> getMusicHashMaps( 85 List<Mp3Info> musicInfos) 86 { 87 List<HashMap<String,String>> musicHashMaps = 88 new ArrayList<HashMap<String,String>>(); 89 for(Iterator<Mp3Info> iterator = musicInfos.iterator();iterator.hasNext();) 90 { 91 Mp3Info music = iterator.next(); 92 HashMap<String,String> map = new HashMap<String,String>(); 93 map.put("title", music.getTitle()); 94 map.put("artist", music.getArtist()); 95 map.put("size", String.valueOf(music.getSize())); 96 map.put("time", formatTime(music.getDuration())); 97 map.put("url", music.getUrl()); 98 musicHashMaps.add(map); 99 } 100 return musicHashMaps; 101 } 102 }
相信大家都能看懂,Cursor对象进行查询,然后存到一个Mp3Info类型的List中,之所以下面还写了一个List<HashMap<String,String>>进行保存,就是为了给SimpleAdapter用的,这个adapter给listView填充数据。
这个和原作者写的没什么区别,另外其中的Mp3Info是一个javabean,如下所示:
1 package org.com.ViPlayer; 2 3 public class Mp3Info 4 { 5 public long id; 6 public String title; 7 public String artist; 8 public long duration; 9 public long size; 10 public String url; 11 public long getId() { 12 return id; 13 } 14 public void setId(long id) { 15 this.id = id; 16 } 17 public String getTitle() { 18 return title; 19 } 20 public void setTitle(String title) { 21 this.title = title; 22 } 23 public String getArtist() { 24 return artist; 25 } 26 public void setArtist(String artist) { 27 this.artist = artist; 28 } 29 public long getDuration() { 30 return duration; 31 } 32 public void setDuration(long duration) { 33 this.duration = duration; 34 } 35 public long getSize() { 36 return size; 37 } 38 public void setSize(long size) { 39 this.size = size; 40 } 41 public String getUrl() { 42 return url; 43 } 44 public void setUrl(String url) { 45 this.url = url; 46 } 47 48 }
有了音乐的信息之后,我们可以开始编辑界面了,我的界面里面的那些个图片也是在原创作者页面下载的。但是里面的模式稍微有些不一样,被我给简化了。。。
一共只用到了这么多图片:背景,播放按钮图,下一首按钮图,前一首按钮图,音乐模式按钮图,音乐小图标。
然后就是主界面xml文件:
1 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 2 xmlns:tools="http://schemas.android.com/tools" 3 android:layout_width="match_parent" 4 android:layout_height="match_parent" 5 android:background="@drawable/bk1" 6 android:paddingBottom="@dimen/activity_vertical_margin" 7 android:paddingLeft="@dimen/activity_horizontal_margin" 8 android:paddingRight="@dimen/activity_horizontal_margin" 9 android:paddingTop="@dimen/activity_vertical_margin" 10 tools:context=".MainActivity" > 11 12 13 <LinearLayout 14 android:id="@+id/handleButton" 15 android:layout_width="fill_parent" 16 android:layout_height="wrap_content" 17 android:layout_alignParentTop="true" 18 android:layout_centerHorizontal="true" > 19 20 <Button 21 android:id="@+id/repeat" 22 android:layout_width="wrap_content" 23 android:layout_height="wrap_content" 24 android:layout_weight="1" 25 android:layout_alignParentLeft="true" 26 android:background="@drawable/repeat_none" /> 27 <Button 28 android:id="@+id/previous" 29 android:layout_width="wrap_content" 30 android:layout_height="wrap_content" 31 android:layout_weight="1" 32 android:layout_toRightOf="@+id/repeat" 33 android:background="@drawable/previous" /> 34 35 <Button 36 android:id="@+id/playMusic" 37 android:layout_width="wrap_content" 38 android:layout_height="wrap_content" 39 android:layout_weight="1" 40 android:layout_toRightOf="@+id/previous" 41 android:background="@drawable/play" /> 42 43 <Button 44 android:id="@+id/next" 45 android:layout_width="wrap_content" 46 android:layout_height="wrap_content" 47 android:layout_weight="1" 48 android:layout_toRightOf="@+id/playMusic" 49 android:background="@drawable/next" /> 50 51 <!-- <Button 52 android:id="@+id/randomMode" 53 android:layout_width="wrap_content" 54 android:layout_height="wrap_content" 55 android:layout_weight="1" 56 android:layout_toRightOf="@+id/next" 57 android:background="@drawable/random1" /> --> 58 </LinearLayout> 59 60 <ListView 61 android:id="@+id/musicList" 62 android:layout_width="fill_parent" 63 android:layout_height="300dp" 64 android:layout_below="@+id/handleButton" > 65 </ListView> 66 67 <RelativeLayout 68 android:id="@+id/songInfo" 69 android:layout_width="match_parent" 70 android:layout_height="wrap_content" 71 android:layout_below="@+id/musicList" 72 android:layout_alignParentBottom="true" 73 > 74 75 <ImageView 76 android:id="@+id/musicAlbum" 77 android:layout_width="wrap_content" 78 android:layout_height="wrap_content" 79 android:layout_alignParentBottom="true" 80 android:layout_alignParentLeft="true" 81 android:src="@drawable/viplayer"/> 82 83 <RelativeLayout 84 android:id="@+id/musicInfo" 85 android:layout_width="match_parent" 86 android:layout_height="match_parent" 87 android:layout_alignTop="@+id/musicAlbum" 88 android:layout_toRightOf="@+id/musicAlbum" > 89 90 <TextView 91 android:id="@+id/titleInfo" 92 android:layout_width="wrap_content" 93 android:layout_height="wrap_content" 94 android:layout_alignParentLeft="true" 95 android:text="" 96 android:textSize="20sp" 97 android:textColor="@color/white" 98 /> 99 <TextView 100 android:id="@+id/artist" 101 android:layout_width="wrap_content" 102 android:layout_height="wrap_content" 103 android:layout_alignParentLeft="true" 104 android:layout_below="@+id/titleInfo" 105 android:text="" 106 android:textSize="18sp" 107 android:textColor="@color/white" 108 /> 109 </RelativeLayout> 110 <!-- <Button 111 android:layout_width="wrap_content" 112 android:layout_height="wrap_content" 113 android:layout_alignParentBottom="true" 114 android:layout_alignParentRight="true" 115 android:background="@drawable/category" 116 /> --> 117 </RelativeLayout> 118 </RelativeLayout>
效果是这样子的:
1 <?xml version="1.0" encoding="utf-8"?> 2 <resources> 3 <color name="white">#FFFFFF</color><!--白色 --> 4 <color name="ivory">#FFFFF0</color><!--象牙色 --> 5 <color name="lightyellow">#FFFFE0</color><!--亮黄色--> 6 <color name="yellow">#FFFF00</color><!--黄色 --> 7 <color name="snow">#FFFAFA</color><!--雪白色 --> 8 <color name="floralwhite">#FFFAF0</color><!--花白色 --> 9 <color name="lemonchiffon">#FFFACD</color><!--柠檬绸色 --> 10 <color name="cornsilk">#FFF8DC</color><!--米绸色 --> 11 <color name="seashell">#FFF5EE</color><!--海贝色 --> 12 <color name="lavenderblush">#FFF0F5</color><!--淡紫红 --> 13 <color name="papayawhip">#FFEFD5</color><!--番木色 --> 14 <color name="blanchedalmond">#FFEBCD</color><!--白杏色 --> 15 <color name="mistyrose">#FFE4E1</color><!--浅玫瑰色 --> 16 <color name="bisque">#FFE4C4</color><!--桔黄色 --> 17 <color name="moccasin">#FFE4B5</color><!--鹿皮色 --> 18 <color name="navajowhite">#FFDEAD</color><!--纳瓦白 --> 19 <color name="peachpuff">#FFDAB9</color><!--桃色 --> 20 <color name="gold">#FFD700</color><!--金色 --> 21 <color name="pink">#FFC0CB</color><!--粉红色 --> 22 <color name="lightpink">#FFB6C1</color><!--亮粉红色--> 23 <color name="orange">#FFA500</color><!--橙色 --> 24 <color name="lightsalmon">#FFA07A</color><!--亮肉色 --> 25 <color name="darkorange">#FF8C00</color><!--暗桔黄色 --> 26 <color name="coral">#FF7F50</color><!--珊瑚色 --> 27 <color name="hotpink">#FF69B4</color><!--热粉红色 --> 28 <color name="tomato">#FF6347</color><!--西红柿色 --> 29 <color name="orangered">#FF4500</color><!--红橙色 --> 30 <color name="deeppink">#FF1493</color><!--深粉红色 --> 31 <color name="fuchsia">#FF00FF</color><!--紫红色 --> 32 <color name="magenta">#FF00FF</color><!--红紫色 --> 33 <color name="red">#FF0000</color><!--红色 --> 34 <color name="oldlace">#FDF5E6</color><!--老花色 --> 35 <color name="lightgoldenrodyellow">#FAFAD2</color><!--亮金黄色 --> 36 <color name="linen">#FAF0E6</color><!--亚麻色 --> 37 <color name="antiquewhite">#FAEBD7</color><!--古董白 --> 38 <color name="salmon">#FA8072</color><!--鲜肉色 --> 39 <color name="ghostwhite">#F8F8FF</color><!--幽灵白 --> 40 <color name="mintcream">#F5FFFA</color><!--薄荷色 --> 41 <color name="whitesmoke">#F5F5F5</color><!--烟白色 --> 42 <color name="beige">#F5F5DC</color><!--米色 --> 43 <color name="wheat">#F5DEB3</color><!--浅黄色 --> 44 <color name="sandybrown">#F4A460</color><!--沙褐色--> 45 <color name="azure">#F0FFFF</color><!--天蓝色 --> 46 <color name="honeydew">#F0FFF0</color><!--蜜色 --> 47 <color name="aliceblue">#F0F8FF</color><!--艾利斯兰 --> 48 <color name="khaki">#F0E68C</color><!--黄褐色 --> 49 <color name="lightcoral">#F08080</color><!--亮珊瑚色 --> 50 <color name="palegoldenrod">#EEE8AA</color><!--苍麒麟色 --> 51 <color name="violet">#EE82EE</color><!--紫罗兰色 --> 52 <color name="darksalmon">#E9967A</color><!--暗肉色 --> 53 <color name="lavender">#E6E6FA</color><!--淡紫色 --> 54 <color name="lightcyan">#E0FFFF</color><!--亮青色 --> 55 <color name="burlywood">#DEB887</color><!--实木色 --> 56 <color name="plum">#DDA0DD</color><!--洋李色 --> 57 <color name="gainsboro">#DCDCDC</color><!--淡灰色 --> 58 <color name="crimson">#DC143C</color><!--暗深红色 --> 59 <color name="palevioletred">#DB7093</color><!--苍紫罗兰色 --> 60 <color name="goldenrod">#DAA520</color><!--金麒麟色 --> 61 <color name="orchid">#DA70D6</color><!--淡紫色 --> 62 <color name="thistle">#D8BFD8</color><!--蓟色 --> 63 <color name="lightgray">#D3D3D3</color><!--亮灰色 --> 64 <color name="lightgrey">#D3D3D3</color><!--亮灰色 --> 65 <color name="tan">#D2B48C</color><!--茶色 --> 66 <color name="chocolate">#D2691E</color><!--巧可力色 --> 67 <color name="peru">#CD853F</color><!--秘鲁色 --> 68 <color name="indianred">#CD5C5C</color><!--印第安红 --> 69 <color name="mediumvioletred">#C71585</color><!--中紫罗兰色 --> 70 <color name="silver">#C0C0C0</color><!--银色 --> 71 <color name="darkkhaki">#BDB76B</color><!--暗黄褐色 --> 72 <color name="rosybrown">#BC8F8F</color> <!--褐玫瑰红 --> 73 <color name="mediumorchid">#BA55D3</color><!--中粉紫色 --> 74 <color name="darkgoldenrod">#B8860B</color><!--暗金黄色 --> 75 <color name="firebrick">#B22222</color><!--火砖色 --> 76 <color name="powderblue">#B0E0E6</color><!--粉蓝色 --> 77 <color name="lightsteelblue">#B0C4DE</color><!--亮钢兰色 --> 78 <color name="paleturquoise">#AFEEEE</color><!--苍宝石绿 --> 79 <color name="greenyellow">#ADFF2F</color><!--黄绿色 --> 80 <color name="lightblue">#ADD8E6</color><!--亮蓝色 --> 81 <color name="darkgray">#A9A9A9</color><!--暗灰色 --> 82 <color name="darkgrey">#A9A9A9</color><!--暗灰色 --> 83 <color name="brown">#A52A2A</color><!--褐色 --> 84 <color name="sienna">#A0522D</color><!--赭色 --> 85 <color name="darkorchid">#9932CC</color><!--暗紫色--> 86 <color name="palegreen">#98FB98</color><!--苍绿色 --> 87 <color name="darkviolet">#9400D3</color><!--暗紫罗兰色 --> 88 <color name="mediumpurple">#9370DB</color><!--中紫色 --> 89 <color name="lightgreen">#90EE90</color><!--亮绿色 --> 90 <color name="darkseagreen">#8FBC8F</color><!--暗海兰色 --> 91 <color name="saddlebrown">#8B4513</color><!--重褐色 --> 92 <color name="darkmagenta">#8B008B</color><!--暗洋红 --> 93 <color name="darkred">#8B0000</color><!--暗红色 --> 94 <color name="blueviolet">#8A2BE2</color><!--紫罗兰蓝色 --> 95 <color name="lightskyblue">#87CEFA</color><!--亮天蓝色 --> 96 <color name="skyblue">#87CEEB</color><!--天蓝色 --> 97 <color name="gray">#808080</color><!--灰色 --> 98 <color name="grey">#808080</color><!--灰色 --> 99 <color name="olive">#808000</color><!--橄榄色 --> 100 <color name="purple">#800080</color><!--紫色 --> 101 <color name="maroon">#800000</color><!--粟色 --> 102 <color name="aquamarine">#7FFFD4</color><!--碧绿色--> 103 <color name="chartreuse">#7FFF00</color><!--黄绿色 --> 104 <color name="lawngreen">#7CFC00</color><!--草绿色 --> 105 <color name="mediumslateblue">#7B68EE</color><!--中暗蓝色 --> 106 <color name="lightslategray">#778899</color><!--亮蓝灰 --> 107 <color name="lightslategrey">#778899</color><!--亮蓝灰 --> 108 <color name="slategray">#708090</color><!--灰石色 --> 109 <color name="slategrey">#708090</color><!--灰石色 --> 110 <color name="olivedrab">#6B8E23</color><!--深绿褐色 --> 111 <color name="slateblue">#6A5ACD</color><!--石蓝色 --> 112 <color name="dimgray">#696969</color><!--暗灰色 --> 113 <color name="dimgrey">#696969</color><!--暗灰色 --> 114 <color name="mediumaquamarine">#66CDAA</color><!--中绿色 --> 115 <color name="cornflowerblue">#6495ED</color><!--菊兰色 --> 116 <color name="cadetblue">#5F9EA0</color><!--军兰色 --> 117 <color name="darkolivegreen">#556B2F</color><!--暗橄榄绿 --> 118 <color name="indigo">#4B0082</color><!--靛青色 --> 119 <color name="mediumturquoise">#48D1CC</color><!--中绿宝石 --> 120 <color name="darkslateblue">#483D8B</color><!--暗灰蓝色 --> 121 <color name="steelblue">#4682B4</color><!--钢兰色 --> 122 <color name="royalblue">#4169E1</color><!--皇家蓝 --> 123 <color name="turquoise">#40E0D0</color><!--青绿色 --> 124 <color name="mediumseagreen">#3CB371</color><!--中海蓝 --> 125 <color name="limegreen">#32CD32</color><!--橙绿色 --> 126 <color name="darkslategray">#2F4F4F</color><!--暗瓦灰色 --> 127 <color name="darkslategrey">#2F4F4F</color><!--暗瓦灰色 --> 128 <color name="seagreen">#2E8B57</color><!--海绿色 --> 129 <color name="forestgreen">#228B22</color><!--森林绿 --> 130 <color name="lightseagreen">#20B2AA</color><!--亮海蓝色 --> 131 <color name="dodgerblue">#1E90FF</color><!--闪兰色 --> 132 <color name="midnightblue">#191970</color><!--中灰兰色 --> 133 <color name="aqua">#00FFFF</color><!--浅绿色 --> 134 <color name="cyan">#00FFFF</color><!--青色 --> 135 <color name="springgreen">#00FF7F</color><!--春绿色--> 136 <color name="lime">#00FF00</color><!--酸橙色 --> 137 <color name="mediumspringgreen">#00FA9A</color><!--中春绿色 --> 138 <color name="darkturquoise">#00CED1</color><!--暗宝石绿 --> 139 <color name="deepskyblue">#00BFFF</color><!--深天蓝色 --> 140 <color name="darkcyan">#008B8B</color><!--暗青色 --> 141 <color name="teal">#008080</color><!--水鸭色 --> 142 <color name="green">#008000</color><!--绿色 --> 143 <color name="darkgreen">#006400</color><!--暗绿色 --> 144 <color name="blue">#0000FF</color><!--蓝色 --> 145 <color name="mediumblue">#0000CD</color><!--中兰色 --> 146 <color name="darkblue">#00008B</color><!--暗蓝色 --> 147 <color name="navy">#000080</color><!--海军色 --> 148 <color name="black">#000000</color><!--黑色 --> 149 150 </resources>
这个播放器是不需要任何额外权限的。所以Manifest中除了Activity的信息不需要别的注册,Activity是自动生成的,所以就不用管咯......真开心...
1 <?xml version="1.0" encoding="utf-8"?> 2 3 <RelativeLayout 4 android:id="@+id/listView" 5 xmlns:android="http://schemas.android.com/apk/res/android" 6 android:layout_width="fill_parent" 7 android:layout_height="wrap_content" 8 android:padding="5dp" 9 > 10 <LinearLayout 11 android:layout_width="fill_parent" 12 android:layout_height="1dp" 13 android:background="#252525" /> 14 <TextView 15 android:id="@+id/title" 16 android:layout_width="fill_parent" 17 android:layout_height="wrap_content" 18 android:textColor="@color/white" 19 android:textSize="20sp" 20 android:text="" 21 /> 22 23 <TextView 24 android:id="@+id/Artist" 25 android:layout_width="fill_parent" 26 android:layout_height="wrap_content" 27 android:layout_below="@+id/title" 28 android:text="" 29 android:textColor="@color/white" 30 android:textSize="18sp" 31 /> 32 <TextView 33 android:id="@+id/duration" 34 android:layout_width="wrap_content" 35 android:layout_height="wrap_content" 36 android:layout_alignParentRight="true" 37 android:layout_below="@+id/Artist" 38 android:text="" 39 android:textSize="18sp" 40 android:textColor="@color/white"/> 41 </RelativeLayout>
好了,目前为止,已经把最简单的工作都做完了,接下来就开始正式着手做功能了。。。
首先要把我们获取到的音乐信息显示到主界面的listView控件中,只要在Activity的onCreate函数中使用Adapter对listView进行数据填充:
1 private SimpleAdapter adapter; 2 3 private List<Mp3Info> musicInfo; 4 private List<HashMap<String,String>> musicList; 5 6 musicInfo = GetMusicListUtil.getMusicInfos(MainActivity.this); 7 musicList = GetMusicListUtil.getMusicHashMaps(musicInfo); 8 adapter = new SimpleAdapter(MainActivity.this, musicList, R.layout.music_list_item, 9 new String[]{"title","artist","time"}, 10 new int[]{R.id.title, R.id.Artist, R.id.duration}); 11 listView.setAdapter(adapter);
经过这样的赋值,就可以把音乐的信息显示在list列表中了。我们这里用到的SimpleAdapter是最简单的一种,通常做项目的时候会自定义一个Adapter,继承自BaseAdapter,可以在其中进行一些操作,用的最多。比如想在listView列表中加入图片信息,建议自定义一个Adapter然后采用异步加载的方式。废话不多说,接着做。
现在我们已经把列表显示出来了,那么下一步就是点击一下列表,然后实现播放的功能,到这里为止,和原作者采用的方法都一样:
给listView添加OnItemClick事件:
listView.setOnItemClickListener(new MusicItemClickListener());
这个MusicItemClickLitener是自定义的一个类:
1 public class MusicItemClickListener implements OnItemClickListener 2 { 3 @Override 4 public void onItemClick(AdapterView<?> parent, View view, int position, 5 long id) { 6 if(musicInfo != null) 7 { 8 play.setBackgroundResource(R.drawable.pause); 9 Intent intent = new Intent(); 10 intent.setAction("org.com.ViPlayer.MUSIC_SERVICE"); 11 intent.putExtra("url", music.getUrl()); 12 intent.putExtra("msg", 0); 13 startService(intent); 14 } 15 } 16 17 }
可以看出来,在item的点击事件中,启动播放音乐的服务,当点击音乐开始播放的时候,将播放按钮变成暂停按钮的图标,其中intent携带的msg这个信息传递的是0,暂定0为播放的意思,这个服务是单独写的一个类,叫做PlayerService,它继承自Service,但是如何知道上面Activity启动的是哪一个service呢,那就是intent.setAction("这个service的名字")这句话起的作用,我们能根据这个名字来调用service,就必须在AndroidManifest中注册这个service:
1 <service android:name="org.com.ViPlayer.PlayerService"> 2 <intent-filter> 3 <action android:name="org.com.ViPlayer.MUSIC_SERVICE"/> 4 </intent-filter> 5 </service>
把这个service服务放在application中就行,service中的android:name=“service在包中的位置”,下面的action中的android:name才是真正引导service行动的指标。
接下来我们实现这个PlayerService类:
1 package org.com.ViPlayer; 2 3 import java.util.List; 4 5 import android.app.Service; 6 import android.content.Context; 7 import android.content.Intent; 8 import android.media.MediaPlayer; 9 import android.media.MediaPlayer.OnPreparedListener; 10 import android.os.IBinder; 11 12 public class PlayerService extends Service { 13 private MediaPlayer mediaPlayer; 14 //播放文件url 15 private String path; 16 17 @Override 18 public IBinder onBind(Intent arg0) { 19 return null; 20 } 21 22 @Override 23 public int onStartCommand(Intent intent, int flags, int startId) { 24 //从上级页面取到音乐源 25 path = intent.getStringExtra("url"); 26 int msg = intent.getIntExtra("msg", 0); 27 28 if(msg == 0) 29 { 30 31 //从头开始播放 32 play(0); 33 } 34 else if(msg == 1) 35 { 36 stop(); 37 } 38 else if(msg == 2) 39 { 40 pause(); 41 } 42 return super.onStartCommand(intent, flags, startId); 43 } 44 45 /** 46 * 播放音乐 47 * 48 * @param position 49 */ 50 public void play(int position) 51 { 52 try{ 53 mediaPlayer.reset(); 54 mediaPlayer.setDataSource(path); 55 mediaPlayer.prepare(); 56 //注册监听器 57 mediaPlayer.setOnPreparedListener(new PreparedListener(position)); 58 }catch(Exception e) 59 { 60 e.printStackTrace(); 61 } 62 } 63 64 /** 65 * 暂停音乐 66 * 每次暂停isPause标识设为false并得到当前的播放位置 67 */ 68 public void pause() 69 { 70 if(mediaPlayer != null) 71 { 72 mediaPlayer.pause(); 73 } 74 } 75 /** 76 * 停止音乐 77 * 这个stop和pause不一样,stop之后再想播放音乐要重新prepare一次 78 * 79 */ 80 public void stop() 81 { 82 if(mediaPlayer != null) 83 { 84 mediaPlayer.stop(); 85 try 86 { 87 mediaPlayer.prepare(); 88 }catch(Exception e) 89 { 90 e.printStackTrace(); 91 } 92 } 93 } 94 95 @Override 96 public void onDestroy() { 97 if(mediaPlayer != null) 98 { 99 mediaPlayer.stop(); 100 mediaPlayer.release(); 101 } 102 } 103 104 private final class PreparedListener implements OnPreparedListener 105 { 106 private int position; 107 public PreparedListener(int positon) 108 { 109 this.position = positon; 110 } 111 @Override 112 public void onPrepared(MediaPlayer mp) { 113 mediaPlayer.start(); 114 //如果不是从头开始播放,找到当前位置 115 if(position >0) 116 { 117 mediaPlayer.seekTo(position); 118 } 119 } 120 } 121 122 }
到目前位置,你已经可以点击列表中的一个音乐并实现播放的功能咯。
下次继续实现的内容,下一篇的内容就非常简练化了,只要自己有想法就能直接做了:
1.播放完一个音乐之后,如何继续播放下一个。(给mediaPlayer添加歌曲完成事件)
2.实现前一首和后一首歌的播放。(获取当前点击位置,然后传递(位置-1)或(位置+1))
3.设定歌曲的播放模式。(设定模式标志变量,利用本地broadcast广播给service,实时变换模式。)
4.将当前播放歌曲的信息显示在界面下方。(同理利用licalBroadcastManager实现)
上面写到点击列表中的一项,就能实现播放的功能,接下来就要继续写咯...
1.播放完一个音乐怎么让播放器继续播放,MediaPlayer这个类提供了音乐播放完毕监听事件:setOnCompletionListener。
我们要想在这音乐结束后继续播放下一个,就必须知道当前播放的音乐在列表中的位置,所以在启动PlayerService的时候,让intent对象多携带一个音乐的位置数据,在PlayerSerivce进行获取
musicPosition = intent.getExtras().getInt("musicPosition");
获取到这个position之后,我们可以用对其进行+1操作,然后启动+1这个位置的音乐url。
1 mediaPlayer.setOnCompletionListener(new OnCompletionListener() 2 { 3 @Override 4 public void onCompletion(MediaPlayer arg0) { 5 //musicList可以用工具类GetMusicListUtil里面的函数得到,是List<Mp3Info>类型 6 musicPosition = musicPosition + 1; 7 path = musicList.get(musicPosition).getUrl(); 8 play(0); 9 10 } 11 });
这样就实现继续播放下一首音乐的目的了。
而在主界面中最容易实现的就是前一首歌曲和下一首歌曲的播放功能了。
首先,声明一个listPosition变量来记录当前播放音乐在listView中的位置。然后分为以下两种情况:
第一种情况,刚打开播放器,这时候用户用手机点击某一个音乐,会触发listView的OnItemClickListener事件,这个事件中的函数参数有一个就是position,直接将这个position赋值给listPosition变量就可以。此时用户再点击上一首歌曲(或夏一首歌曲)直接对listPosition-(+)1操作,然后得到这个位置的音乐url,然后启动服务播放就行。
第二种情况,用户点击列表中的一首音乐,这首歌播放完毕,而PlayerService监听自动播放下一首歌,但是listPosition这个变量的值并未改变,若是此时点击下一首歌曲,就会重新播放当前的这个歌曲。所以当音乐换了的时候,我们需要让listPosition也知道,它应该改变。这里我用到的是LocalBroadcastManager这个类。
LocalBroadcastManager是用来在应用程序内部进行广播消息的类。当音乐播放完毕事件触发时,发送一个消息给主界面,告诉它现在播放的音乐已经变了,让其改变listPosition的值。具体用法:
在MainActivity中写一个广播接收器:
1 private LocalBroadcastManager broadcastManager; 2 3 4 broadcastManager = LocalBroadcastManager.getInstance(MainActivity.this); 5 IntentFilter intentFilter = new IntentFilter(); 6 intentFilter.addAction("这是个String字符串,随意起个名字:music_update"); 7 BroadcastReceiver receiver = new BroadcastReceiver(){ 8 @Override 9 public void onReceive(Context context, Intent intent) { 10 if(intent.getAction().equals(Constants.MUSIC_UPDATE)) 11 { 12 listPosition = intent.getExtras().getInt("musicPosition"); 13 } 14 15 } 16 }; 17 broadcastManager.registerReceiver(receiver, intentFilter);
在PlayerService中进行广播放送:
1 //也是先声明LocalBroadcastManager对象 2 broadcastManager = LocalBroadcastManager.getInstance(PlayerService.this); 3 4 //传递信息给主界面,让其改变显示信息 5 Intent intent = new Intent("这个String名字就是你在上面的那个:music_update"); 6 intent.putExtra("musicPosition", musicPosition); 7 broadcastManager.sendBroadcast(intent);
把这个放送的代码放在
setOnCompletionListener{}这个类的函数中就可以,当音乐完毕事件被触发,播放下一首音乐,就传递消息给主界面MainActivity。
在这里放上播放下一首歌曲的函数:
1 private void next() 2 { 3 listPosition = listPosition + 1; 4 if(listPosition <= musicInfo.size()-1) 5 { 6 music = musicInfo.get(listPosition); 7 } 8 else 9 { 10 music = musicInfo.get(0); 11 } 12 Intent intent = new Intent(); 13 intent.setAction("org.com.ViPlayer.MUSIC_SERVICE"); 14 intent.putExtra("url", music.getUrl()); 15 intent.putExtra("musicPosition", listPosition); 16 intent.putExtra("msg", 0);//0代表播放音乐哦 17 startService(intent); 18 Toast.makeText(MainActivity.this, "播放下一首歌曲", Toast.LENGTH_SHORT).show(); 19 }
这样,到目前为止。。。播放上一首歌曲和下一首歌曲还有继续播放的功能就实现了哦。
接下来就是音乐模式的问题:
我设定的音乐模式只有三种类型,单曲循环播放,顺序循环播放以及随机播放。如果想要把顺序播放列表一次和顺序循环播放分开的同学,可以自行添加。
在这里稍微做一点小修改,改变一下代码结构,将用到的一些常量代表值放在一个Constants的类中:
1 public class Constants { 2 3 //播放,暂停,停止 4 public static final int PLAY_MSG = 1; 5 public static final int PAUSE_MSG = 2; 6 public static final int STOP_MSG = 3; 7 8 //单曲重复,全部重复,随机播放,顺序播放一次 9 public static final int REPEAT_ONE = 4; 10 public static final int REPEAT_ALL = 5; 11 public static final int REPEAT_RANDOM = 6; 12 public static final int REPEAT_NONE = 7; 13 14 //更新播放模式动作 15 public static final String MODE_UPDATE = "update_music_mode"; 16 //更新歌曲信息,用来最下的显示 17 public static final String MUSIC_UPDATE = "update_music_info"; 18 }
将启动播放服务写成一个函数叫做startService,提取公共部分的代码出来:
1 private void startService() 2 { 3 Intent intent = new Intent(); 4 intent.setAction("org.com.ViPlayer.MUSIC_SERVICE"); 5 intent.putExtra("url", music.getUrl()); 6 intent.putExtra("musicMode", musicMode); 7 intent.putExtra("musicPosition", listPosition); 8 intent.putExtra("msg", Constants.PLAY_MSG); 9 startService(intent); 10 }
给里面的按钮统一设定点击事件(listView除外):
1 repeat.setOnClickListener(this);//repeat代表的是音乐模式的按钮 2 previous.setOnClickListener(this);//前一首歌 3 play.setOnClickListener(this);//播放按钮 4 next.setOnClickListener(this);//后一首歌 5 listView.setOnItemClickListener(new MusicItemClickListener());
点击事件的处理:
首先,用户第一次打开播放器的时候,默认模式是顺序循环播放的,当用户点击模式按钮repeat的时候,模式由(顺序循环)转换为(单曲循环),再点击一次,模式由(单曲循环)转换为(随机模式),再点击一次,模式变回(顺序循环)。这期间每点击一次,图标按钮就改变一次,setBackgroundResource改变按钮图标,用Intent对象携带音乐模式数据,然后同理用LocalBroadcastManager播放给PlayerService知道
1 public void onClick(View v) { 2 switch(v.getId()) 3 { 4 case R.id.repeat: 5 Intent intent = new Intent(Constants.MODE_UPDATE); 6 if(musicMode == Constants.REPEAT_NONE) 7 { 8 musicMode = Constants.REPEAT_ONE; 9 intent.putExtra("musicMode", musicMode); 10 repeat.setBackgroundResource(R.drawable.repeat_one); 11 Toast.makeText(MainActivity.this, "单曲循环模式", Toast.LENGTH_SHORT).show(); 12 } 13 else if(musicMode == Constants.REPEAT_ONE) 14 { 15 musicMode = Constants.REPEAT_RANDOM; 16 intent.putExtra("musicMode", musicMode); 17 repeat.setBackgroundResource(R.drawable.random); 18 Toast.makeText(MainActivity.this, "随机模式", Toast.LENGTH_SHORT).show(); 19 } 20 else if(musicMode == Constants.REPEAT_RANDOM) 21 { 22 musicMode = Constants.REPEAT_NONE; 23 intent.putExtra("musicMode", musicMode); 24 repeat.setBackgroundResource(R.drawable.repeat_none); 25 Toast.makeText(MainActivity.this, "顺序循环模式", Toast.LENGTH_SHORT).show(); 26 } 27 broadcastManager.sendBroadcast(intent); 28 break; 29 //如果想要把顺序播放和顺序重复播放分开,再另外写 30 /*else if(musicMode == Constants.REPEAT_ALL) 31 { 32 musicMode = Constants.REPEAT_NONE; 33 repeat.setBackgroundResource(R.drawable.repeat_none); 34 }*/ 35 case R.id.previous: 36 previous(); 37 break; 38 case R.id.playMusic: 39 playMusic(); 40 break; 41 case R.id.next: 42 next(); 43 break; 44 45 } 46 47 }
在PlayerService中监听音乐模式:
1 private void initReceiver() 2 { 3 IntentFilter intentFilter = new IntentFilter(); 4 intentFilter.addAction(Constants.MODE_UPDATE); 5 BroadcastReceiver receiver = new BroadcastReceiver(){ 6 @Override 7 public void onReceive(Context context, Intent intent) { 8 if(intent.getAction().equals(Constants.MODE_UPDATE)) 9 { 10 musicMode = intent.getExtras().getInt("musicMode"); 11 } 12 13 } 14 }; 15 broadcastManager.registerReceiver(receiver, intentFilter); 16 }
在PlayerService的音乐结束事件监听函数中判断当前音乐播放模式:
1 mediaPlayer.setOnCompletionListener(new OnCompletionListener() 2 { 3 @Override 4 public void onCompletion(MediaPlayer arg0) { 5 //顺序播放,设定本身就是顺序循环播放 6 if(musicMode == Constants.REPEAT_NONE) 7 { 8 if(musicPosition < musicList.size()-1) 9 { 10 musicPosition = musicPosition + 1; 11 } 12 else 13 { 14 musicPosition = 0; 15 } 16 } 17 //单曲循环 18 else if(musicMode == Constants.REPEAT_ONE) 19 { 20 //musicPosition = musicPosition; 21 } 22 23 /*else if(musicMode == Constants.REPEAT_ALL) 24 { 25 }*/ 26 27 //随机播放 28 else if(musicMode == Constants.REPEAT_RANDOM) 29 { 30 musicPosition = randomPosition(musicList.size()); 31 } 32 path = musicList.get(musicPosition).getUrl(); 33 play(0); 34 //传递信息给主界面,让其改变显示信息 35 Intent intent = new Intent(Constants.MUSIC_UPDATE); 36 intent.putExtra("musicPosition", musicPosition); 37 broadcastManager.sendBroadcast(intent); 38 } 39 });
randomPosition是一个得到0-musicList.size()之间随机数的函数。
一口气直接写完,音乐模式已经写完了,接下来就是音乐的暂停和继续播放问题。
MediaPlayer这个类有一个seekTo(int position)函数,就是用于在当前的position(是个时间)继续播放。好了,最重要的已经有了,就差不重要的了。
在点击列表中的一个音乐开始播放后,播放按钮就应该改变图标,变成可暂停状态,我们在MainActivity中设定一个isPause的布尔变量,初始化为false,就是不暂停的意思。然后再播放歌曲的函数中设定暂停或者是开启音乐:
1 /** 2 * 播放歌曲 3 * 转换播放和暂停的图标 4 */ 5 private void playMusic() 6 { 7 if(isPause == true) 8 {//暂停中 9 play.setBackgroundResource(R.drawable.play); 10 startPauseService(); 11 isPause = false; 12 } 13 else 14 {//播放中 15 play.setBackgroundResource(R.drawable.pause); 16 startService(); 17 isPause = true; 18 } 19 }
注意,else启动的是开启播放服务,if启动的是开启暂停服务。暂停服务只有msg的数据和播放服务不一样,告诉PlayerService我是来启动你的暂停音乐播放功能的。
1 private void startPauseService() 2 { 3 Intent intent = new Intent(); 4 intent.setAction("org.com.ViPlayer.MUSIC_SERVICE"); 5 intent.putExtra("musicMode", musicMode); 6 intent.putExtra("url", music.getUrl()); 7 intent.putExtra("musicPosition", listPosition); 8 intent.putExtra("msg", Constants.PAUSE_MSG); 9 startService(intent); 10 }
而在PlayerService中,接收来自主界面的信息进行服务:
1 public int onStartCommand(Intent intent, int flags, int startId) { 2 //从上级页面取到音乐源和播放标识,播放位置 3 path = intent.getStringExtra("url"); 4 musicMode = intent.getIntExtra("musicMode", Constants.REPEAT_NONE); 5 int msg = intent.getIntExtra("msg", 0); 6 musicPosition = intent.getExtras().getInt("musicPosition"); 7 if(msg == Constants.PLAY_MSG) 8 { 9 //如果当前状态是一首歌的暂停状态 10 if(isPause == true) 11 {//那么需要继续从currentPosition位置播放 12 play(timePosition); 13 } 14 else 15 {//如果当前未播放歌曲,则从头开始播放 16 play(0); 17 } 18 } 19 else if(msg == Constants.STOP_MSG) 20 { 21 stop(); 22 } 23 else if(msg == Constants.PAUSE_MSG) 24 { 25 pause(); 26 } 27 return super.onStartCommand(intent, flags, startId); 28 }
其中的timePosition很重要,是从哪里来呢,是在pause()函数中记录的。
1 /** 2 * 播放音乐 3 * 4 * @param position 5 */ 6 public void play(int position) 7 { 8 try{ 9 mediaPlayer.reset(); 10 mediaPlayer.setDataSource(path); 11 mediaPlayer.prepare(); 12 //注册监听器 13 mediaPlayer.setOnPreparedListener(new PreparedListener(position)); 14 }catch(Exception e) 15 { 16 e.printStackTrace(); 17 } 18 //当前是播放状态,则暂停标识为false 19 isPause = false; 20 } 21 22 /** 23 * 暂停音乐 24 * 每次暂停isPause标识设为false并得到当前的播放位置 25 */ 26 public void pause() 27 { 28 if(mediaPlayer != null) 29 { 30 mediaPlayer.pause(); 31 isPause = true; 32 timePosition = mediaPlayer.getCurrentPosition(); 33 } 34 } 35 /** 36 * 停止音乐 37 * 这个stop和pause不一样,stop之后再想播放音乐要重新prepare一次 38 * 39 */ 40 public void stop() 41 { 42 if(mediaPlayer != null) 43 { 44 mediaPlayer.stop(); 45 try 46 { 47 mediaPlayer.prepare(); 48 }catch(Exception e) 49 { 50 e.printStackTrace(); 51 } 52 } 53 }
这样,最基本的功能就实现咯。 其中还有很多细节:比如说打开播放器,没有点击音乐列表进行播放,而是直接点击播放的按钮,这个问题可以在初始时给其一个默认播放的音乐,比如说listPosition是0的音乐(列表中的第一首歌)。 还有列表下方显示歌曲信息,项目中也写了,因为LocalBroadcastManager原理一样,就没说明。
同学们有需要可以自行在其中添加音乐播放进度条,或者是列表中显示专辑封面图片等需求。。。。
这个界面很一般,没什么设计感= =。 就是随意学个知识,大家不要吐槽。。。稍后就上传源码咯。。。
最后的版本就是酱紫。。。