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其实原理是一样的,我们就直接来看的代码吧:

1)先在res/values/中创建一个attrs.xml文件,在里面自定义属性:

<?xml version="1.0" encoding="utf-8"?>                                                                                                        

我们定义了两个属性,其中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事件分发机制完全解析,带你从源码的角度彻底理解(下)

到这里,我们的控件就可以了,接下来就是在布局文件中,把它当作按钮用了。

                     

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

然后我们就可以看到一大排自定义的按钮了,想要什么图案,自己画哦!

到这里,其实没完!

我发现有一个副作用。。。。

因为我们界面上有进度条嘛,所以其实界面一直在刷刷刷,那么我们自定义的按钮,也就一直在刷刷刷。。。

好了,睡觉了。源代码请再等等,慢慢讲,我还会慢慢改,然后最后会放上来的。


更多相关文章

  1. android EventBus学习记录
  2. android 录音事件
  3. Android(安卓)2.2 r1 API 中文文档系列(12) ―― Button
  4. 使用Android(安卓)Studio检测内存泄露
  5. 不依赖于Activity的Android全局悬浮窗的实现
  6. 【移动开发】Android中三种超实用的滑屏方式汇总(ViewPager、View
  7. Android实现控制第三方音乐播放器暂停/播放
  8. Android(安卓)XML解析(Pull解析器)
  9. 五个维度:Android内存管理、优化

随机推荐

  1. Context与Application Context
  2. android service 之一 (start service)
  3. Android(安卓)菜单(OptionMenu)大全 建立
  4. AudioManager详解(结合源代码)
  5. Android(安卓)PendingIntent Notificatio
  6. Android(安卓)Animation 用法解析
  7. 关于Android隐式启动Activity
  8. ubutun下NDK编译环境配置
  9. Andriod视频http://pan.baidu.com/share/
  10. Mac下Android(安卓)SDK配置环境变量的配