Android 音樂播放器的實現(一)自訂按鈕的實現

來源:互聯網
上載者:User

Android 系統提供了MediaPlayer控制項,讓我們能夠利用它實現音訊播放。

而從學Android開始,在看教程的時候,我就想,我要自己做一個音樂播放器,因為一個完整的音樂播放器是有很多功能的,它涉及到很多方面的知識,可以協助我們更好地學習和掌握關於Android的點點滴滴的知識。

一步一步地,來實現我們的音樂播放器吧。

那麼思路是怎麼樣的呢?我當時是這樣想的,先做一部分功能,能夠看到音樂,控制音樂就可以了,所以目前的功能實現如下:

1)要拿出本地的音樂檔案,然後將它展現在一個列表上。

1.1)利用ContentResolver 擷取本機資料,關於怎麼擷取本地的音樂檔案或者圖片檔案,請看: Android中利用ContentResolver擷取本地音樂和相片

2)要有一排按鈕,能夠實現播放,前一首,後一首,退出,模式選擇(順序播放,迴圈播放,單曲迴圈,全部隨機播放等),放在最下面

3)要有一條進度條,隨著音樂的播放,一步一步地向前刷刷刷,

4)既然有進度條,那也要有兩個展示時間的控制項,一個展示音樂有多長,一個展示播到哪了。這個跟進度條都要放在按鈕的上面。

5)一個展示當前播放歌曲的TextView,放在最上面。

所以一開始就有了下面的介面:



因為我不會美工啊,所以一開始我就用按鈕來做播放,停止等控制功能,我們是在學習嘛,美化的東西慢慢來。(其實看上去也不算很醜,對吧?)

但是後來一想,既然是學習啊,又不會美工,那麼我就來實現一排自訂的Button吧,於是就有了下面的介面。

看到下面一排醜醜的按鈕沒了,哈哈,我畫的!

既然說到了這個,我們這篇檔案就先說說自訂按鈕是怎麼實現的吧。

我在之前寫過一篇關於自訂View的文章:其實原理是一樣的,我們就直接來看的代碼吧:

1)先在res/values/中建立一個attrs.xml檔案,在裡面自訂屬性:

<?xml version="1.0" encoding="utf-8"?><resources>    <declare-styleable name="CustomAudioIcon">        <attr name="type">            <enum name="start" value="0" />                        <enum name="forward" value="1" />            <enum name="backward" value="2" />            <enum name="exit" value="3" />            <enum name="mode" value="4" />        </attr>        <attr name="color" format="color"/>    </declare-styleable></resources>

我們定義了兩個屬性,其中type是一個枚舉類型,分別有start, forward, backward, exit, mode等類型。

雖然我們有五種類型的按鈕要展現,但是我們只要實現一個自訂的類,然後根據傳入的不同 type 的值來畫出不同的圖形就好。

下面我們看看自訂按鈕的代碼:

