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

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

2. 滚动歌词的绘制

3. 歌词解析

效果图

通知栏

  1. 自己定义布局:

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal"
    android:id="@+id/layout_notification"
    android:padding="10dp" > <ImageView
    android:layout_width="40dp"
    android:layout_height="40dp"
    android:background="@mipmap/ic_launcher" /> <LinearLayout
    android:layout_width="0dp"
    android:layout_height="40dp"
    android:layout_weight="1"
    android:layout_marginLeft="10dp"
    android:orientation="vertical" > <TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:singleLine="true"
    android:text="标题"
    android:id="@+id/tv_notification_title"
    android:textColor="@color/white"
    android:textSize="17sp" /> <TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:singleLine="true"
    android:text="艺术家"
    android:id="@+id/tv_notification_content"
    android:textColor="@color/gray_white"
    android:textSize="14sp" />
    </LinearLayout> <LinearLayout android:layout_width="0dp"
    android:layout_weight="1"
    android:gravity="center"
    android:orientation="horizontal"
    android:layout_height="40dp"> <ImageView android:layout_width="30dp"
    android:layout_height="30dp"
    android:layout_marginRight="20dp"
    android:id="@+id/btn_notification_pre"
    android:background="@mipmap/icon_notification_pre"/> <ImageView android:layout_width="30dp"
    android:layout_height="30dp"
    android:id="@+id/btn_notification_next"
    android:background="@mipmap/icon_notification_next"/> </LinearLayout> </LinearLayout>
  2. 通知栏的相关逻辑:

    1. 下一首
    2. 上一首
    3. 进入播放页
        /**
    * 发送自己定义布局的通知
    */
    private void sendNotification() {
    Notification.Builder builder = new Notification.Builder(AudioPlayerService.this);
    builder.setOngoing(true)
    .setSmallIcon(R.mipmap.notification_music_playing)
    .setTicker("正在播放:" + StringUtil.formatAudioName(audioList.get(currentPosition).getTitle()))
    .setWhen(System.currentTimeMillis())
    .setContent(getRemoteViews()); startForeground(1, builder.build());
    } private RemoteViews getRemoteViews() {
    RemoteViews remoteViews = new RemoteViews(getPackageName(),
    R.layout.layout_music_notification);
    remoteViews.setTextViewText(R.id.tv_notification_title, StringUtil.formatAudioName(audioList.get(currentPosition).getTitle()));
    remoteViews.setTextViewText(R.id.tv_notification_content, audioList.get(currentPosition).getArtist()); remoteViews.setOnClickPendingIntent(R.id.btn_notification_pre, getPrePendingIntent());
    remoteViews.setOnClickPendingIntent(R.id.btn_notification_next, getNextPendingIntent());
    remoteViews.setOnClickPendingIntent(R.id.layout_notification, getContentPendingIntent()); return remoteViews;
    } private PendingIntent getPrePendingIntent() {
    Intent intent = new Intent(AudioPlayerService.this, AudioPlayerService.class);
    intent.putExtra("viewAction", ACTION_NOTIFICATION_PRE);
    intent.putExtra("isFromNotification", true);
    PendingIntent pendingIntent = PendingIntent.getService(AudioPlayerService.this, 1, intent, PendingIntent.FLAG_UPDATE_CURRENT);
    return pendingIntent;
    } private PendingIntent getNextPendingIntent() {
    Intent intent = new Intent(AudioPlayerService.this, AudioPlayerService.class);
    intent.putExtra("viewAction", ACTION_NOTIFICATION_NEXT);
    intent.putExtra("isFromNotification", true);
    PendingIntent pendingIntent = PendingIntent.getService(AudioPlayerService.this, 2, intent, PendingIntent.FLAG_UPDATE_CURRENT);
    return pendingIntent;
    } private PendingIntent getContentPendingIntent() {
    Intent intent = new Intent(AudioPlayerService.this, AudioPlayerActivity.class);
    Bundle bundle = new Bundle();
    bundle.putInt("viewAction", ACTION_NOTIFICATION_LAYOUT);
    bundle.putBoolean("isFromNotification", true);
    intent.putExtras(bundle);
    PendingIntent pendingIntent = PendingIntent.getActivity(AudioPlayerService.this, 3, intent, PendingIntent.FLAG_UPDATE_CURRENT);
    return pendingIntent;
    }
    /*发送通知的方法应该在音乐准备完毕和開始播放的时候调用*/
    
        private OnPreparedListener onPreparedListener = new OnPreparedListener() {
    @Override
    public void onPrepared(MediaPlayer mp) {
    mediaPlayer.start();
    notifyPrepared();
    sendNotification();
    }
    };
    public void start() {
    if (mediaPlayer != null) {
    mediaPlayer.start();
    }
    sendNotification();
    } //音乐准备暂停时移除通知
    public void pause() {
    if (mediaPlayer != null) {
    mediaPlayer.pause();
    }
    stopForeground(true);//移除通知
    }

