- 浏览: 650663 次
文章分类
最新评论
-
richondow:
Android实战开发租赁管理软件百度网盘下载:链接:http ...
Android实战开发租赁管理软件(适配UI,数据的存储,多线程下载)课程分享 -
古老的传言:
大哥,课程还有吗?有的话可否发一份给我?我的邮箱7606089 ...
马哥Linux系列七:实战大数据技术专题(Hadoop、NoSQL、Zookeeper、MapReduce)课程分享 -
kgtw:
1223137028@qq.com求发一份
基于Lucene4.6+Solr4.6+Heritrix1.14+S2SH实战开发从无到有垂直搜索引擎
Android 音乐播放器的实现(一)自定义按钮的实现
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"?> <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 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事件分发机制完全解析,带你从源码的角度彻底理解(下)
到这里,我们的控件就可以了,接下来就是在布局文件中,把它当作按钮用了。
<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的值是不同的。
然后我们就可以看到一大排自定义的按钮了,想要什么图案,自己画哦!
到这里,其实没完!
我发现有一个副作用。。。。
因为我们界面上有进度条嘛,所以其实界面一直在刷刷刷,那么我们自定义的按钮,也就一直在刷刷刷。。。
好了,睡觉了。源代码请再等等,慢慢讲,我还会慢慢改,然后最后会放上来的。
相关推荐
android 自定义 单选按钮 radioButton
用两种方法来实现android自定义按钮效果。
Android自定义按钮,实现长按处理的功能 详情见http://blog.csdn.net/huahuadashen/article/details/17711551
android 自定义按钮实现水波纹效果。android 自定义按钮实现水波纹效果
android 自定义好看的开关按钮 android 自定义好看的开关按钮
使用Android完全自定义控件,继承自View,Canvas绘制。
android源代码(程序启动播放背景音乐,自定义按钮,自定义对话框,界面精美)
在具体实现代码之前,我们先来了解一下Android api对实现自定义Camera的介绍。 根据api的介绍,对于Camera应用可以简单总结以下几个步骤。 1.检查Camera是否存在,并在AndroidManifest.xml中赋予相关的权限; 2....
android 自定义控件代码实现。有需要自定义按钮的,请下载使用,供学习
自定义View实现随滑动由箭头变对勾的指示按钮
android自定义倒计时按钮,可以自定义样式
Android自定义View实现开关按钮效果,适用于Android进阶初级开发者参考探讨。博文地址:http://blog.csdn.net/jaynm/article/details/52601935
本示例完整实现了Android中自定义开关按钮的功能,具体实现细节请参见博文:http://blog.csdn.net/l1028386804/article/details/48102871
「Ticktock」一款 material design 风格的音乐播放器,可以播放本地和网络音乐,并且提供歌词显示。项目采用 clean architecture、mvp、rxJava2、retrofit2、dagger2 进行开发,代码结构清晰,层次分明。 本项目衍生...
实现按钮所有的文件都是java文件,可以很方便的打包成jar,在项目中直接使用。本工程最外层目录有demo的运行图演示。代码设计模式良好,不仅能学习自定义View,还能学习如何分割功能。
android studio 自定义标题栏,自定义回退按钮事件,自定义右侧显示文字还是图片,自定义右侧点击事件
android 自定义按钮控件。本人博客:day_moon
android下的自定义图片按钮,ImageButton
Android技术知识点:如何实现自定义开关按钮
自定义实现switch开关按钮,这一个拥有漂亮外观的Android自定义开关按纽SwitchButton。