前一篇已经将音乐播放及切换的相关逻辑弄好了,今天主要理一下剩余的部分,包含:

1. 自己定义通知栏的布局及逻辑处理

2. 滚动歌词的绘制

3. 歌词解析

效果图

通知栏

  1. 自己定义布局:

    1. <?xml version="1.0" encoding="utf-8"?>
    2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    3. android:layout_width="match_parent"
    4. android:layout_height="wrap_content"
    5. android:orientation="horizontal"
    6. android:id="@+id/layout_notification"
    7. android:padding="10dp" >
    8. <ImageView
    9. android:layout_width="40dp"
    10. android:layout_height="40dp"
    11. android:background="@mipmap/ic_launcher" />
    12. <LinearLayout
    13. android:layout_width="0dp"
    14. android:layout_height="40dp"
    15. android:layout_weight="1"
    16. android:layout_marginLeft="10dp"
    17. android:orientation="vertical" >
    18. <TextView
    19. android:layout_width="wrap_content"
    20. android:layout_height="wrap_content"
    21. android:singleLine="true"
    22. android:text="标题"
    23. android:id="@+id/tv_notification_title"
    24. android:textColor="@color/white"
    25. android:textSize="17sp" />
    26. <TextView
    27. android:layout_width="wrap_content"
    28. android:layout_height="wrap_content"
    29. android:singleLine="true"
    30. android:text="艺术家"
    31. android:id="@+id/tv_notification_content"
    32. android:textColor="@color/gray_white"
    33. android:textSize="14sp" />
    34. </LinearLayout>
    35. <LinearLayout android:layout_width="0dp"
    36. android:layout_weight="1"
    37. android:gravity="center"
    38. android:orientation="horizontal"
    39. android:layout_height="40dp">
    40. <ImageView android:layout_width="30dp"
    41. android:layout_height="30dp"
    42. android:layout_marginRight="20dp"
    43. android:id="@+id/btn_notification_pre"
    44. android:background="@mipmap/icon_notification_pre"/>
    45. <ImageView android:layout_width="30dp"
    46. android:layout_height="30dp"
    47. android:id="@+id/btn_notification_next"
    48. android:background="@mipmap/icon_notification_next"/>
    49. </LinearLayout>
    50. </LinearLayout>
  2. 通知栏的相关逻辑:

    1. 下一首
    2. 上一首
    3. 进入播放页
    1. /**
    2. * 发送自己定义布局的通知
    3. */
    4. private void sendNotification() {
    5. Notification.Builder builder = new Notification.Builder(AudioPlayerService.this);
    6. builder.setOngoing(true)
    7. .setSmallIcon(R.mipmap.notification_music_playing)
    8. .setTicker("正在播放:" + StringUtil.formatAudioName(audioList.get(currentPosition).getTitle()))
    9. .setWhen(System.currentTimeMillis())
    10. .setContent(getRemoteViews());
    11. startForeground(1, builder.build());
    12. }
    13. private RemoteViews getRemoteViews() {
    14. RemoteViews remoteViews = new RemoteViews(getPackageName(),
    15. R.layout.layout_music_notification);
    16. remoteViews.setTextViewText(R.id.tv_notification_title, StringUtil.formatAudioName(audioList.get(currentPosition).getTitle()));
    17. remoteViews.setTextViewText(R.id.tv_notification_content, audioList.get(currentPosition).getArtist());
    18. remoteViews.setOnClickPendingIntent(R.id.btn_notification_pre, getPrePendingIntent());
    19. remoteViews.setOnClickPendingIntent(R.id.btn_notification_next, getNextPendingIntent());
    20. remoteViews.setOnClickPendingIntent(R.id.layout_notification, getContentPendingIntent());
    21. return remoteViews;
    22. }
    23. private PendingIntent getPrePendingIntent() {
    24. Intent intent = new Intent(AudioPlayerService.this, AudioPlayerService.class);
    25. intent.putExtra("viewAction", ACTION_NOTIFICATION_PRE);
    26. intent.putExtra("isFromNotification", true);
    27. PendingIntent pendingIntent = PendingIntent.getService(AudioPlayerService.this, 1, intent, PendingIntent.FLAG_UPDATE_CURRENT);
    28. return pendingIntent;
    29. }
    30. private PendingIntent getNextPendingIntent() {
    31. Intent intent = new Intent(AudioPlayerService.this, AudioPlayerService.class);
    32. intent.putExtra("viewAction", ACTION_NOTIFICATION_NEXT);
    33. intent.putExtra("isFromNotification", true);
    34. PendingIntent pendingIntent = PendingIntent.getService(AudioPlayerService.this, 2, intent, PendingIntent.FLAG_UPDATE_CURRENT);
    35. return pendingIntent;
    36. }
    37. private PendingIntent getContentPendingIntent() {
    38. Intent intent = new Intent(AudioPlayerService.this, AudioPlayerActivity.class);
    39. Bundle bundle = new Bundle();
    40. bundle.putInt("viewAction", ACTION_NOTIFICATION_LAYOUT);
    41. bundle.putBoolean("isFromNotification", true);
    42. intent.putExtras(bundle);
    43. PendingIntent pendingIntent = PendingIntent.getActivity(AudioPlayerService.this, 3, intent, PendingIntent.FLAG_UPDATE_CURRENT);
    44. return pendingIntent;
    45. }
    1. /*发送通知的方法应该在音乐准备完毕和開始播放的时候调用*/
    2. private OnPreparedListener onPreparedListener = new OnPreparedListener() {
    3. @Override
    4. public void onPrepared(MediaPlayer mp) {
    5. mediaPlayer.start();
    6. notifyPrepared();
    7. sendNotification();
    8. }
    9. };
    10. public void start() {
    11. if (mediaPlayer != null) {
    12. mediaPlayer.start();
    13. }
    14. sendNotification();
    15. }
    16. //音乐准备暂停时移除通知
    17. public void pause() {
    18. if (mediaPlayer != null) {
    19. mediaPlayer.pause();
    20. }
    21. stopForeground(true);//移除通知
    22. }