public class CustomAudioIcon extends View implements OnTouchListener {//private static final String TAG = "com.example.nature.CustoAudioIcon";private static final int defaultType = -1;private static final int start = 0;private static final int forward = 2;private static final int backward = 3;private static final int exit = 4;private static final int mode = 5;private int type;private int color;private Paint upPaint;private Paint pressPaint;private Paint boxPaint;private Paint paint;private int width,height;private boolean pressed = false;//Only for StartStopButtonprivate boolean flagStart = true;//Only for ModeButtonpublic static final int MODE_ONE_LOOP = 0;public static final int MODE_ALL_LOOP = 1;public static final int MODE_RANDOM = 2;public static final int MODE_SEQUENCE = 3; private int currentMode = 3;public CustomAudioIcon(Context context, AttributeSet attrs) {super(context, attrs);TypedArray typedArray = context.obtainStyledAttributes(attrs,R.styleable.CustomAudioIcon);type = typedArray.getInt(R.styleable.CustomAudioIcon_type, defaultType);color = typedArray.getColor(R.styleable.CustomAudioIcon_color,Color.BLACK);typedArray.recycle();init();setClickable(true);//In order to make this view can accept the OnClickListenersetOnTouchListener(this);}private void init() {boxPaint = new Paint();boxPaint.setColor(color);boxPaint.setAntiAlias(true);boxPaint.setStrokeWidth(1);upPaint = new Paint();upPaint.setColor(Color.BLACK);upPaint.setAntiAlias(true);upPaint.setStrokeWidth(1);pressPaint = new Paint();pressPaint.setColor(Color.GREEN);pressPaint.setAntiAlias(true);pressPaint.setStrokeWidth(1);}public void onDraw(Canvas canvas) {paint = pressed ? pressPaint : upPaint;width = getMeasuredWidth();height = getMeasuredHeight();if(pressed){canvas.drawColor(Color.parseColor("#447744"));}switch (type) {case start:if(flagStart){drawStart(canvas, pressed);}else{drawStop(canvas, pressed);}break;case forward:drawForward(canvas, pressed);break;case backward:drawBackward(canvas, pressed);break;case exit:drawExit(canvas, pressed);break;case mode:drawMode(canvas, pressed);break;}boxPaint.setStyle(Style.STROKE);Rect rect = canvas.getClipBounds();rect.bottom--;rect.right--;canvas.drawRect(rect, boxPaint);}// public void onMeasure(int widthMeasureSpec, int heightMeasureSpec){// setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec),// MeasureSpec.getSize(heightMeasureSpec));// }private void drawStart(Canvas canvas, boolean pressed) {float scaleWidth = width < height ? width : height;// calculate the vertexes.float[] verticles = { (float) (0.21 * scaleWidth),(float) (0.1 * scaleWidth), (float) (0.21 * scaleWidth),(float) (0.9 * scaleWidth), (float) (0.9 * scaleWidth),(float) (0.5 * scaleWidth) };canvas.drawLine(verticles[0], verticles[1], verticles[2], verticles[3],paint);canvas.drawLine(verticles[0], verticles[1], verticles[4], verticles[5],paint);canvas.drawLine(verticles[2], verticles[3], verticles[4], verticles[5],paint);}private void drawStop(Canvas canvas, boolean pressed) {float scaleWidth = width < height ? width : height;// calculate the vertexes.float[] verticles = { (float) (0.4 * scaleWidth), (float) (0.1 * scaleWidth), (float) (0.4 * scaleWidth), (float) (0.9 * scaleWidth), (float) (0.6 * scaleWidth), (float) (0.1 * scaleWidth),(float) (0.6 * scaleWidth), (float) (0.9 * scaleWidth)};canvas.drawLine(verticles[0], verticles[1], verticles[2], verticles[3],paint);canvas.drawLine(verticles[4], verticles[5], verticles[6], verticles[7],paint);}private void drawForward(Canvas canvas, boolean pressed) {// get the shorter width or heightint minWH = width < height ? width : height;float scaleWidth = (float) (minWH * 0.8);// calculte the vertexes.float[] verticles = { (float) (0.21 * scaleWidth),(float) (0.1 * scaleWidth), (float) (0.21 * scaleWidth),(float) (0.9 * scaleWidth), (float) (0.9 * scaleWidth),(float) (0.5 * scaleWidth), (float) (0.9 * scaleWidth),(float) (0.1 * scaleWidth), (float) (0.9 * scaleWidth),(float) (0.9 * scaleWidth) };canvas.save();canvas.translate((float) (0.1 * minWH), (float) (0.1 * minWH));// draw the trianglecanvas.drawLine(verticles[0], verticles[1], verticles[2], verticles[3],paint);canvas.drawLine(verticles[0], verticles[1], verticles[4], verticles[5],paint);canvas.drawLine(verticles[2], verticles[3], verticles[4], verticles[5],paint);// draw the vertical linecanvas.drawLine(verticles[6], verticles[7], verticles[8], verticles[9],paint);canvas.restore();}private void drawBackward(Canvas canvas, boolean pressed) {// get the shorter width or heightint minWH = width < height ? width : height;float scaleWidth = (float) (minWH * 0.8);// calculte the vertexes.float[] verticles = { (float) (0.79 * scaleWidth),(float) (0.1 * scaleWidth), (float) (0.79 * scaleWidth),(float) (0.9 * scaleWidth), (float) (0.1 * scaleWidth),(float) (0.5 * scaleWidth), (float) (0.1 * scaleWidth),(float) (0.1 * scaleWidth), (float) (0.1 * scaleWidth),(float) (0.9 * scaleWidth) };canvas.save();canvas.translate((float) (0.1 * minWH), (float) (0.1 * minWH));// draw the trianglecanvas.drawLine(verticles[0], verticles[1], verticles[2], verticles[3],paint);canvas.drawLine(verticles[0], verticles[1], verticles[4], verticles[5],paint);canvas.drawLine(verticles[2], verticles[3], verticles[4], verticles[5],paint);// draw the vertical linecanvas.drawLine(verticles[6], verticles[7], verticles[8], verticles[9],paint);canvas.restore();}private void drawExit(Canvas canvas, boolean pressed) {paint.setStyle(Style.STROKE);// get the shorter width or heightint minWH = width < height ? width : height;float scaleWidth = (float) (minWH * 0.8);canvas.save();canvas.translate((float) (0.1 * minWH), (float) (0.1 * minWH));canvas.drawCircle((float)(0.5 * scaleWidth), (float)(0.5 * scaleWidth), (float)(0.4 * scaleWidth), paint);canvas.restore();}private void drawMode(Canvas canvas, boolean pressed) {paint.setStyle(Style.STROKE);// get the shorter width or heightint minWH = width < height ? width : height;float scaleWidth = (float) (minWH * 0.8);canvas.save();canvas.translate((float) (0.1 * minWH), (float) (0.1 * minWH));switch(currentMode){case MODE_ONE_LOOP:canvas.drawCircle((float)(0.5 * scaleWidth), (float)(0.5 * scaleWidth), (float)(0.1 * scaleWidth), paint);break;case MODE_ALL_LOOP:canvas.drawCircle((float)(0.4 * scaleWidth), (float)(0.5 * scaleWidth), (float)(0.1 * scaleWidth), paint);canvas.drawCircle((float)(0.6 * scaleWidth), (float)(0.5 * scaleWidth), (float)(0.1 * scaleWidth), paint);break;case MODE_RANDOM:canvas.drawCircle((float)(0.3 * scaleWidth), (float)(0.5 * scaleWidth), (float)(0.1 * scaleWidth), paint);canvas.drawCircle((float)(0.5 * scaleWidth), (float)(0.5 * scaleWidth), (float)(0.1 * scaleWidth), paint);canvas.drawCircle((float)(0.7 * scaleWidth), (float)(0.5 * scaleWidth), (float)(0.1 * scaleWidth), paint);break;case MODE_SEQUENCE:canvas.drawCircle((float)(0.2 * scaleWidth), (float)(0.5 * scaleWidth), (float)(0.1 * scaleWidth), paint);canvas.drawCircle((float)(0.4 * scaleWidth), (float)(0.5 * scaleWidth), (float)(0.1 * scaleWidth), paint);canvas.drawCircle((float)(0.6 * scaleWidth), (float)(0.5 * scaleWidth), (float)(0.1 * scaleWidth), paint);canvas.drawCircle((float)(0.8 * scaleWidth), (float)(0.5 * scaleWidth), (float)(0.1 * scaleWidth), paint);break;}canvas.restore();}@Overridepublic boolean onTouch(View v, MotionEvent event) {switch (event.getAction()) {case MotionEvent.ACTION_DOWN:pressed = true;invalidate();break;case MotionEvent.ACTION_UP:pressed = false;invalidate();if(type == start){flagStart = !flagStart;}if(type == mode){currentMode = (currentMode + 1) % 4;}break;}return false;}/** * If showing the start triangle, returns true, otherwise returns false * @return */public boolean isStartStatus() {return flagStart;}/** * Change the flag outside * @param flagStart */public void setFlagStart(boolean flagStart) {this.flagStart = flagStart;invalidate();}}

在類中,我們首先還是通過typedArray來擷取到我們的type值,然後根據type值來畫不同的內容。

因為這幾個控制項我都是在布局檔案中定義好長寬的,所以不需要在這裡面重寫onMeasure函數,我們只要關心如何在 Ondraw() 裡面畫圖形就好了。

可以看到在 onDraw() 方法裡面,根據不同的type,我們是會畫不同的按鈕,比如 start 按鈕,它有兩個狀態,當我們點擊start的時候,它是會變成stop(或者pause,在這裡我沒有實現pause,下一次實現)的狀態。

case start:if(flagStart){drawStart(canvas, pressed);}else{drawStop(canvas, pressed);}break;
在drawStart 裡面,我們是畫了一個向右的等邊三角形,

private void drawStart(Canvas canvas, boolean pressed) {float scaleWidth = width < height ? width : height;// calculate the vertexes.float[] verticles = { (float) (0.21 * scaleWidth),(float) (0.1 * scaleWidth), (float) (0.21 * scaleWidth),(float) (0.9 * scaleWidth), (float) (0.9 * scaleWidth),(float) (0.5 * scaleWidth) };canvas.drawLine(verticles[0], verticles[1], verticles[2], verticles[3],paint);canvas.drawLine(verticles[0], verticles[1], verticles[4], verticles[5],paint);canvas.drawLine(verticles[2], verticles[3], verticles[4], verticles[5],paint);}
而當我們點擊start的時候,它就會變成stop了,就要畫stop了,就是一個豎起來的等號(||)了,

private void drawStop(Canvas canvas, boolean pressed) {float scaleWidth = width < height ? width : height;// calculate the vertexes.float[] verticles = { (float) (0.4 * scaleWidth), (float) (0.1 * scaleWidth), (float) (0.4 * scaleWidth), (float) (0.9 * scaleWidth), (float) (0.6 * scaleWidth), (float) (0.1 * scaleWidth),(float) (0.6 * scaleWidth), (float) (0.9 * scaleWidth)};canvas.drawLine(verticles[0], verticles[1], verticles[2], verticles[3],paint);canvas.drawLine(verticles[4], verticles[5], verticles[6], verticles[7],paint);}
p.s. ^_^,有意思吧,哈哈哈哈。

好了,那麼是如何?按鈕點擊的效果的呢,那就是要實現OnTouchListener了,

@Overridepublic boolean onTouch(View v, MotionEvent event) {switch (event.getAction()) {case MotionEvent.ACTION_DOWN:pressed = true;invalidate();break;case MotionEvent.ACTION_UP:pressed = false;invalidate();if(type == start){flagStart = !flagStart;}if(type == mode){currentMode = (currentMode + 1) % 4;}break;}return false;}
其實任何一個控制項的點擊,都是由這兩個動作組成的,Down下去,Up上來,在這裡,擷取到touch事件,然後根據不同的狀態,設定pressed 的值,在onDraw函數中,會根據pressed的值去擷取不同的Paint,

public void onDraw(Canvas canvas) {paint = pressed ? pressPaint : upPaint;width = getMeasuredWidth();height = getMeasuredHeight();if(pressed){canvas.drawColor(Color.parseColor("#447744"));}
然後再調用 Invaldiate() 函數重新重新整理頁面,就達到點擊的效果了。
一般情況,我們如果調用OnTouch函數,我們都是在OnTouch函數中返回一個 true,表明touch事件已經被我們消費掉了,不用再繼續走下去了。

但是在這裡,我們不能這麼做,因為我們在Activity中要給這些自訂的View設定OnClickListener呢,才能來控制我們音樂的播放暫停啊,所以這裡必須返回false。

但是如果返回 false, Down事件被觸發之後,就不會再繼續觸發Up事件了,這是因為預設的View是不能點擊的,才會發生這樣的事情,所以我們只需要在初始化的時候,將這個View 設定成可點擊的就好了,如下:

public CustomAudioIcon(Context context, AttributeSet attrs) {super(context, attrs);TypedArray typedArray = context.obtainStyledAttributes(attrs,R.styleable.CustomAudioIcon);type = typedArray.getInt(R.styleable.CustomAudioIcon_type, defaultType);color = typedArray.getColor(R.styleable.CustomAudioIcon_color,Color.BLACK);typedArray.recycle();init();setClickable(true);//In order to make this view can accept the OnClickListenersetOnTouchListener(this);}
關於這個Touch事件和Click事件,推薦大家去看一下郭大俠的這兩篇文章:

Android事件分發機制完全解析,帶你從源碼的角度徹底理解(上)

Android事件分發機制完全解析,帶你從源碼的角度徹底理解(下)

到這裡,我們的控制項就可以了,接下來就是在布局檔案中,把它當作按鈕用了。

  <com.example.nature.CustomAudioIcon        android:id="@+id/btnMode"        android:layout_width="64dip"        android:layout_height="64dip"        android:layout_alignParentBottom="true"        custom:type="mode"        custom:color="#66DD22" />    <com.example.nature.CustomAudioIcon        android:id="@+id/btnPrevious"        android:layout_width="64dip"        android:layout_height="64dip"        android:layout_alignBaseline="@+id/btnMode"        android:layout_alignParentBottom="true"        android:layout_toRightOf="@+id/btnMode"        custom:type="backward"        custom:color="#66DD22" />    <com.example.nature.CustomAudioIcon        android:id="@+id/btnStartStop"        android:layout_width="64dip"        android:layout_height="64dip"        android:layout_alignBaseline="@+id/btnMode"        android:layout_alignParentBottom="true"        android:layout_toRightOf="@+id/btnPrevious"        custom:type="start"        custom:color="#66DD22" />    <com.example.nature.CustomAudioIcon        android:id="@+id/btnNext"        android:layout_width="64dip"        android:layout_height="64dip"        android:layout_alignBaseline="@+id/btnMode"        android:layout_alignParentBottom="true"        android:layout_toRightOf="@+id/btnStartStop"        custom:type="forward"        custom:color="#66DD22" />    <com.example.nature.CustomAudioIcon        android:id="@+id/btnExit"        android:layout_width="64dip"        android:layout_height="64dip"        android:layout_alignBaseline="@+id/btnMode"        android:layout_alignParentBottom="true"        android:layout_toRightOf="@+id/btnNext"        custom:type="exit"        custom:color="#66DD22" />   

可以看到我們自己設定的custom:type的值是不同的。

然後我們就可以看到一大排自訂的按鈕了,想要什麼圖案,自己畫哦!

到這裡,其實沒完!

我發現有一個副作用。。。。

因為我們介面上有進度條嘛,所以其實介面一直在刷刷刷,那麼我們自訂的按鈕,也就一直在刷刷刷。。。

好了,睡覺了。原始碼請再等等,慢慢講,我還會慢慢改,然後最後會放上來的。


聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.