失眠网,内容丰富有趣,生活中的好帮手!
失眠网 > Android 自定义音乐播放器实现

Android 自定义音乐播放器实现

时间:2021-09-20 22:21:24

相关推荐

Android 自定义音乐播放器实现

Android自定义音乐播放器

一:首先介绍用了哪些Android的知识点:

1 MediaPlayer工具来播放音乐

2 Handle。因为存在定时任务(歌词切换,动画,歌词进度条变换等)需要由Handle来处理Ui相关内容

3 动态权限申请(该应用程序读取本地歌曲,并且设置音质相关属性)且这两个权限在Android6.0后都需要动态申请

4 手势控制 (左划和右划需要满足一定条件后可以进行切歌)

5 Service服务 (启动Service 绑定Service 前台Service)

6 BroadcastReceiver 广播,Service与Activity,Activity与Activity存在动态交互,需要广播实现

7 基本的重写View能力和Intent交互数据的能力

8 Animation动画 图片旋转 歌词更新

二:实现过程(简要步骤,下面会详细讲解)

1 先编写MusicInfo工具类。因为我们是从手机内存中去读取音乐的相关信息,那么读出的数据该存储到MusicInfo工具类集合中。

2 先申请权限,然后再去手机内存将音乐及其相关的信息读出来 ,用一个ListView容器去装载所有的本地音乐

PS:到了这里基本的音乐信息列表已经有了 这也是我们的主界面(音乐列表界面) 即: 展示音乐/歌曲列表;

3 这时候先不方去实现播放这类的功能,我们先去处理歌词

这里说明一下。一个歌词文件(.Lrc)里面内容格式如下

(张卫健--真英雄)

可以发现他由时间戳和歌词内容两部分组成。有了这个信息后。编写LrcContent工具类,用于记录歌词内容和歌词时间。然后去手机里面寻找歌曲对应的歌词文件,将其编码,读出,装载为LrcContent集合。

4 编写Service类,Service主要用来处理:音乐播放,前台服务。在播放状态改变的时候与播放音乐的Activity进行通信。该Service由主界面启动,后面的Activity绑定即可

5 编写播放音乐的Activity类(MusicPlay)。当我们从主界面(音乐列表界面)点击了一首具体的音乐时,就会调转到该Activity,所有首先,主界面Activity需要传递一些信息给该Activity。

(1) MusicInfo工具类集合。即手机中所有的音乐信息

(2)当前点击的歌曲,传递位置(position)即可

好,现在我们播放音乐的Activity有了所有的音乐信息,还有当前需要播放的歌曲位置。因为需要前台服务,所以我们把音乐播放的控制权交给Service,我们去绑定服务,然后把所有的音乐信息,还有当前需要播放的歌曲位置都传递给Serivce,Service来控制播放音乐。

6 好了,现在我们的程序可以播放音乐了,我们再来一步步完善细节,歌词同步,该功能自定义View实现,最后显现在播放音乐的Activity中。注意Mediaplayer有一个重要属性:

mediaPlayer.getCurrentPosition()。该方法会返回当前播放时间,不过返回的时候时毫秒(重)。

自定义View(LrcView 显示歌词),该View中除了传统的自定义View需要的OnDraw之类的方法外,还需要获取第三步中的LrcContent集合,有了这个我们就有了所有的歌词内容和歌词相应的时间,那么同步如何实现呢?音乐最终该View要显示在播放音乐的Activity(MusicPlay)中,我们去MusicPlay的布局文件申明该View,然后在MusicPlay中编写一个定时器,可以设定每一秒启动一次,定时器发送消息,在Handle中接受消息,处理消息。Handle中我们需要:获取歌曲当前播放时间,根据当前播放时间去LrcContent集合中寻求匹配的歌词。用invalidate()方法,通知自定义View重绘,来同步更新歌词

7 我们的音乐播放器还差一个重要的东西,音乐控制器部分。这部分需要来控制播放上一首,下一首,播放/暂停,音乐进度拖动,音量设置。

该部分不难,所以再这里不详细讲。