歌词绘制

思路:自己定义LyricView继承TextView。覆盖onSizeChanged(),onDraw()方法。

  1. 绘制一行居中文本

    1. /**
    2. * 绘制水平居中的歌词文本
    3. *
    4. * @param canvas 画布
    5. * @param text 文本
    6. * @param y 竖直方向的y坐标
    7. * @param isLight 是否高亮
    8. */
    9. private void drawCenterHorizontalText(Canvas canvas, String text, float y, boolean isLight) {
    10. paint.setColor(isLight ?
    11. LYRCI_HIGHLIGHT_COLOR : LYRIC_DEFAULT_COLOR);
    12. paint.setTextSize(isLight ?
    13. getResources().getDimension(R.dimen.lyric_highlight_textsize)
    14. : getResources().getDimension(R.dimen.lyric_default_textsize));
    15. float x = width / 2 - paint.measureText(text) / 2;
    16. canvas.drawText(text, x, y, paint);
    17. }
  2. 绘制多行歌词

    1. /**
    2. * 绘制全部的歌词
    3. *
    4. * @param canvas 画布
    5. */
    6. private void drawLyricList(Canvas canvas) {
    7. Lyric lightLyric = lyricList.get(lightLyricIndex);
    8. //1.首先将高亮行的歌词绘制出来,作为一个參照物
    9. float lightLyricY = height / 2 + getTextHeight(lightLyric.getContent()) / 2;
    10. drawCenterHorizontalText(canvas, lightLyric.getContent(), lightLyricY, true);
    11. //2.遍历高亮行之前的歌词,并绘制出来
    12. for (int pre = 0; pre < lightLyricIndex; pre++) {
    13. Lyric lyric = lyricList.get(pre);
    14. float y = lightLyricY - (lightLyricIndex - pre) * LYRIC_ROW_HEIGHT;
    15. drawCenterHorizontalText(canvas, lyric.getContent(), y, false);
    16. }
    17. //3.遍历高亮行之后的歌词,并绘制出来
    18. for (int next = lightLyricIndex + 1; next < lyricList.size(); next++) {
    19. Lyric lyric = lyricList.get(next);
    20. float y = lightLyricY + (next - lightLyricIndex) * LYRIC_ROW_HEIGHT;
    21. drawCenterHorizontalText(canvas, lyric.getContent(), y, false);
    22. }
    23. }
    1. /**
    2. * 获取文本的高度
    3. *
    4. * @param text 文本
    5. * @return 文本的高度
    6. */
    7. private float getTextHeight(String text) {
    8. Rect bounds = new Rect();
    9. paint.getTextBounds(text, 0, text.length(), bounds);
    10. return bounds.height();
    11. }
  3. 滚动歌词

    1. /**
    2. * 滚动歌词
    3. */
    4. public void roll(long currentPosition,long audioDuration){
    5. this.currentPosition = currentPosition;
    6. this.audioDuration = audioDuration;
    7. //1. 依据歌词播放的position。计算出高亮行的索引lightLyricIndex
    8. if(lyricList.size() != 0){
    9. //1.依据当前歌曲播放的位置去计算lightLyricIndex
    10. caculateLightLyricIndex();
    11. }
    12. //2. 拿到新的lightLyricIndex之后,更新view
    13. invalidate();
    14. }
    1. /**
    2. * 计算高亮歌词的索引值
    3. * 仅仅要当前音乐的position大于当前行的startPoint。
    4. * 而且小于下一行的startPoint,就是高亮行
    5. */
    6. private void caculateLightLyricIndex() {
    7. for (int i = 0; i < lyricList.size(); i++) {
    8. long startPoint = lyricList.get(i).getStartPoint();
    9. if(i == lyricList.size() - 1){//最后一行
    10. if(currentPosition > startPoint){
    11. lightLyricIndex = i;
    12. }
    13. }else{//不是最后一行
    14. Lyric next = lyricList.get(i + 1);
    15. if(currentPosition > startPoint && currentPosition < next.getStartPoint()){
    16. lightLyricIndex = i;
    17. }
    18. }
    19. }
    20. }
  4. 平滑滚动歌词

    1. /**
    2. * 绘制全部的歌词
    3. *
    4. * @param canvas 画布
    5. */
    6. private void drawLyricList(Canvas canvas) {
    7. Lyric lightLyric = lyricList.get(lightLyricIndex);
    8. //平滑移动歌词
    9. //1. 算出歌词的总的播放时间 即 下一行的startPoint - 当前的startPoint
    10. int totalDuration;
    11. if(lightLyricIndex==(lyricList.size()-1)){
    12. //假设最后一行是高亮行,则拿歌曲总时间减去当前的startPoint
    13. totalDuration = (int) (audioDuration - lightLyric.getStartPoint());
    14. }else {
    15. totalDuration = (int) (lyricList.get(lightLyricIndex+1).getStartPoint()-lightLyric.getStartPoint());
    16. }
    17. //2. 算出当前已经播放的秒数占总时间的百分比 currentAudioPosition - startPoint
    18. float offsetPosition = (int) (currentPosition - lightLyric.getStartPoint());
    19. float percent = offsetPosition/totalDuration;
    20. //3. 依据百分比算出应该移动的距离 percent * LYRIC_ROW_HEIGHT
    21. float dy = LYRIC_ROW_HEIGHT * percent;
    22. canvas.translate(0, -dy);
    23. //1.首先将高亮行的歌词绘制出来,作为一个參照物
    24. float lightLyricY = height / 2 + getTextHeight(lightLyric.getContent()) / 2;
    25. drawCenterHorizontalText(canvas, lightLyric.getContent(), lightLyricY, true);
    26. //2.遍历高亮行之前的歌词。并绘制出来
    27. for (int pre = 0; pre < lightLyricIndex; pre++) {
    28. Lyric lyric = lyricList.get(pre);
    29. float y = lightLyricY - (lightLyricIndex - pre) * LYRIC_ROW_HEIGHT;
    30. drawCenterHorizontalText(canvas, lyric.getContent(), y, false);
    31. }
    32. //3.遍历高亮行之后的歌词,并绘制出来
    33. for (int next = lightLyricIndex + 1; next < lyricList.size(); next++) {
    34. Lyric lyric = lyricList.get(next);
    35. float y = lightLyricY + (next - lightLyricIndex) * LYRIC_ROW_HEIGHT;
    36. drawCenterHorizontalText(canvas, lyric.getContent(), y, false);
    37. }
    38. }
  5. 提供设置歌词的方法

    1. public void setLyricList(ArrayList<Lyric> lyricList){
    2. this.lyricList = lyricList;
    3. if(this.lyricList==null){
    4. hasNoLyric = true;
    5. }
    6. }

