当前位置: 移动技术网 > 移动技术>移动开发>Android > Android实现歌曲播放时歌词同步显示具体思路

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

2019年07月24日  | 移动技术网移动技术  | 我要评论
我们需要读取以上歌词文件的每一行转换成成一个个歌词实体: 复制代码 代码如下: 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>

如您对本文有疑问或者有任何想说的,请 点击进行留言回复,万千网友为您解惑!

相关文章:

验证码:
移动技术网