三:效果图 因为完整录制的GIF太大,传不上来所以分批处理

四:代码精讲 代码量也不很大,但是全贴出来挨着讲又影响阅读。所以部分节选和重要知识点一并讲解。

PS:源码中含有大量System.out.println("XXXX");语句。个人比较偏爱的一种测试方式。。应该不干扰阅读,忘见谅

1 权限获取,我们要做的第一件事就是去内存读取音乐相关信息,那么我们就需要获得相关的权限,从Android6.0开始部分权限不仅需要在AndroidManifest.xml文件中声明,还需要在运行程序的时候动态获取.这里以读取存储权限为例:

首先在AndroidManifest.xml中定义

<uses-permissionandroid:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

在Java业务代码中先判断是否已经有权限,有权限就不再申请,没有就申请权限

//首先检查自身是否已经拥有相关权限,拥有则不再重复申请intcheck = checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) ;//没有相关权限if(check != PackageManager.PERMISSION_GRANTED){//申请权限 STORGE_REQUEST = 1ActivityCompat.requestPermissions(this,newString[] {Manifest.permission.WRITE_EXTERNAL_STORAGE} ,STORGE_REQUEST);}else{//已有权限的情况下可以直接初始化程序 init();}

当我们申请权限后,去判断用户是否给与了相关权限,如果赋予了就可以做我们的事情了