歌词解析

  1. 读取每一行歌词文本
  2. 解析每一行歌词
  3. 对歌词集合进行排序

    1. /**
    2. * 歌词解析的工具类
    3. */
    4. public class LyricParser {
    5. public static ArrayList<Lyric> parseLyricFromFile(File lyricFile){
    6. if(lyricFile==null || !lyricFile.exists())return null;
    7. ArrayList<Lyric> list = new ArrayList<Lyric>();
    8. try {
    9. //1.读取每一行歌词文本
    10. BufferedReader reader = new BufferedReader(new InputStreamReader
    11. (new FileInputStream(lyricFile),"utf-8"));
    12. String line;
    13. while((line=reader.readLine())!=null){
    14. //2.解析每一行歌词
    15. //[00:04.05][00:24.05][01:24.05]北京北京 -> split("\\]")
    16. //[00:04.05 [00:24.05 [01:24.05 北京北京
    17. String[] arr = line.split("\\]");
    18. for (int i = 0; i < arr.length-1; i++) {
    19. Lyric lyric = new Lyric();
    20. lyric.setContent(arr[arr.length-1]);//设置歌词内容
    21. lyric.setStartPoint(formatStartPoint(arr[i]));
    22. list.add(lyric);
    23. }
    24. }
    25. //3.对歌词集合进行排序
    26. Collections.sort(list);//从小到大
    27. } catch (Exception e) {
    28. e.printStackTrace();
    29. }
    30. return list;
    31. }
    32. /**
    33. * 将[00:04.05转long类型的时间
    34. * @param str
    35. * @return
    36. */
    37. private static long formatStartPoint(String str){
    38. str = str.substring(1);//00:04.05
    39. //1.先以冒号切割
    40. String[] arr1 = str.split("~i");//00 04.05
    41. String[] arr2 = arr1[1].split("\\.");//04 05
    42. int minute = Integer.parseInt(arr1[0]);//得到多少分钟
    43. int second = Integer.parseInt(arr2[0]);//得到多少秒
    44. int mills = Integer.parseInt(arr2[1]);//得到多少10毫秒
    45. return mills*10 + second*1000 + minute*60*1000;
    46. }
    47. }
    1. /**模拟歌词载入模块
    2. * TODO:拿歌曲id去server请求相应的歌词文件
    3. */
    4. public class LyricLoader {
    5. // private static final String LYRIC_DIR = Environment.getExternalStorageDirectory()+"/MIUI/music/lyric";
    6. private static final String LYRIC_DIR = Environment.getExternalStorageDirectory()+"/test/audio";
    7. public static File loadLyricFileByName(String audioName){
    8. File file = new File(LYRIC_DIR,StringUtil.formatAudioName(audioName)+".lrc");
    9. LogUtils.i(LYRIC_DIR);
    10. if(!file.exists()){
    11. file = new File(LYRIC_DIR,StringUtil.formatAudioName(audioName)+".txt");
    12. }
    13. return file;
    14. }
    15. }

