android MusicPlayer 音乐播放器 Lrc歌词控件的实现

时间:2022-08-17 10:12:16

                   MusicPlayer Lrc歌词控件的实现

最近在做一个音乐播放器,关于其中歌词控件,上网查过了一些资料,然后进行修改,也算完整的实现了其功能。先看看实现后的效果。         实现的原理实际上是自定义一个View来显示歌词,然后利用View.invalidate()方法来不断的调用onDraw方法,重绘该View。 至于歌词的读取,实际上按照路径读取歌词文件,然后将其时间和每一句歌词分解开并给每一句一个index,通过处理当前播放时间来得到index以此来显示。 具体实现分别如下: 首先创建一个IrcContent类,相当于结构体。每一个IrcContent的实例实际上就是一句歌词,而真个Irc文件就是个List<IrcContent>.
package com.example.zhsmusicplayer;
/*
* 歌词实体类
* 每一句带时间的歌词就是一个LrcContent实例
*
* */
public class LrcContent {

private String lrcStr; //歌词内容
private int lrcTime; //歌词当前时间
public String getLrcStr() {
return lrcStr;
}
public void setLrcStr(String lrcStr) {
this.lrcStr = lrcStr;
}
public int getLrcTime() {
return lrcTime;
}
public void setLrcTime(int lrcTime) {
this.lrcTime = lrcTime;
}
}
然后创建一个LrcProcess类用来读取歌词并将歌词中的时间Time转化为 MediaPlayer类中所使用的getCurrentPosition的毫秒。
  
package com.example.zhsmusicplayer;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;

import android.util.Log;

/*
* 用来读取歌词并且解析类
*
* */
public class LrcProcess {
private List<LrcContent> lrcList;//List集合用来储存歌词对象(每一句歌词)
private LrcContent mLrcContent;
/* 无参数构造函数*/
public LrcProcess() {
mLrcContent = new LrcContent();
lrcList = new ArrayList<LrcContent>();
}

/* 读取某个地址下的歌词文件*/
public String readLRC(String path){
StringBuilder stringBuilder = new StringBuilder();
//这里是直接将MP3的地址直接赋予lrc,及要求lrc的地址与MP3地址一致且名称与MP3一致
File f = new File(path.replace(".mp3", ".lrc"));
//创建一个文件输入流
try {
//创建一个文件输入流对象
FileInputStream fis = new FileInputStream(f);
InputStreamReader isr = new InputStreamReader(fis, "utf-8");
BufferedReader br = new BufferedReader(isr);
String s = "";
while((s = br.readLine()) != null) {
//替换字符
s = s.replace("[", "");
s = s.replace("]", "@");

//分离“@”字符
String splitLrcData[] = s.split("@");
if(splitLrcData.length > 1) {
mLrcContent.setLrcStr(splitLrcData[1]);

//处理歌词取得歌曲的时间
int lrcTime = time2Str(splitLrcData[0]);
mLrcContent.setLrcTime(lrcTime);

//添加进列表数组
lrcList.add(mLrcContent);

//新创建歌词内容对象
mLrcContent = new LrcContent();
}
}
} catch (FileNotFoundException e) {
e.printStackTrace();
stringBuilder.append("木有歌词文件,赶紧去下载!...");
} catch (IOException e) {
e.printStackTrace();
stringBuilder.append("木有读取到歌词哦!");
}
return stringBuilder.toString();
}
/**
* 解析歌词时间
* 就是将读取到的Time转换为duration
*/
public int time2Str(String timeStr) {
timeStr = timeStr.replace(":", ".");
timeStr = timeStr.replace(".", "@");

String timeData[] = timeStr.split("@"); //将时间分隔成字符串数组

//分离出分、秒并转换为整型
int minute = Integer.parseInt(timeData[0]);
int second = Integer.parseInt(timeData[1]);
int millisecond = Integer.parseInt(timeData[2]);
//计算上一行与下一行的时间转换为毫秒数
int currentTime = (minute * 60 + second) * 1000 + millisecond * 10;
return currentTime;
}
public List<LrcContent> getLrcList() {
return lrcList;
}

}
<span style="font-family:Arial, Helvetica, sans-serif;"><span style="white-space: normal;"></span></span>      关于File f = new File(path.replace(".mp3", ".lrc")); 这一句是用来读取某地址下的歌词文件,我这里path参数实际上MP3文件的地址,这就意味着,我打开歌词文件的地址必须和MP3地址相同,在同一个文件夹且文件名也相同。
      接下来就是自定义View作为IrcView了,这里继承了TextView,复写了onDraw()函数来显示需要的信息。
