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

来源:互联网 发布:数据透视表求和项为0 编辑:程序博客网 时间:2024/04/28 03:40

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

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

2 0
原创粉丝点击