好了。手机影音项目的整理就到这里。

Android 手机影音 开发过程记录(六)的更多相关文章

  1. 记录android学习、开发过程温故知新

    记录android学习.开发过程温故知新

  2. GPS部标平台的架构设计(六)-Android手机客户端和手机查车设计

    对于GPS软件平台,虽然有功能非常丰富的PC端或BS客户端,但是客户也是需要移动客户端来作为自己的辅助工具,也是需要的.做为GPS平台的设计者和开发者,在开发移动客户端的时候,也需要从常规的服务器开发 ...

  3. 三星 S4 手机误删除相片(相册)后的恢复问题,仅记录处理过程,其它Android手机同样适用

    无意中删除了三星S4手机中相机的相册.过程是这样的,用手机拍了几张照片,觉得最后那张拍得不好,想删除,于是进入相册,看到有那张照片的图标,选择,删除,悲剧发生了! 这里得说三星的不好:在相册中,相册文 ...

  4. 如何在 Android 手机上实现抓包?

    如何在 Android 手机上实现抓包? http://www.zhihu.com/question/20467503 我想知道某个应用究竟在数据提交到哪里,提交了什么.网上的教程太复杂,不想麻烦.有 ...

  5. 【朝花夕拾】Android性能篇之(六)Android进程管理机制

    前言        Android系统与其他操作系统有个很不一样的地方,就是其他操作系统尽可能移除不再活动的进程,从而尽可能保证多的内存空间,而Android系统却是反其道而行之,尽可能保留进程.An ...

  6. 为什么Android手机总是越用越慢?

    根据第三方的调研数据显示,有77%的Android手机用户承认自己曾遭遇过手机变慢的影响,百度搜索“Android+卡慢”,也有超过460万条结果.在业内,Android手机一直有着“越用越慢”的口碑 ...

  7. 为啥Android手机总会越用越慢?

    转自:http://www.androidchina.net/818.html 根据第三方的调研数据显示,有77%的Android手机用户承认自己曾遭遇过手机变慢的影响,百度搜索“Android+卡慢 ...

  8. ubuntu下USB连接Android手机

    初始工作:将Android手机通过usb连接到电脑,之后点击VM-Removable Devices-google Android - Connect,即可. 若通过usb连接到电脑,Removabl ...

  9. Android应用基础学习记录

    01_前言 前言,了解了Android的情况.这里也介绍一下本文.本文是记录学习Android应用程序开发过程,视频中使用的Android2.2版本号,我以4.2版本号为基础,找出当中的差异并记录下来 ...