/*申请权限处理结果*/@Overridepublic voidonRequestPermissionsResult(intrequestCode, @NonNull String[] permissions, @NonNullint[] grantResults) {super.onRequestPermissionsResult(requestCode, permissions, grantResults);switch(requestCode){caseSTORGE_REQUEST:if(grantResults.length> 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED){//完成程序的初始化init();System.out.println("程序申请权限成功,完成初始化") ;}else{System.out.println("程序没有获得相关权限,请处理");}break;}}

关于权限这部分:有兴趣的可以看下参考下这篇博客

Android 6.0 运行时权限管理最佳实践

2 有了权限,我们就可以从手机中去读取音乐相关信息了,这里讲解下如何去读取信息

(1) 我们需要先创建一个存储音乐信息MusicInfo工具类

注意:代码没贴完,还有属性的get和set方法没列出

public class MusicInfo implements Serializable {private int _id = - 1; //音乐标识码private int duration = -1 ; //音乐时常private String artist = null ; //音乐作者private String musicName= null ; //音乐名字private String album = null ; //音乐文件专辑private String title = null ; //音乐文件标题private int size ; //音乐文件的大小 返回byte大小private String data ; //获取文件的完整路径private String album_id ; //实际存储为音乐专辑团片}

(2)读取音乐信息。

Android中的音乐信息存储再手机数据库中,那么我们就需要去访问数据库。这里介绍下如何访问数据库

首先要知道基本数据库查询操作,返回Cursor对象

public final Cursor query(Uri uri , //查询路径

String[] projection , //查询指定的列

String selection, //查询条件

String[] selectionArgs, //查询参数

String sortOrder } //查询结果的排序方式

那么就有了我们查询音乐的操作。

//申明ContentResolver对象,用于访问系统数据库privateContentResolvercontentResolver;//用于装载MusicInfo对象privateList<MusicInfo>musicInfos;

//获取系统的ContentResolvercontentResolver= getContentResolver() ;//从数据库中获取指定列的信息mCursor=contentResolver.query(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,newString[] {MediaStore.Audio.Media._ID,MediaStore.Audio.Media.TITLE, MediaStore.Audio.Media.ALBUM,MediaStore.Audio.Media.ARTIST, MediaStore.Audio.Media.DURATION,MediaStore.Audio.Media.DISPLAY_NAME, MediaStore.Audio.Media.SIZE,MediaStore.Audio.Media.DATA, MediaStore.Audio.Media.ALBUM_ID} ,null,null,null) ;

我们已经查询出了所有的音乐信息,并且存储在mCursor对象中,现在我们需要把音乐信息装载到musicInfos工具类集合中。

但是注意,音乐专辑图片需要特殊处理:我们这是只查询出了音乐专辑id MediaStore.Audio.Media.ALBUM_ID我们需要根据音乐专辑id再去查询音乐专辑图片,用该图片来作为音乐图片。

/* 获取本地音乐专辑的图片*/privateString getAlbumArt(intalbum_id){String UriAlbum ="content://media/external/audio/albums";String projecttion[] =newString[] {"album_art"} ;Cursor cursor =contentResolver.query(Uri.parse(UriAlbum + File.separator+Integer.toString(album_id)) ,projecttion ,null,null,null);String album =null;if(cursor.getCount() > 0 && cursor.getColumnCount() > 0){cursor.moveToNext() ;album = cursor.getString(0) ;}//关闭资源数据 cursor.close();returnalbum ;}

这时还可能出现一个问题,可能存在本地专辑音乐图片不存在,所以我们要进行判断,没有专辑图片就使用默认的图片。好了,现在我们就可以装载音乐信息了

musicInfos=newArrayList<>() ;for(inti = 0 ; i <mCursor.getCount() ; i++){Map<String , String> map =newHashMap<>() ;MusicInfo musicInfo =newMusicInfo() ;//列表移动mCursor.moveToNext() ;//将数据装载到List<MusicInfo>中 musicInfo.set_id(mCursor.getInt(0));musicInfo.setTitle(mCursor.getString(1));musicInfo.setAlbum(mCursor.getString(2));musicInfo.setArtist(mCursor.getString(3));musicInfo.setDuration(mCursor.getInt(4));musicInfo.setMusicName(mCursor.getString(5));musicInfo.setSize(mCursor.getInt(6));musicInfo.setData(mCursor.getString(7));//将数据装载到List<Map<String ,String>>中 //获取本地音乐专辑图片 String MusicImage = getAlbumArt(mCursor.getInt(8)) ;//判断本地专辑的图片是否为空if(MusicImage ==null){//为空,用默认图片map.put("image", String.valueOf(R.mipmap.timg)) ;musicInfo.setAlbum_id(String.valueOf(R.mipmap.timg));}else{//不为空,设定专辑图片为音乐显示的图片map.put("image", MusicImage) ;musicInfo.setAlbum_id(MusicImage);}// musicInfo.setAlbum_id(mCursor.getInt(8));musicInfos.add(musicInfo) ;

3 MediaPlayer重要方法:MediaPlayer用来指定音乐文件和播放相关动作,所以我们需要掌握与音乐播放相关的方法。

方法: getCurrentPosition()

解释:返回 Int, 得到当前播放位置 毫秒单位时间

方法: getDuration()

解释:返回 Int,得到文件的时间

方法:isLooping()

解释:返回 boolean ,是否循环播放

方法:isPlaying()

解释:返回 boolean,是否正在播放

方法:pause()

解释:无返回值 ,暂停

方法:release()

解释:无返回值,释放 MediaPlayer 对象

方法:reset()

解释:无返回值,重置 MediaPlayer 对象

方法:seekTo(int msec)

解释:无返回值,指定播放的位置(以毫秒为单位的时间)

方法:setDataSource(String path)

解释:无返回值,设置多媒体数据来源【根据 路径】

方法:setLooping(boolean looping)

解释:无返回值,设置是否循环播放

方法:start()

解释:无返回值,开始播放

方法:stop()

解释:无返回值,停止播放

方法:setVolume(float leftVolume, float rightVolume)

解释:无返回值,设置音量

事件:setOnCompletionListener(MediaPlayer.OnCompletionListener listener)

解释:监听事件,网络流媒体播放结束监听

事件:setOnBufferingUpdateListener(MediaPlayer.OnBufferingUpdateListener listener)

解释:监听事件,网络流媒体的缓冲监听

事件:setOnErrorListener(MediaPlayer.OnErrorListener listener)

解释:监听事件,设置错误信息监听

4 歌词处理 说下思路把:首先根据歌曲去内存寻找对应的歌词文件,找到后解码为歌词内容和歌词时间,并且把歌词内容和个时间装载到歌词信息类集合中。然后提供一个外部访问的方法用于向外部输出歌词信息

// 注:LrcContent是歌词处理类 其中包含两个参数,歌词时间和歌词内容

public class LrcProcess {//所有需要处理的歌词对象private List<LrcContent> lrcList;//一个歌词对象private LrcContent mLrcContent;/*构造函数完成对象的初始化*/public LrcProcess() {lrcList = new ArrayList<>();}/**从内存中读取歌词文件,并转换为String对象输出*/public String readLrc(String path){//用StringBuild来存储歌词内容StringBuilder sb = new StringBuilder() ;//获取歌词文件 因为传入的文件为MusicInfo类中的Data内容,其文件为mp3,需要更换为lrc文件File f = new File(path.replace(".mp3" , ".lrc")) ;try{//通过文件流对象来获取文件内容并且导入歌词内容对象集合中(lrcList)FileInputStream inputStream = new FileInputStream(f) ;InputStreamReader streamReader = new InputStreamReader(inputStream , "utf-8") ;BufferedReader bufferedReader = new BufferedReader(streamReader) ;String tempStr = "" ;while((tempStr = bufferedReader.readLine()) != null){//实现字符替换tempStr = tempStr.replace("[" , "") ;tempStr = tempStr.replace("]" , "@") ;//根据@分号对文件分离String[] splitData = tempStr.split("@") ;// System.out.println("THE TEMP STR IS " + splitData[0]) ;if (splitData.length > 1){//新建歌词内容对象mLrcContent = new LrcContent();//设置歌词文本内容mLrcContent.setLrcStr(splitData[1]);//设置歌词时间int lrcTime = timeToStr(splitData[0]) ;mLrcContent.setLrcTime(lrcTime);//添加到列表lrcList.add(mLrcContent);// System.out.println("录入歌词成功") ;}else {// System.out.println("录入歌词失败") ;}}}catch (FileNotFoundException e){sb.append("木有歌词文件,赶紧去下载!...");} catch (UnsupportedEncodingException e) {e.printStackTrace();sb.append("木有读取到歌词哦!");} catch (IOException e) {e.printStackTrace();sb.append("木有读取到歌词哦!");}return sb.toString() ;}/** 对歌词文件lrc中的时间内容进行转码* [00:02.32]陈奕迅 时间分别代表分,秒,毫秒* [00:03.43]好久不见*/public int timeToStr(String timeStr){timeStr = timeStr.replace(":" , ".") ;timeStr = timeStr.replace("." , "@") ;String []splitTime = timeStr.split("@") ;//分离出分, 秒, 毫秒 int minute = Integer.parseInt(splitTime[0]) ;int second = Integer.parseInt(splitTime[1]) ;int millisSecond = Integer.parseInt(splitTime[2]) ;int time = (minute * 60 + second) * 1000 + millisSecond ;return time ;}/*提供一个外界方法歌词对象集合的方法*/public List<LrcContent> getLrcList(){return lrcList ;}}

//实例化歌词处理对象 Activity中调用方法 //musicInfosList为封装好的所有音乐信息类,get(position)可以获取当前音乐信息,getData获取音乐路径LrcProcess mLrcProcess = new LrcProcess() ;mLrcProcess.readLrc(musicInfosList.get(position).getData()) ;

这里再补一个内容:通常经常下载的歌曲都是没有歌词文件的,所以我们需要自己去下载歌词文件,注意该程序中你需要把歌词文件和音乐文件放在一个地方,并且歌词文件和音乐文件前缀相同只是将.mp3改为了.lrc。我用的是网易云音乐,这里顺便推荐一个网易云下载歌词的网址,大神们各种推荐供你选择

知乎:网易云音乐怎么下载歌词?

5 歌曲播放界面

看图分析:

(1)标题动画

这两个TextView的布局代码就不再列出了。动画效果主要是继承了Animation类自定实现。

public classTextAnimationextendsAnimation {private floatcurrentX; //指定Xprivate floatcurrentY; //指定Y //定义持续时间private intduration;//设定CameraprivateCameracamera=newCamera() ;publicTextAnimation(floatx ,floaty ,intduration){currentX= x ;currentY= y ;this.duration= duration ;System.out.println("THE X IS "+currentX+"\nTHE Y IS "+currentY) ;}@Overridepublic voidinitialize(intwidth,intheight,intparentWidth,intparentHeight) {super.initialize(width, height, parentWidth, parentHeight);//设置持续时间setDuration(duration);//设置动画结束后保留setFillAfter(true);//设置变换速度setInterpolator(newAccelerateDecelerateInterpolator()); //减速 //setInterpolator(new AccelerateInterpolator()); //加速 默认情况 //setInterpolator(new LinearInterpolator());//匀速 }@Overrideprotected voidapplyTransformation(floatinterpolatedTime, Transformation t) {super.applyTransformation(interpolatedTime, t);/* 保存 */camera.save();//根据interpolatedTime来控制X Y Z上的偏移camera.translate(10.0f - 10.0f * interpolatedTime ,30.0f - 30.0f * interpolatedTime ,80.0f - 80.0f * interpolatedTime);//根据interpolatedTime在X轴做角度变换camera.rotateX(360 * interpolatedTime);//根据interpolatedTime在Y轴做角度变换camera.rotateY(360 * interpolatedTime);//获取Transformation参数封装的matrix对象camera.getMatrix(t.getMatrix());t.getMatrix().preTranslate(-currentX/ 4 , -currentY/ 4) ;t.getMatrix().postTranslate(currentX,currentY) ;/* 如果存在保存的状态,就恢复 */camera.restore();}}

initialize(intwidth,intheight,intparentWidth,intparentHeight)中,width和height代表指定播放动画的View空间宽高,parentWidth和parentHeight代表该View控件所在的父控件宽高。可以在该方法中获取View的宽和高,但是我在这里面主要是完成的初始化

applyTransformation()方法是动画具体的实现方法,在系统绘制动画时会反复调用这个方法,每调用一次applyTransformation()方法,其中的interpolatedTime参数都会改变一次,值从0到1递增,当interpolatedTime的值为1时则动画结束。Transformatio类是一个变换的矩阵,通过改变该矩阵就可以实现各种复杂的效果。

PS:更多的相关的动画知识可以阅读官方文档,或者其它博文,书籍,网络视频获取。

Activity调用该动画方法:

/*获取并且设置音乐的标题和歌手。并添加动画来显示*/

MusicArtist= (TextView) findViewById(R.id.MusicArtist) ;MusicName= (TextView) findViewById(R.id.MusicName) ;MusicName.setText(musicInfosList.get(position).getTitle());MusicArtist.setText(musicInfosList.get(position).getArtist());//动画效果MusicArtist.setAnimation(newTextAnimation(0 , 0 , 2000));MusicName.setAnimation(newTextAnimation(0 , 0 , 2000));

(2)音乐专辑图片处理

1,圆形图片

需要用到Shader

Shader的使用步骤:

1.构建Shader对象

2.通过Paint的setShader方法设置渲染对象

3.设置渲染对象

4.绘制时使用这个Paint对象

public classXCRoundImageViewextendsandroid.support.v7.widget.AppCompatImageView{privatePaintmPaintBitmap=newPaint(Paint.ANTI_ALIAS_FLAG);privateBitmapmRawBitmap;privateBitmapShadermShader;privateMatrixmMatrix=newMatrix();publicXCRoundImageView(Context context, AttributeSet attrs) {super(context, attrs);}@Overrideprotected voidonDraw(Canvas canvas) {//获取原生Bitmap位图Bitmap rawBitmap = getBitmap(getDrawable());if(rawBitmap !=null){//获取图片宽和高intviewWidth = getWidth();intviewHeight = getHeight();//由于要变换为圆形,在变换过程中取边长相对小的为基准intviewMinSize = Math.min(viewWidth, viewHeight);floatdstWidth = viewMinSize;floatdstHeight = viewMinSize;//如果是第一次绘制if(mShader==null|| !rawBitmap.equals(mRawBitmap)){mRawBitmap= rawBitmap;/*BitmapShader是Shader的子类,可以通过Paint.setShader(Shader shader)进行设置、这里我们只关注BitmapShader,构造方法:mBitmapShader = new BitmapShader(bitmap, TileMode.CLAMP, TileMode.CLAMP);参数1:bitmap参数2,参数3:TileMode;TileMode的取值有三种:CLAMP 拉伸REPEAT 重复MIRROR 镜像如果大家给电脑屏幕设置屏保的时候,如果图片太小,可以选择重复、拉伸、镜像;重复:就是横向、纵向不断重复这个bitmap镜像:横向不断翻转重复,纵向不断翻转重复;拉伸:这个和电脑屏保的模式应该有些不同,这个拉伸的是图片最后的那一个像素;横向的最后一个横行像素,不断的重复,纵项的那一列像素,不断的重复;*/mShader=newBitmapShader(mRawBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);}if(mShader!=null){/*void setLocalMatrix(Matrix localM);设置shader的本地矩阵,如果localM为空将重置shader的本地矩阵。*/mMatrix.setScale(dstWidth / rawBitmap.getWidth(), dstHeight / rawBitmap.getHeight());mShader.setLocalMatrix(mMatrix);}mPaintBitmap.setShader(mShader);floatradius = viewMinSize / 2.0f;canvas.drawCircle(radius, radius, radius,mPaintBitmap);}else{super.onDraw(canvas);}}privateBitmap getBitmap(Drawable drawable){if(drawableinstanceofBitmapDrawable){return((BitmapDrawable)drawable).getBitmap();}else if(drawableinstanceofColorDrawable){Rect rect = drawable.getBounds();intwidth = rect.right- rect.left;intheight = rect.bottom- rect.top;intcolor = ((ColorDrawable)drawable).getColor();Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);Canvas canvas =newCanvas(bitmap);canvas.drawARGB(Color.alpha(color), Color.red(color), Color.green(color), Color.blue(color));returnbitmap;}else{return null;}}}

PS:作者当时也是搬运的,只是补充了部分注释。内容可能有点复杂,需要多实践,建议参考资料文档

自定义控件之 圆形 / 圆角 ImageView

自定义控件三部曲之绘图篇(十八)——BitmapShader与望远镜效果2,图片旋转

首先定义旋转动画 :image_rotate.xml

<?xml version="1.0" encoding="utf-8"?><set xmlns:android="/apk/res/android"android:interpolator="@android:anim/linear_interpolator"><rotateandroid:fromDegrees="0"android:toDegrees="359"android:pivotX="50%"android:pivotY="50%"android:duration="8000"/></set>

然后在Java业务代码中设定定时器来启动动画

/*该Timer用于实现:音乐播放界面图片旋转动画*/MyHandle2 handle2=newMyHandle2() ;newTimer().schedule(newTimerTask() {@Overridepublic voidrun() {handle2.sendEmptyMessage(0x112) ;}}, 0 ,8000);

最后再Handle中接收消息,并且启动动画

public classMyHandle2extendsHandler{@Overridepublic voidhandleMessage(Message msg) {if((msg.what== 0x112)){//设置图片旋转MusicImage.setAnimation(AnimationUtils.loadAnimation(MusicPlay.this, R.anim.image_rotate));}}}

五:下载地址github

/547291213/MusicPlayer

六:参考资料

Android音乐播放器(一):搜索手机存储的音乐Android6.0运行时权限管理最佳实践android完美获取音乐文件中的专辑图片并显示Android开发笔记(一百二十六)自定义音乐播放器Android应用开发--MP3音乐播放器滚动歌词实现Android实现音乐示波器、均衡器、重低音和音场功能自定义控件之圆形/圆角ImageView【Android】Service前台服务的使用官方开发文档

七:总结

第一次写这么长文的博客,也没想过邀功。不过真心希望您再阅读了这篇博文,并且有所建议和收获后留下您宝贵的评论。谢谢。

PS:转载请注明 /qq_29989087/article/details/80206290

如果觉得《Android 自定义音乐播放器实现》对你有帮助,请点赞、收藏,并留下你的观点哦!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。