Android 系統提供瞭MediaPlayer控件,讓我們能夠利用它實現音頻的播放。
而從學Android開始,在看教程的時候,我就想,我要自己做一個音樂播放器,因為一個完整的音樂播放器是有很多功能的,它涉及到很多方面的知識,可以幫助我們更好地學習和掌握關於Android的點點滴滴的知識。
既然我們現在是來學習怎麼用代碼打出我們自己的音樂播放器,我們就別著急,心急吃不瞭熱豆腐,一口吃不成大胖子。
一步一步地,來實現我們的音樂播放器吧。
那麼思路是怎麼樣的呢?我當時是這樣想的,先做一部分功能,能夠看到音樂,控制音樂就可以瞭,所以目前的功能實現如下:
1)要拿出本地的音樂文件,然後將它展現在一個列表上。
1.1)利用ContentResolver 獲取本地數據,關於怎麼獲取本地的音樂文件或者圖片文件,請看:
Android中利用ContentResolver獲取本地音樂和相片
1.2)利用ListView 展現數據,每個Listitem會顯示歌曲名,歌手,播放時間,還有如果有唱片的圖片,還要把唱片圖片展示出來。
2)要有一排按鈕,能夠實現播放,前一首,後一首,退出,模式選擇(順序播放,循環播放,單曲循環,隨機播放等),放在最下面
3)要有一條進度條,隨著音樂的播放,一步一步地向前刷刷刷,
4)既然有進度條,那也要有兩個展示時間的控件,一個展示音樂有多長,一個展示播到哪瞭。這個跟進度條都要放在按鈕的上面。
5)一個展示當前播放歌曲的TextView,放在最上面。
所以一開始就有瞭下面的界面:
因為我不會美工啊,所以一開始我就用按鈕來做播放,停止等控制功能,我們是在學習嘛,美化的東西慢慢來。(其實看上去也不算很醜,對吧?)
但是後來一想,既然是學習啊,又不會美工,那麼我就來實現一排自定義的Button吧,於是就有瞭下面的界面。
看到下面一排醜醜的按鈕沒瞭,哈哈,我畫的!
既然說到瞭這個,我們這篇文件就先說說自定義按鈕是怎麼實現的吧。
我在之前寫過一篇關於自定義View的文章:Android學習小demo(1)自定義View,vc3Ryb25nPsbkyrXUrcDtysfSu9H5tcSjrM7Sw8e+zdaxvdPAtL+0tcS0+sLrsMmjujwvcD4KPHA+MaOpz8jU2nJlcy92YWx1ZXMv1tC0tL2o0ru49mF0dHJzLnhtbM7EvP6jrNTawO/D5tfUtqjS5cr00NSjujwvcD4KPHA+PHByZSBjbGFzcz0=”brush:java;”>
我們定義瞭兩個屬性,其中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 StartStopButton private boolean flagStart = true; //Only for ModeButton public 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 OnClickListener setOnTouchListener(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 height int 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 triangle 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); // draw the vertical line canvas.drawLine(verticles[6], verticles[7], verticles[8], verticles[9],paint); canvas.restore(); } private void drawBackward(Canvas canvas, boolean pressed) { // get the shorter width or height int 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 triangle 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); // draw the vertical line canvas.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 height int 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 height int 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(); } @Override public 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瞭,
@Override public 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 OnClickListener setOnTouchListener(this); }
關於這個Touch事件和Click事件,推薦大傢去看一下郭大俠的這兩篇文章:
Android事件分發機制完全解析,帶你從源碼的角度徹底理解(上)
Android事件分發機制完全解析,帶你從源碼的角度徹底理解(下)
到這裡,我們的控件就可以瞭,接下來就是在佈局文件中,把它當作按鈕用瞭。
可以看到我們自己設定的custom:type的值是不同的。
然後我們就可以看到一大排自定義的按鈕瞭,想要什麼圖案,自己畫哦!
到這裡,其實沒完!
我發現有一個副作用。。。。
因為我們界面上有進度條嘛,所以其實界面一直在刷刷刷,那麼我們自定義的按鈕,也就一直在刷刷刷。。。
好瞭,睡覺瞭。源代碼請再等等,慢慢講,我還會慢慢改,然後最後會放上來的。