随机推荐

  1. 通过rpm安装jdk

    通过rpm安装,安装在/usr/local 1 .编辑系统环境变量 vi /etc/profile 输入i 加入内容如下: export JAVA_HOME=/usr/local/jdk1.7.0_7 ...

  2. 【 【henuacm2016级暑期训练】动态规划专题 G】 Palindrome pairs

    [链接] 我是链接,点我呀:) [题意] 在这里输入题意 [题解] 先用枚举回文串中点的方法. 得到这个字符串中出现的所有的回文. 得到他们的左端点以及右端点. 整理成一个pair<int,in ...

  3. Docker学习总结(13)——从零开始搭建Jenkins+Docker自动化集成环境

    本文只简单标记下大概的步骤,具体搭建各个部分的细节,还请自行搜索.第一.二部分只是对Jenkins和Docker的简单介绍,熟悉的同学请直接跳到第三部分. 一.关于Jenkins Jenkins简介 ...

  4. mysql5.7官网直译SQL语句优化--select语句优化

    8.2 sql语句优化 大致内容如下: 8.2.1:SELECT语句的优化 8.2.2:优化子查询,派生表和试图引用 8.2.3:优化INFORMATION_SCHEMA查询 8.2.4:优化数据改变 ...

  5. ASP.NET-技巧01

    ==符号的写法 ViewBag.StatusMessage = message == ManageMessageId.ChangePasswordSuccess ? "你的密码已更改.&qu ...

  6. BTrace介绍和生产环境样例

    BTrace latest realese: release-1.2.5.1 BTrace guide(1.2-20101020): http://kenai.com/projects/btrace/ ...

  7. POJ 2906 数学期望

    开始时直接设了一个状态,dp[i][j]为发现i种bug,j个系统有bug的期望天数.但很错误,没能转移下去.... 看了题解,设状态dp[i][j]为已发现i种bug,j个系统有bug,到完成目标状 ...

  8. scikit-learn:3.5. Validation curves: plotting scores to evaluate models

    參考:http://scikit-learn.org/stable/modules/learning_curve.html estimator's generalization error can b ...

  9. C#高级编程五十八天----并行集合

    并行集合 对于并行任务,与其相关紧密的就是对一些共享资源,数据结构的并行訪问.常常要做的就是对一些队列进行加锁-解锁,然后运行类似插入,删除等等相互排斥操作. .NET4提供了一些封装好的支持并行操作 ...

  10. 如何做到Ubuntu14.04下的mongdb远程访问?(图文详解)

    不多说,直接上干货! 本教程详细指导大家如何开启并设置用户权限.MongoDB默认是没有开启用户权限的,如果直接在公网服务器上如此搭建MongoDB,那么所有人都可以直接访问并修改数据库数据了. 其实 ...