<pre name="code" class="java">package com.example.zhsmusicplayer;
import java.util.ArrayList;import java.util.List;import android.content.Context;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.graphics.Typeface;import android.util.AttributeSet;import android.util.Log;import android.view.LayoutInflater;import android.widget.ImageView;import android.widget.ProgressBar;import android.widget.RelativeLayout;import android.widget.TextView;class CustomBar extends RelativeLayout{private TextView musicName;private ImageView musicImg;private TextView musicArtist;private TextView musicTime;private ImageView iconPlay;private ImageView iconNext;private ImageView iconPrev;private ProgressBar timeBar;public CustomBar(Context context){super(context);}public CustomBar(Context context, AttributeSet attrs) {super(context, attrs);// TODO Auto-generated constructor stubLayoutInflater inflater=(LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);inflater.inflate(R.layout.custom_music_play, this);if(isInEditMode()){return;}musicName=(TextView)findViewById(R.id.name_text);musicArtist=(TextView)findViewById(R.id.author_text);musicTime=(TextView)findViewById(R.id.time_text);musicImg=(ImageView)findViewById(R.id.music_img);iconPlay=(ImageView)findViewById(R.id.play_img);iconNext=(ImageView)findViewById(R.id.next_img);iconPrev=(ImageView)findViewById(R.id.prev_img);timeBar=(ProgressBar)findViewById(R.id.time_progressBar);}public void setMusicName(String name){musicName.setText(name);}public void setMusicArtist(String Author){musicArtist.setText(Author);}public void setMusicTime(String time){musicTime.setText(time);}public void setOnMusicPlayListener(OnClickListener playListener){iconPlay.setOnClickListener(playListener);}public void setOnMusicPlayNextListener(OnClickListener playNextListener){iconNext.setOnClickListener(playNextListener);}public void setOnMusicPlayPrevListener(OnClickListener playPrevListener){iconPrev.setOnClickListener(playPrevListener);}public void setPlayImg(boolean isPause){if(isPause)iconPlay.setImageResource(R.drawable.icon_pause);elseiconPlay.setImageResource(R.drawable.icon_play);}public void setTimeBarMax(int max){timeBar.setMax(max);}public void setTimeBarProgress(int progress){timeBar.setProgress(progress);}}    class LrcView extends TextView{ private float width;        //歌词视图宽度      private float height;       //歌词视图高度      private Paint currentPaint; //当前画笔对象      private Paint notCurrentPaint;  //非当前画笔对象      private float textHeight = 25;  //文本高度      private float textSize = 18;        //文本大小      private int index = 0;      //list集合下标                  private List<LrcContent> mLrcList = new ArrayList<LrcContent>();            public void setmLrcList(List<LrcContent> mLrcList) {          this.mLrcList = mLrcList;      }        public LrcView(Context context) {          super(context);          init();      }      public LrcView(Context context, AttributeSet attrs, int defStyle) {          super(context, attrs, defStyle);          init();      }        public LrcView(Context context, AttributeSet attrs) {          super(context, attrs);          init();      }        private void init() {          setFocusable(true);     //设置可对焦                    //高亮部分          currentPaint = new Paint();          currentPaint.setAntiAlias(true);    //设置抗锯齿,让文字美观饱满          currentPaint.setTextAlign(Paint.Align.CENTER);//设置文本对齐方式                    //非高亮部分          notCurrentPaint = new Paint();          notCurrentPaint.setAntiAlias(true);          notCurrentPaint.setTextAlign(Paint.Align.CENTER);      }            /**      * 绘画歌词      */      @Override      protected void onDraw(Canvas canvas) {          super.onDraw(canvas);          if(canvas == null) {              return;          }          //Log.e("zhs","Index in LrcView Ondraw====="+index);        /*if(index!=0)        Log.e("zhs",mLrcList.get(index).getLrcStr()); */                 //设置字体的大小以及绘制出来的画笔        currentPaint.setColor(Color.argb(210, 251, 248, 29));          notCurrentPaint.setColor(Color.argb(140, 255, 255, 255));                    currentPaint.setTextSize(24);          currentPaint.setTypeface(Typeface.SERIF);                    notCurrentPaint.setTextSize(textSize);          notCurrentPaint.setTypeface(Typeface.DEFAULT);                    try {              setText("");              canvas.drawText(mLrcList.get(index).getLrcStr(), width / 2, height / 2, currentPaint);                             float tempY = height / 2;              //画出本句之前的句子              for(int i = index - 1; i >= 0; i--) {                  //向上推移                  tempY = tempY - textHeight;                  canvas.drawText(mLrcList.get(i).getLrcStr(), width / 2, tempY, notCurrentPaint);              }              tempY = height / 2;              //画出本句之后的句子              for(int i = index + 1; i < mLrcList.size(); i++) {                  //往下推移                  tempY = tempY + textHeight;                  canvas.drawText(mLrcList.get(i).getLrcStr(), width / 2, tempY, notCurrentPaint);              }           } catch (Exception e) {              setText("异常了");          }      }        /**      * 当view大小改变的时候调用的方法      */      @Override      protected void onSizeChanged(int w, int h, int oldw, int oldh) {          super.onSizeChanged(w, h, oldw, oldh);          this.width = w;          this.height = h;      }        public void setIndex(int index) {          this.index = index;      }        }


                       绘制的原理就是当前index指示的即为当前播放的歌词为一种Paint,而利用另外一种Paint来绘制出其余的歌词。利用setmLrcList(List<LrcContent> mLrcList)函数与setIndex(int index)函数来设置歌词文件以及当前index。
然后就是创建一个用来承载LrcView的MusicPlayActivity.xml文件如下:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.zhsmusicplayer.MusicPlayActivity"
android:background="#ffffff"
tools:ignore="MergeRootFrame" >

<com.example.zhsmusicplayer.LrcView
android:id="@+id/lrcShowView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#486432"
android:layout_centerHorizontal="true" />

</RelativeLayout>
接下来要做的就是在播放音乐的service中处理以获取到Index与当前播放音乐的地址并将其传给activity.
获取index,如下:
/**
* 根据时间获取歌词显示的索引值
* @return
*/
public int lrcIndex() {
if(mediaPlayer.isPlaying()) {
currentTime = mediaPlayer.getCurrentPosition();
duration = mediaPlayer.getDuration();
}
if(currentTime < duration) {
for (int i = 0; i < lrcList.size(); i++) {
if (i < lrcList.size() - 1) {
if (currentTime < lrcList.get(i).getLrcTime() && i == 0) {
index = i;
}
if (currentTime > lrcList.get(i).getLrcTime()
&& currentTime < lrcList.get(i + 1).getLrcTime()) {
index = i;
}
}
if (i == lrcList.size() - 1
&& currentTime > lrcList.get(i).getLrcTime()) {
index = i;
}
}
}
return index;
}
                          初始化歌词,开启线程不断的向Activity中传递数据。
   /** * 初始化歌词配置 */public void initLrc(){handler.post(mRunnable);}
Runnable mRunnable = new Runnable() {@Overridepublic void run() {intentForLrc = new Intent("message_for_lrc");intentForLrc.putExtra("path",path);intentForLrc.putExtra("LrcIndext",lrcIndex());sendBroadcast(intentForLrc);handler.postDelayed(mRunnable, 100);}};
runnable线程会不断地向Activity发送包括地址与Index信息的广播。
在播放音乐的同时调用initLrc()函数来不断获取并发送歌词的信息。
 /** 
* 播放音乐
* @param position
*/
private void play(int position) {
try {
initLrc();
mediaPlayer.reset();//把各项参数恢复到初始状态
mediaPlayer.setDataSource(path);
mediaPlayer.prepare(); //进行缓冲
// mediaPlayer.setOnPreparedListener(new PreparedListener(position));//注册一个监听器
mediaPlayer.start();
isPlaying = true;
}
catch (Exception e) {
e.printStackTrace();
}
}
最后就是Activity中进行广播的接收以及控件的重绘了。
package com.example.zhsmusicplayer;



import java.util.List;

import com.example.zhsmusicplayer.MusicListActivity.widgetReceiver;

import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.view.animation.AnimationUtils;

public class MusicPlayActivity extends Activity {
LrcView lrcView;
ServiceReceiver LrcReceiver;
IntentFilter intentFilter;//接收来自Service的广播,用来跟新歌词信息
Handler handler;//handler用来执行实现LrcIndex的检测的接收
Runnable mRunnable;
int currentTime,duration;
String path =null;//当前歌词的path
int Index=-1;//歌词每一句的索引
LrcProcess mLrcProcess;
List<LrcContent> lrcList;
boolean IsRunnable=false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_music_play);
lrcView = (LrcView) findViewById(R.id.lrcShowView);

//广播的注册
LrcReceiver=new ServiceReceiver();
intentFilter = new IntentFilter();
intentFilter.addAction("message_for_lrc");
registerReceiver(LrcReceiver, intentFilter);

//handler
handler=new Handler(){};
//该标志位的目的就是每次进入到该activity执行一次handler.post(mRunnable)
IsRunnable=false;
//Runnable
mRunnable = new Runnable() {

@Override
public void run() {
lrcView.setIndex(Index);
lrcView.invalidate();
handler.postDelayed(mRunnable, 100);
}
};
}


@Override
protected void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
unregisterReceiver(LrcReceiver);
}

