前一篇已經將音樂播放及切換的相關邏輯弄好了,今天主要理一下剩餘的部分,包括:
1. 自定義通知欄的佈局及邏輯處理
2. 滾動歌詞的繪製
3. 歌詞解析
效果圖
通知欄
自定義佈局:
<?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>
通知欄的相關邏輯:
- 下一首
- 上一首
- 進入播放頁
/** * 發送自定義佈局的通知 */ 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()方法。
繪製一行居中文本
/** * 繪製水平居中的歌詞文本 * * @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); }
繪製多行歌詞
/** * 繪製所有的歌詞 * * @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(); }
滾動歌詞
/** * 滾動歌詞 */ 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; } } } }
平滑滾動歌詞
/** * 繪製所有的歌詞 * * @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); } }
提供設置歌詞的方法
public void setLyricList(ArrayList<Lyric> lyricList){ this.lyricList = lyricList; if(this.lyricList==null){ hasNoLyric = true; } }
歌詞解析
- 讀取每一行歌詞文本
- 解析每一行歌詞
對歌詞集合進行排序
/** * 歌詞解析的工具類 */ 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; } }
好了,手機影音項目的整理就到這裏。