MusicPlayer Lrc歌词控件的实现
最近在做一个音乐播放器,关于其中歌词控件,上网查过了一些资料,然后进行修改,也算完整的实现了其功能。先看看实现后的效果。 实现的原理实际上是自定义一个View来显示歌词,然后利用View.invalidate()方法来不断的调用onDraw方法,重绘该View。 至于歌词的读取,实际上按照路径读取歌词文件,然后将其时间和每一句歌词分解开并给每一句一个index,通过处理当前播放时间来得到index以此来显示。 具体实现分别如下: 首先创建一个IrcContent类,相当于结构体。每一个IrcContent的实例实际上就是一句歌词,而真个Irc文件就是个List<IrcContent>.package com.example.zhsmusicplayer;然后创建一个LrcProcess类用来读取歌词并将歌词中的时间Time转化为 MediaPlayer类中所使用的getCurrentPosition的毫秒。
/*
* 歌词实体类
* 每一句带时间的歌词就是一个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;
}
}
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"接下来要做的就是在播放音乐的service中处理以获取到Index与当前播放音乐的地址并将其传给activity. 获取index,如下:
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>
/**初始化歌词,开启线程不断的向Activity中传递数据。
* 根据时间获取歌词显示的索引值
* @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;
}
/** * 初始化歌词配置 */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()函数来不断获取并发送歌词的信息。
/**最后就是Activity中进行广播的接收以及控件的重绘了。
* 播放音乐
* @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();
}
}
package com.example.zhsmusicplayer;动态注册广播接收器同时创建ruannable线程来不断更新LrcView。实际上由于只需要在第一次获取到path信息的时候启动Runnable,故设立了标志位IsRunnable 来进行控制。同时保证了当你每次跳到该Activity时,可以调用Runnable来继续刷新IrcView。 IrcView的基本实现就是这样了。
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);
}
}
}
}
}
}