歌词绘制

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

  1. 绘制一行居中文本

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

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

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

    /**
    * 绘制全部的歌词
    *
    * @param canvas 画布
    */
    private void drawLyricList(Canvas canvas) {
    Lyric lightLyric = lyricList.get(lightLyricIndex);
    //平滑移动歌词
    //1. 算出歌词的总的播放时间 即 下一行的startPoint - 当前的startPoint
    int totalDuration;
    if(lightLyricIndex==(lyricList.size()-1)){
    //假设最后一行是高亮行,则拿歌曲总时间减去当前的startPoint
    totalDuration = (int) (audioDuration - lightLyric.getStartPoint());
    }else {
    totalDuration = (int) (lyricList.get(lightLyricIndex+1).getStartPoint()-lightLyric.getStartPoint());
    }
    //2. 算出当前已经播放的秒数占总时间的百分比 currentAudioPosition - startPoint
    float offsetPosition = (int) (currentPosition - lightLyric.getStartPoint());
    float percent = offsetPosition/totalDuration;
    //3. 依据百分比算出应该移动的距离 percent * LYRIC_ROW_HEIGHT
    float dy = LYRIC_ROW_HEIGHT * percent;
    canvas.translate(0, -dy); //1.首先将高亮行的歌词绘制出来,作为一个參照物
    float lightLyricY = height / 2 + getTextHeight(lightLyric.getContent()) / 2;
    drawCenterHorizontalText(canvas, lightLyric.getContent(), lightLyricY, true);
    //2.遍历高亮行之前的歌词。并绘制出来
    for (int pre = 0; pre < lightLyricIndex; pre++) {
    Lyric lyric = lyricList.get(pre);
    float y = lightLyricY - (lightLyricIndex - pre) * LYRIC_ROW_HEIGHT;
    drawCenterHorizontalText(canvas, lyric.getContent(), y, false);
    }
    //3.遍历高亮行之后的歌词,并绘制出来
    for (int next = lightLyricIndex + 1; next < lyricList.size(); next++) {
    Lyric lyric = lyricList.get(next);
    float y = lightLyricY + (next - lightLyricIndex) * LYRIC_ROW_HEIGHT;
    drawCenterHorizontalText(canvas, lyric.getContent(), y, false);
    } }
  5. 提供设置歌词的方法

        public void setLyricList(ArrayList<Lyric> lyricList){
    this.lyricList = lyricList;
    if(this.lyricList==null){
    hasNoLyric = true;
    }
    }

歌词解析

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

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

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

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. Docker决战到底(三) Rancher2.x的安装与使用 - 简书

    原文:Docker决战到底(三) Rancher2.x的安装与使用 - 简书   image.png 当越来越多的容器化应用被部署,一个可以管理编排这些容器的工具此时就显得尤为重要了.目前容器编排领域 ...

  2. 2015 Multi-University Training Contest 5 hdu 5349 MZL's simple problem

    MZL's simple problem Time Limit: 3000/1500 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Oth ...

  3. ORACLE-014:oracle中查看DBLinkpassword

    dblink在数据库中使用是比較多的,简单方便.只是随着oracle安全机制的加强.普通用户登录仅仅能看到,username以及连接字符串.那么dblinkpassword怎样查看呢? 非常easy. ...

  4. Android中通过ViewHelper.setTranslationY实现View移动控制(NineOldAndroids开源项目)

    我们知道有不少开源project,能实现非常多不错的效果.前几天,我看了一个效果,刚好项目中也用到了这个jar包. 没事挖一挖 学一学(一说到挖一挖.那么问题来了.挖掘机技术究竟哪家强 ),看看不错的 ...

  5. 【c语言】输入一个递增排序的数组的一个旋转,输出旋转数组中的最小元素

    //旋转数组的最小数字 //题目:把一个数组最開始的若干个元素搬到数组的末尾.我们称之为数组的旋转. //输入一个递增排序的数组的一个旋转.输出旋转数组中的最小元素. //比如:数组{3.4,5,1, ...

  6. 模式匹配的KMP 算法

    常见的字符串匹配时,模式串长度为n,源串长度为m,则从头匹配,两个指针i指向源串,j指向模式串,如遇到不同则回溯使j=0,这样就要反复匹配会使效率变低. 因为在如今i之前 的模式串与匹配串的匹配是同样 ...

  7. android sudio 执行的中文是乱码解决方式

    1.File-->Setings-->查找file encodings 例如以下图 2.将 IDE Encoding .Project Encoding.Default encoding ...

  8. xBIM 基础16 IFC的空间层次结构

    系列目录    [已更新最新开发文章,点击查看详细]  本篇介绍如何从文件中检索空间结构.IFC中的空间结构表示层次结构的嵌套结构,表示项目,站点,建筑物,楼层和空间.如果您查看IFC文档, 您会发现 ...

  9. vue项目中设置全局引入scss,使每个组件都可以使用变量

    在Vue项目中使用scss,如果写了一套完整的有变量的scss文件.那么就需要全局引入,这样在每个组件中使用. 可以在mian.js全局引入,下面是使用方法. 1: 安装node-sass.sass- ...

  10. Git 连接细节

    Git 连接细节 首先创建Git 账号 https://github.com/ 下载Git https://git-scm.com/downloads 新建远程仓库 SSH keys : 打开 Git ...