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去服务器请求对应的歌词文件
     */
    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;
        }
    }
    

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

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-12-28 04:28:48

Android 手机影音 学习过程记录(六)的相关文章

黑马2018年最新kotlin项目实战视频 (手机影音、即时通讯、黑马外卖、坦克大战等)

├─1.kotlin简介和学习方法│ 01_kotlin课程简介_01.mp4│ 02_kotlin学习方法_01.mp4│ 03_kotlin选好教练车_01.mp4│ 03_kotlin选好教练车_02.mp4│ 04_kotlin你好世界_01.mp4│ 05_kotlin变量与输出_01.mp4│ 05_kotlin变量与输出_02.mp4│ ├─10.类,对象和接口(三)│ 38_kotlin面向对象-抽象类和接口_01.mp4│ 38_kotlin面向对象-抽象类和接口_02.mp4

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

对于GPS软件平台,虽然有功能非常丰富的PC端或BS客户端,但是客户也是需要移动客户端来作为自己的辅助工具,也是需要的.做为GPS平台的设计者和开发者,在开发移动客户端的时候,也需要从常规的服务器开发和客户端开发的思维中,转变过来,当然客户的需求也需要转变,因为毕竟不能随心所欲的将PC端的所有功能需求照搬到手机客户端,手机的开发环境.网络环境.使用环境都决定了设计理念与PC端的设计是完全不一样的. 通常我们成为GPS部标平台的手机客户端为手机查车,实际上现在的功能不仅仅是查车,由于客户需求的推进

为什么android手机越用越慢

根据第三方的调研数据显示,有 77% 的 Android 手机用户承认自己曾遭遇过手机变慢的影响,百度搜索"Android+ 卡慢",也有超过 460 万条结果.在业内,Android 手机一直有着"越用越慢"的口碑,这个现象甚至超出了硬件范畴--很多中高端 Android 手机在硬件参数上都优于同一代 iPhone,但是它们仍然会在使用半年到一年的时间后进入"欠流畅"的状态--这无疑是一件令人困扰的事情. 然而,若是要回答这个问题,我们需要追溯

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

根据第三方的调研数据显示,有77%的Android手机用户承认自己曾遭遇过手机变慢的影响,百度搜索“Android+卡慢”,也有超过460万条结果.在业内,Android手机一直有着“越用越慢”的口碑,这个现象甚至超出了硬件范畴——很多中高端Android手机在硬件参数上都优于同一代iPhone,但是它们仍然会在使用半年到一年的时间后进入“欠流畅”的状态——这无疑是一件令人困扰的事情. 然而,若是要回答这个问题,我们需要追溯到上个世纪,去寻找智能手机的起源. 西方历史及奇幻文学作品十分热衷于表达

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

转自:http://www.androidchina.net/818.html 根据第三方的调研数据显示,有77%的Android手机用户承认自己曾遭遇过手机变慢的影响,百度搜索“Android+卡慢”,也有超过460万条结果.在业内,Android手机一直有着“越用越慢”的口碑,这个现象甚至超出了硬件范畴——很多中高端Android手机在硬件参数上都优于同一代iPhone,但是它们仍然会在使用半年到一年的时间后进入“欠流畅”的状态——这无疑是一件令人困扰的事情. 然而,若是要回答这个问题,我们

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

根据第三方的调研数据显示,有77%的Android手机用户承认自己曾遭遇过手机变慢的影响,百度搜索“Android+卡慢”,也有超过460万条结果.在业内,Android手机一直有着“越用越慢”的口碑,这个现象甚至超出了硬件范畴——很多中高端Android手机在硬件参数上都优于同一代iPhone,但是它们仍然会在使用半年到一年的时间后进入“欠流畅”的状态——这无疑是一件令人困扰的事情.     然而,若是要回答这个问题,我们需要追溯到上个世纪,去寻找智能手机的起源. 1965 年,贝尔实验室.通

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

前言        Android系统与其他操作系统有个很不一样的地方,就是其他操作系统尽可能移除不再活动的进程,从而尽可能保证多的内存空间,而Android系统却是反其道而行之,尽可能保留进程.Android这样设计有什么优势呢?又是通过怎样的方法来管理这些被保留的进程的呢?Android用户又该如何正确使用手机从而更好发挥Android系统所特有的优势呢?本文将一一为您解开这些谜团.        一.Android进程管理的特殊设计 Linux系统对进程的管理方式是一旦进程活动停止,系统就

Android手机 Fildder真机抓包

Android 手机 Fildder 真Fiddler是一个http调试代理,它能 够记录所有的你电脑和互联网之间的http通讯,Fiddler 可以也可以让你检查所有的http通讯,设置断点,以及Fiddle 所有的"进出"的数据(指cookie,html,js,css等文件,这些都可以让你胡乱修改的意思). Fiddler 要比其他的网络调试器要更加简单,因为它仅仅暴露http通讯还有提供一个用户友好的格式. 对于Android开发的同事最头疼的事情莫过于真机抓包,然后Fiddle

android手机设置虚拟网络的步骤

上个周五参加麦子学院班会的时候,android开发老师葛老师讲到android手机设置虚拟网络的问题,她说在android手机使用中,经常会需要设置虚拟网络,下面我就给大家回顾一下麦子学院android开发老师讲的虚拟网络的设置,来看看具体设置步骤: 第一步,打开手机主菜单,选择"设置",然后选择"无线和网络" 第二步:选择"虚拟专用网设置" 第三步:选择"添加虚拟专用网" 第四步:选择"添加L2TP/IPSec P