Android 音乐APP(三)播放音乐、自定义进度条、自动下一曲 您所在的位置:网站首页 安卓进度条app Android 音乐APP(三)播放音乐、自定义进度条、自动下一曲

Android 音乐APP(三)播放音乐、自定义进度条、自动下一曲

2023-08-14 05:16| 来源: 网络整理| 查看: 265

Android 音乐APP 播放音乐前言正文① 修改布局② 初始化数据③ 播放音乐④ 暂停音乐⑤ 自动下一曲⑥ 播放进度⑦ 旋转动画⑧ 运行效果图结语

前言

作为音乐APP的主要功能,我放到了第三篇文章,因为播放音乐的功能并没有看上去那么简单,里面有很多细节是在写代码的时候就要考虑,并且加入到逻辑里面的,这可不是危言耸听,下面来看是怎样一个不简单吧。

正文

既然要做播放音乐的功能自然要好好的设计一下UI了,不然太难看我可拿不出手,于是我参考了QQ和网易的列表播放页面,合二为一就产生了下面这个页面

20201021175108116.png

从这张图可以看出什么呢?首先播放布局不随页面滚动,一直固定在屏幕的底部,其次是播放的进度是左边的这个logo中,而这个logo在播放的时候自动旋转,logo右边的是歌曲信息,当内容超过控件时会有跑马灯效果,最右边自然就是控制歌曲的播放和暂停了。你可能会问上一曲、下一曲呢?这个嘛,我打算放在下一篇文章再来说明,步步为营,循序渐进。

① 修改布局

首先要修改这个布局先达到图中的效果。

20201021180915623.png

下面我附上现在的布局代码。如果你之前是跟着来写的话,那么这里你可以对照着布局,再增加上去,不过你可能会漏掉一些细节,所以建议你先看懂布局,然后复制粘贴,万无一失。activity_local_music.xml如下:

里面用到三个图标一个自定义View。图标你可以去我的源码里面拿,源码图标,自定义View我会写出来。在com.llw.goodmusic下新建一个view包,然后新建一个MusicRoundProgressView。

20201021181346348.png

代码如下:

package com.llw.goodmusic.view; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.RectF; import android.util.AttributeSet; import android.view.View; import androidx.annotation.Nullable; import com.llw.goodmusic.R; /** * 圆形进度条 * * @author llw */ public class MusicRoundProgressView extends View { /** * 画笔 */ private Paint mPaint; /** * 画笔颜色 */ private int mPaintColor; /** * 半径 */ private float mRadius; /** * 圆环半径 */ private float mRingRadius; /** * 圆环宽度 */ private float mStrokeWidth; /** * 圆心 X 轴坐标 */ private int mCenterX; /** * 圆心 Y 轴坐标 */ private int mCenterY; /** * 总进度 */ private int mTotalProgress; /** * 当前进度 */ private int mProgress; public MusicRoundProgressView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.RoundProgressView); //半径 mRadius = typedArray.getDimension(R.styleable.RoundProgressView_radius, 40); //宽度 mStrokeWidth = typedArray.getDimension(R.styleable.RoundProgressView_strokeWidth, 5); //颜色 mPaintColor = typedArray.getColor(R.styleable.RoundProgressView_strokeColor, 0xFFFFFFFF); //圆环半径 = 半径 + 圆环宽度的1/2 mRingRadius = mRadius + mStrokeWidth / 2; //画笔 mPaint = new Paint(); mPaint.setAntiAlias(true); mPaint.setColor(mPaintColor); mPaint.setStyle(Paint.Style.STROKE); mPaint.setStrokeWidth(mStrokeWidth); } @Override protected void onDraw(Canvas canvas) { mCenterX = getWidth() / 2; mCenterY = getHeight() / 2; if (mProgress > 0) { RectF rectF = new RectF(); rectF.left = (mCenterX - mRingRadius); rectF.top = (mCenterY - mRingRadius); rectF.right = mRingRadius * 2 + (mCenterX - mRingRadius); rectF.bottom = mRingRadius * 2 + (mCenterY - mRingRadius); canvas.drawArc(rectF, -90, ((float) mProgress / mTotalProgress) * 360, false, mPaint); } } /** * 设置进度 */ public void setProgress(int progress, int totalProgress) { mProgress = progress; mTotalProgress = totalProgress; //重绘 postInvalidate(); } }

里面涉及到的样式如下:

20201021181557544.png

在styles.xml中增加如下代码:

现在你的自定义从理论上来说就不会报错了。当然可能需要改一下包名之类,下面就是回到LocalMusicActivty。

