• Android 手机影音 开发过程记录(六)


    前一篇已经将音乐播放及切换的相关逻辑弄好了,今天主要理一下剩余的部分,包含:
    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;
          }
      }
      

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

  • 相关阅读:
    【MySql存储过程】DATE_ADD用法
    MySQL日期时间函数大全
    mysql中增加某一时间段内的时间数据(包含:时间、年、月、日、第几周、季度)
    webService学习之路(二):springMVC集成CXF快速发布webService
    WebService 学习之路(一):了解并使用webService
    Python 运算符简介与用法
    python实现排列组合公式C(m,n)求值
    Python 学会字典 干货
    Python代码实现视频字符化
    python处理txt文件操作
  • 原文地址:https://www.cnblogs.com/mthoutai/p/7059155.html
Copyright © 2020-2023  润新知