@Override
protected void onPause() {
// TODO Auto-generated method stub
super.onPause();
}

//接收来自widget的broadcast
public class ServiceReceiver extends BroadcastReceiver {

@Override
public void onReceive(Context context, Intent intent) {
// TODO Auto-generated method stub
String action = intent.getAction();
if(action.equals("message_for_lrc")){
if(intent.hasExtra("path")){
path=intent.getStringExtra("path");
mLrcProcess = new LrcProcess();
//读取歌词文件
mLrcProcess.readLRC(path);
//传回处理后的歌词文件
lrcList = mLrcProcess.getLrcList();
lrcView.setmLrcList(lrcList);
if(IsRunnable){}
else{
handler.post(mRunnable);
IsRunnable=true;
}
}

if(intent.hasExtra("LrcIndext")){
if(intent.getIntExtra("LrcIndext",-1)!=Index){
Index=intent.getIntExtra("LrcIndext",-1);
}



}
}


}
}




}
动态注册广播接收器同时创建ruannable线程来不断更新LrcView。实际上由于只需要在第一次获取到path信息的时候启动Runnable,故设立了标志位IsRunnable
来进行控制。同时保证了当你每次跳到该Activity时,可以调用Runnable来继续刷新IrcView。 IrcView的基本实现就是这样了。