② 初始化数据

首先在当前定位按钮后面加上这些变量

/** * 底部logo图标,点击之后弹出当前播放歌曲详情页 */ private ShapeableImageView ivLogo; /** * 底部当前播放歌名 */ private MaterialTextView tvSongName; /** * 底部当前歌曲控制按钮, 播放和暂停 */ private MaterialButton btnPlay; /** * 音频播放器 */ private MediaPlayer mediaPlayer; /** * 记录当前播放歌曲的位置 */ public int mCurrentPosition = -1; /** * 自定义进度条 */ private MusicRoundProgressView musicProgress; /** * 音乐进度间隔时间 */ private static final int INTERNAL_TIME = 1000; /** * 图片动画 */ private ObjectAnimator logoAnimation;

每一个都有注释,然后绑定相关的控件

20201022151910733.png

同样点击事件必不可少

20201022152744291.png

③ 播放音乐

常规的操作是通过点击音乐列表中的某一首歌之后播放歌曲。还记得列表的点击事件在哪里吗?当然是在**showLocalMusicData()**方法里面,之前在这个方法中设置适配器和列表的一些相关属性和数据,当然还有点击事件。

回顾一下这个代码:

//item的点击事件 mAdapter.setOnItemChildClickListener(new BaseQuickAdapter.OnItemChildClickListener() { @Override public void onItemChildClick(BaseQuickAdapter adapter, View view, int position) { if (view.getId() == R.id.item_music) { if (oldPosition == -1) { //未点击过 第一次点击 oldPosition = position; mList.get(position).setCheck(true); } else { //大于 1次 if (oldPosition != position) { mList.get(oldPosition).setCheck(false); mList.get(position).setCheck(true); //重新设置位置,当下一次点击时position又会和oldPosition不一样 oldPosition = position; } } mAdapter.changeState(); } } });

我不想这里面的代码太多,所以我新写了一个方法。

