欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android实现歌曲播放时歌词同步显示具体思路

程序员文章站 2023-11-12 10:31:46
我们需要读取以上歌词文件的每一行转换成成一个个歌词实体: 复制代码 代码如下: public class lyricobject { public int begintim...
我们需要读取以上歌词文件的每一行转换成成一个个歌词实体:
复制代码 代码如下:

public class lyricobject {
public int begintime; // 开始时间
public int endtime; // 结束时间
public int timeline; // 单句歌词用时
public string lrc; // 单句歌词
}

可根据当前播放器的播放进度与每句歌词的开始时间,得到当前屏幕*高亮显示的那句歌词。在ui线程中另起线程,通过回调函数 ondraw() 每隔100ms重新绘制屏幕,实现歌词平滑滚动的动画效果。mainactivity代码如下:
复制代码 代码如下:

import java.io.ioexception;
import android.app.activity;
import android.media.mediaplayer;
import android.net.uri;
import android.os.bundle;
import android.os.environment;
import android.os.handler;
import android.view.view;
import android.view.view.onclicklistener;
import android.widget.button;
import android.widget.seekbar;
import android.widget.seekbar.onseekbarchangelistener;
public class mainactivity extends activity {
/** called when the activity is first created. */
private lyricview lyricview;
private mediaplayer mediaplayer;
private button button;
private seekbar seekbar;
private string mp3path;
private int interval=45;//歌词每行的间隔
@override
public void oncreate(bundle savedinstancestate) {
super.oncreate(savedinstancestate);
// this.requestwindowfeature(window.feature_no_title);
// getwindow().setflags(windowmanager.layoutparams.flag_fullscreen);
setcontentview(r.layout.main);
mp3path = environment.getexternalstoragedirectory().getabsolutepath() + "/lyricsync/1.mp3";
lyricview = (lyricview) findviewbyid(r.id.mylrc);
mediaplayer = new mediaplayer();
// this.requestwindowfeature(window.feature_no_title);
resetmusic(mp3path);
serchlrc();
lyricview.settextsize();
button = (button) findviewbyid(r.id.button);
button.settext("播放");
seekbar = (seekbar) findviewbyid(r.id.seekbarmusic);
seekbar.setonseekbarchangelistener(new onseekbarchangelistener() {
@override
public void onstoptrackingtouch(seekbar seekbar) {
// todo auto-generated method stub
}
@override
public void onstarttrackingtouch(seekbar seekbar) {
// todo auto-generated method stub
}
@override
public void onprogresschanged(seekbar seekbar, int progress,
boolean fromuser) {
// todo auto-generated method stub
if (fromuser) {
mediaplayer.seekto(progress);
lyricview.setoffsety(220 - lyricview.selectindex(progress)
* (lyricview.getsizeword() + interval-1));
}
}
});
button.setonclicklistener(new onclicklistener() {
@override
public void onclick(view v) {
// todo auto-generated method stub
if (mediaplayer.isplaying()) {
button.settext("播放");
mediaplayer.pause();
} else {
button.settext("暂停");
mediaplayer.start();
lyricview.setoffsety(220 - lyricview.selectindex(mediaplayer.getcurrentposition())
* (lyricview.getsizeword() + interval-1));
}
}
});
mediaplayer.setoncompletionlistener(new mediaplayer.oncompletionlistener() {
@override
public void oncompletion(mediaplayer mp) {
resetmusic(mp3path);
lyricview.settextsize();
lyricview.setoffsety(200);
mediaplayer.start();
}
});
seekbar.setmax(mediaplayer.getduration());
new thread(new runable()).start();
}
public void serchlrc() {
string lrc = mp3path;
lrc = lrc.substring(0, lrc.length() - 4).trim() + ".lrc".trim();
lyricview.read(lrc);
lyricview.settextsize();
lyricview.setoffsety(350);
}
public void resetmusic(string path) {
mediaplayer.reset();
try {
mediaplayer.setdatasource(mp3path);
mediaplayer.prepare();
} catch (illegalargumentexception e) {
// todo auto-generated catch block
e.printstacktrace();
} catch (illegalstateexception e) {
// todo auto-generated catch block
e.printstacktrace();
} catch (ioexception e) {
// todo auto-generated catch block
e.printstacktrace();
}
}
class runable implements runnable {
@override
public void run() {
// todo auto-generated method stub
while (true) {
try {
thread.sleep(100);
if (mediaplayer.isplaying()) {
lyricview.setoffsety(lyricview.getoffsety() - lyricview.speedlrc());
lyricview.selectindex(mediaplayer.getcurrentposition());
seekbar.setprogress(mediaplayer.getcurrentposition());
mhandler.post(mupdateresults);
}
} catch (interruptedexception e) {
// todo auto-generated catch block
e.printstacktrace();
}
}
}
}
handler mhandler = new handler();
runnable mupdateresults = new runnable() {
public void run() {
lyricview.invalidate(); // 更新视图
}
};
}

歌词view的代码如下:
复制代码 代码如下:

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.iterator;
import java.util.treemap;
import java.util.regex.matcher;
import java.util.regex.pattern;
import android.content.context;
import android.graphics.canvas;
import android.graphics.color;
import android.graphics.paint;
import android.util.attributeset;
import android.util.log;
import android.view.motionevent;
import android.view.view;
public class lyricview extends view{
private static treemap<integer, lyricobject> lrc_map;
private float mx; //屏幕x轴的中点,此值固定,保持歌词在x中间显示
private float offsety; //歌词在y轴上的偏移量,此值会根据歌词的滚动变小
private static boolean bllrc=false;
private float touchy; //当触摸歌词view时,保存为当前触点的y轴坐标
private float touchx;
private boolean blscrollview=false;
private int lrcindex=0; //保存歌词treemap的下标
private int sizeword=0;//显示歌词文字的大小值
private int interval=45;//歌词每行的间隔
paint paint=new paint();//画笔,用于画不是高亮的歌词
paint painthl=new paint(); //画笔,用于画高亮的歌词,即当前唱到这句歌词
public lyricview(context context){
super(context);
init();
}
public lyricview(context context, attributeset attrs) {
super(context, attrs);
init();
}
/* (non-javadoc)
* @see android.view.view#ondraw(android.graphics.canvas)
*/
@override
protected void ondraw(canvas canvas) {
if(bllrc){
painthl.settextsize(sizeword);
paint.settextsize(sizeword);
lyricobject temp=lrc_map.get(lrcindex);
canvas.drawtext(temp.lrc, mx, offsety+(sizeword+interval)*lrcindex, painthl);
// 画当前歌词之前的歌词
for(int i=lrcindex-1;i>=0;i--){
temp=lrc_map.get(i);
if(offsety+(sizeword+interval)*i<0){
break;
}
canvas.drawtext(temp.lrc, mx, offsety+(sizeword+interval)*i, paint);
}
// 画当前歌词之后的歌词
for(int i=lrcindex+1;i<lrc_map.size();i++){
temp=lrc_map.get(i);
if(offsety+(sizeword+interval)*i>600){
break;
}
canvas.drawtext(temp.lrc, mx, offsety+(sizeword+interval)*i, paint);
}
}
else{
paint.settextsize(25);
canvas.drawtext("找不到歌词", mx, 310, paint);
}
super.ondraw(canvas);
}
/* (non-javadoc)
* @see android.view.view#ontouchevent(android.view.motionevent)
*/
@override
public boolean ontouchevent(motionevent event) {
// todo auto-generated method stub
system.out.println("bllll==="+blscrollview);
float tt=event.gety();
if(!bllrc){
//return super.ontouchevent(event);
return super.ontouchevent(event);
}
switch(event.getaction()){
case motionevent.action_down:
touchx=event.getx();
break;
case motionevent.action_move:
touchy=tt-touchy;
offsety=offsety+touchy;
break;
case motionevent.action_up:
blscrollview=false;
break;
}
touchy=tt;
return true;
}
public void init(){
lrc_map = new treemap<integer, lyricobject>();
offsety=320;
paint=new paint();
paint.settextalign(paint.align.center);
paint.setcolor(color.green);
paint.setantialias(true);
paint.setdither(true);
paint.setalpha(180);
painthl=new paint();
painthl.settextalign(paint.align.center);
painthl.setcolor(color.red);
painthl.setantialias(true);
painthl.setalpha(255);
}
/**
* 根据歌词里面最长的那句来确定歌词字体的大小
*/
public void settextsize(){
if(!bllrc){
return;
}
int max=lrc_map.get(0).lrc.length();
for(int i=1;i<lrc_map.size();i++){
lyricobject lrcstrlength=lrc_map.get(i);
if(max<lrcstrlength.lrc.length()){
max=lrcstrlength.lrc.length();
}
}
sizeword=320/max;
}
protected void onsizechanged(int w, int h, int oldw, int oldh) {
mx = w * 0.5f;
super.onsizechanged(w, h, oldw, oldh);
}
/**
* 歌词滚动的速度
*
* @return 返回歌词滚动的速度
*/
public float speedlrc(){
float speed=0;
if(offsety+(sizeword+interval)*lrcindex>220){
speed=((offsety+(sizeword+interval)*lrcindex-220)/20);
} else if(offsety+(sizeword+interval)*lrcindex < 120){
log.i("speed", "speed is too fast!!!");
speed = 0;
}
// if(speed<0.2){
// speed=0.2f;
// }
return speed;
}
/**
* 按当前的歌曲的播放时间,从歌词里面获得那一句
* @param time 当前歌曲的播放时间
* @return 返回当前歌词的索引值
*/
public int selectindex(int time){
if(!bllrc){
return 0;
}
int index=0;
for(int i=0;i<lrc_map.size();i++){
lyricobject temp=lrc_map.get(i);
if(temp.begintime<time){
++index;
}
}
lrcindex=index-1;
if(lrcindex<0){
lrcindex=0;
}
return lrcindex;
}
/**
* 读取歌词文件
* @param file 歌词的路径
*
*/
public static void read(string file) {
treemap<integer, lyricobject> lrc_read =new treemap<integer, lyricobject>();
string data = "";
try {
file savefile=new file(file);
// system.out.println("是否有歌词文件"+savefile.isfile());
if(!savefile.isfile()){
bllrc=false;
return;
}
bllrc=true;
//system.out.println("bllrc==="+bllrc);
fileinputstream stream = new fileinputstream(savefile);// context.openfileinput(file);
bufferedreader br = new bufferedreader(new inputstreamreader(stream,"gb2312"));
int i = 0;
pattern pattern = pattern.compile("\\d{2}");
while ((data = br.readline()) != null) {
// system.out.println("++++++++++++>>"+data);
data = data.replace("[","");//将前面的替换成后面的
data = data.replace("]","@");
string splitdata[] =data.split("@");//分隔
if(data.endswith("@")){
for(int k=0;k<splitdata.length;k++){
string str=splitdata[k];
str = str.replace(":",".");
str = str.replace(".","@");
string timedata[] =str.split("@");
matcher matcher = pattern.matcher(timedata[0]);
if(timedata.length==3 && matcher.matches()){
int m = integer.parseint(timedata[0]); //分
int s = integer.parseint(timedata[1]); //秒
int ms = integer.parseint(timedata[2]); //毫秒
int currtime = (m*60+s)*1000+ms*10;
lyricobject item1= new lyricobject();
item1.begintime = currtime;
item1.lrc = "";
lrc_read.put(currtime,item1);
}
}
}
else{
string lrccontenet = splitdata[splitdata.length-1];
for (int j=0;j<splitdata.length-1;j++)
{
string tmpstr = splitdata[j];
tmpstr = tmpstr.replace(":",".");
tmpstr = tmpstr.replace(".","@");
string timedata[] =tmpstr.split("@");
matcher matcher = pattern.matcher(timedata[0]);
if(timedata.length==3 && matcher.matches()){
int m = integer.parseint(timedata[0]); //分
int s = integer.parseint(timedata[1]); //秒
int ms = integer.parseint(timedata[2]); //毫秒
int currtime = (m*60+s)*1000+ms*10;
lyricobject item1= new lyricobject();
item1.begintime = currtime;
item1.lrc = lrccontenet;
lrc_read.put(currtime,item1);// 将currtime当标签 item1当数据 插入treemap里
i++;
}
}
}
}
stream.close();
}
catch (filenotfoundexception e) {
}
catch (ioexception e) {
}
/*
* 遍历hashmap 计算每句歌词所需要的时间
*/
lrc_map.clear();
data ="";
iterator<integer> iterator = lrc_read.keyset().iterator();
lyricobject oldval = null;
int i =0;
while(iterator.hasnext()) {
object ob =iterator.next();
lyricobject val = (lyricobject)lrc_read.get(ob);
if (oldval==null)
oldval = val;
else
{
lyricobject item1= new lyricobject();
item1 = oldval;
item1.timeline = val.begintime-oldval.begintime;
lrc_map.put(new integer(i), item1);
i++;
oldval = val;
}
if (!iterator.hasnext()) {
lrc_map.put(new integer(i), val);
}
}
}
/**
* @return the bllrc
*/
public static boolean isbllrc() {
return bllrc;
}
/**
* @return the offsety
*/
public float getoffsety() {
return offsety;
}
/**
* @param offsety the offsety to set
*/
public void setoffsety(float offsety) {
this.offsety = offsety;
}
/**
* @return 返回歌词文字的大小
*/
public int getsizeword() {
return sizeword;
}
/**
* 设置歌词文字的大小
* @param sizeword the sizeword to set
*/
public void setsizeword(int sizeword) {
sizeword = sizeword;
}
}

xml布局文件如下:
复制代码 代码如下:

<span style="font-size: 18px"><strong><?xml version="1.0" encoding="utf-8"?>
<relativelayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#ffffff" >
<com.music.lyricsync.lyricview
android:id="@+id/mylrc"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_marginbottom="50dip"
android:layout_margintop="50dip" />
<linearlayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignparentbottom="true"
android:orientation="horizontal" >
<button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<seekbar
android:id="@+id/seekbarmusic"
android:layout_width="205px"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginbottom="5px"
android:progress="0" />
</linearlayout>
</relativelayout> </strong></span>