/** * 控制播放位置 * * @param position */ private void playPositionControl(int position) { if (oldPosition == -1) { //未点击过 第一次点击 oldPosition = position; mList.get(position).setCheck(true); } else { //大于 1次 if (oldPosition != position) { mList.get(oldPosition).setCheck(false); mList.get(position).setCheck(true); //重新设置位置,当下一次点击时position又会和oldPosition不一样 oldPosition = position; } } mAdapter.changeState(); }

当点击这个item时将position传递给全局变量mCurrentPosition。

20201022153945210.png

然后通过changeSong(mCurrentPosition);方法来播放歌曲

/** * 切换歌曲 */ private void changeSong(int position) { if (mediaPlayer == null) { mediaPlayer = new MediaPlayer(); } try { //切歌前先重置,释放掉之前的资源 mediaPlayer.reset(); BLog.i(TAG, mList.get(position).path); //设置播放音频的资源路径 mediaPlayer.setDataSource(mList.get(position).path); //设置播放的歌名和歌手 tvSongName.setText(mList.get(position).song + " - " + mList.get(position).singer); //如果内容超过控件,则启用跑马灯效果 tvSongName.setSelected(true); //开始播放前的准备工作,加载多媒体资源,获取相关信息 mediaPlayer.prepare(); //开始播放音频 mediaPlayer.start(); //播放按钮控制 if (mediaPlayer.isPlaying()) { btnPlay.setIcon(getDrawable(R.mipmap.icon_pause)); btnPlay.setIconTint(getColorStateList(R.color.gold_color)); } else { btnPlay.setIcon(getDrawable(R.mipmap.icon_play)); btnPlay.setIconTint(getColorStateList(R.color.white)); } } catch (IOException e) { e.printStackTrace(); } }

相信代码都能够看懂,播放歌曲之前先实例化,然后重置mediaPlayer,设置相关的信息之后就开始播放,这个时候也要处理一下按钮的状态。那么现在你再列表中就可以随意点击了,点击那一首就播放哪一首。现在的确是有播放音乐了,但是我也需要暂停啊。

④ 暂停音乐

在底部播放按钮btn_play的点击事件中进行处理。

case R.id.btn_play: //控制音乐 播放和暂停 if (mediaPlayer == null) { //没有播放过音乐 ,点击之后播放第一首 oldPosition = 0; mCurrentPosition = 0; mList.get(mCurrentPosition).setCheck(true); mAdapter.changeState(); changeSong(mCurrentPosition); } else { //播放过音乐 暂停或者播放 if (mediaPlayer.isPlaying()) { mediaPlayer.pause(); btnPlay.setIcon(getDrawable(R.mipmap.icon_play)); btnPlay.setIconTint(getColorStateList(R.color.white)); } else { mediaPlayer.start(); btnPlay.setIcon(getDrawable(R.mipmap.icon_pause)); btnPlay.setIconTint(getColorStateList(R.color.gold_color)); } } break;

也要考虑到用户一进入这个页面直接点击底部播放按钮的因素,这样就直接播放列表中的第一首,至于记录当前歌曲的位置和播放进度,下一次进入时继续这个进度,这个功能放到后面来实现,先考虑这个页面的。

⑤ 自动下一曲

说道自动下一曲,就是没有人为干涉的情况下,当前歌曲播放完毕之后自行播放下一首。这里需要实现MediaPlayer的OnCompletionListener,r方法如下:

/** * 播放完成之后自动下一曲 * * @param mp */ @Override public void onCompletion(MediaPlayer mp) { int position = -1; if (mList != null) { if (mCurrentPosition == mList.size() - 1) { //当前为最后一首歌时,则切换到列表的第一首歌 position = mCurrentPosition = 0; } else { position = ++mCurrentPosition; } } //移动播放位置 playPositionControl(position); //切歌 changeSong(position); }

在这个方法里面通过监听到歌曲完毕之后,先判断是否在最后一首歌,是则从第一首歌开始,不是则位置+1,然后移动播放的位置,更新列表数据,之后就通过刚才得到的位置进行切歌。当然这个功能要完成还需要最后一个不走。记得加一个监听才行,如下所示,可以在对MediaPlayer进行实例化的时候设置完成播放时的监听。不加,则你的音乐播放完了就一直在哪里不动。

20201022162334504.png

⑥ 播放进度

播放进度对于用户来说是比较重要的,这里我没有用Seekbar,来让用户看到播放进度并且可以手动拖动,而是用了一个自定义View,只用来显示歌曲当前的播放进度,没有具体的播放时间和操作控件,这样做会让你的页面显得很简洁,同时静中有动,说了这么多,不如写代码来的实际。

private Handler mHandler = new Handler(new Handler.Callback() { @Override public boolean handleMessage(Message message) { // 展示给进度条和当前时间 int progress = mediaPlayer.getCurrentPosition(); musicProgress.setProgress(progress, mediaPlayer.getDuration()); //更新进度 updateProgress(); return true; } });

首先通过Handle来发送消息,定时更新进度。

/** * 更新进度 */ private void updateProgress() { // 使用Handler每间隔1s发送一次空消息,通知进度条更新 // 获取一个现成的消息 Message msg = Message.obtain(); // 使用MediaPlayer获取当前播放时间除以总时间的进度 int progress = mediaPlayer.getCurrentPosition(); msg.arg1 = progress; mHandler.sendMessageDelayed(msg, INTERNAL_TIME); }

发送消息

20201022163423385.png

在changeSong方法中,当开始播放时,设置当前的进度和音乐的总进度,然后通过**updateProgress()**方法来发送消息。在handler中更新进行自定义View的重新位置,这样就可以看到进度增长了。因为不管你是点击列表得item还是点击底部的播放按钮,都会进入changeSong方法中,所以我放在这个里面。

⑦ 旋转动画

在静中增加动,可以给用户更好的体验,所以我想到了图片的自转。通过属性动画来实现。

/** * 初始化动画 */ private void initAnimation() { logoAnimation = ObjectAnimator.ofFloat(ivLogo, "rotation", 0.0f, 360.0f); logoAnimation.setDuration(3000); logoAnimation.setInterpolator(new LinearInterpolator()); logoAnimation.setRepeatCount(-1); logoAnimation.setRepeatMode(ObjectAnimator.RESTART); }

在这个方法里面进行了动画的参数配置,我来解释一下上面的配置做了什么事情,首先是顺时针方向旋转360°。然后旋转一圈耗时3s,使用线性插值器,重复旋转。下面就是用的地方了。

20201022164628476.png

在歌曲播放的时候,开始旋转,可以暂停和继续。同时在底部的播放按钮里面也需要做相应的动画控制。

20201022164819660.png

最后在播放完成监听方法里面重置这个动画

20201022164907681.png

好了,功能就写完了,下面直接运行吧。

⑧ 运行效果图

20201022165505400.gif

看这个图片是不是有那么点意思了呢?

结语

写代码的工程中逻辑很重要,最好是一气呵成,当你的思路被打断,无法集中注意力时,是写不好代码的,而文章则是在代码写好之后再写的,如果有什么问题及时提出来,我会尽快解决。



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有