android里教你如何制作一个音乐同步显示歌词的应用
android里教你如何制作一个音乐同步显示歌词的应用最近在做一款android手机上的英语听力播放器,学习到了很多东西,像是Fragment,ActionBar的使用等等,这里就先介绍一下歌词同步的实现问题。
歌词同步的实现思路很简单:获取歌词文件LRC中的时间和歌词内容,然后在指定的时间内播放相应的内容。获取不难,难就在于如何在手机屏幕上实现歌词的滚动。
先上效果图:
我先准备一个歌词文件,先大概看一看里面的内容:
兄弟干杯
作词:秦博
作曲:周宏涛
演唱:秦博
编曲:龙奔
RAP词:刘闯 黄永军
LRC BY :吉时雨
男人在社会 酒一定要学会
烦了累了失恋了 只要你来陪
酒这东西咱别说那贱与贵
酸甜苦辣倒进杯 忘了是与非
现在的社会 美女是一大堆
不富不帅不理咱 不跟咱约会
人要活着就别让那心太累
金钱名利淡如水 不如啊醉一回
来来来 兄弟 干了这一杯
谁大谁小无所谓 有酒今朝醉
来来来 兄弟 干了这一杯
出门在外不容易 辛酸讲给谁
来来来 兄弟 干了这一杯
别管天南和地北 此时最陶醉
来来来 兄弟 干了这一杯
酒逢知己千杯少 人生醉几回
RAP:深夜街头来买醉 究竟为了谁
看破红尘的卑微 口是与心非
种种挫折无所谓 勇敢来面对
不枉人间走一回 男儿不流泪
现在的社会 美女是一大堆
不富不帅不理咱 不跟咱约会
人要活着就别让那心太累
金钱名利淡如水 不如啊醉一回
来来来 兄弟 干了这一杯
谁大谁小无所谓 有酒今朝醉
来来来 兄弟 干了这一杯
出门在外不容易 辛酸讲给谁
来来来 兄弟 干了这一杯
别管天南和地北 此时最陶醉
来来来 兄弟 干了这一杯
酒逢知己千杯少 人生醉几回
来来来 兄弟 干了这一杯
谁大谁小无所谓 有酒今朝醉
来来来 兄弟 干了这一杯
出门在外不容易 辛酸讲给谁
来来来 兄弟 干了这一杯
别管天南和地北 此时最陶醉
来来来 兄弟 干了这一杯
酒逢知己千杯少 人生醉几回
看到了,每段歌词前面都有一个开始时间,那一段的时间就是这一段的结束时间,那原理明白了,就开始准备代码。 先从最基本的读取歌词文件开始:
Public class LrcHandle {
private ListmWords = new ArrayList();
private ListmTimeList = new ArrayList();
//处理歌词文件
public void readLRC(String path) {
File file = new File(path);
try {
FileInputStream fileInputStream = new FileInputStream(file);
InputStreamReader inputStreamReader = new InputStreamReader(
fileInputStream, "utf-8");
BufferedReader bufferedReader = new BufferedReader(
inputStreamReader);
String s = "";
while ((s = bufferedReader.readLine()) != null) {
addTimeToList(s);
if ((s.indexOf("[ar:") != -1) || (s.indexOf("[ti:") != -1)
|| (s.indexOf("[by:") != -1)) {
s = s.substring(s.indexOf(":") + 1, s.indexOf("]"));
} else {
String ss = s.substring(s.indexOf("["), s.indexOf("]") + 1);
s = s.replace(ss, "");
}
mWords.add(s);
}
bufferedReader.close();
inputStreamReader.close();
fileInputStream.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
mWords.add("没有歌词文件,赶紧去下载");
} catch (IOException e) {
e.printStackTrace();
mWords.add("没有读取到歌词");
}
}
public ListgetWords() {
return mWords;
}
public ListgetTime() {
return mTimeList;
}
// 分离出时间
private int timeHandler(String string) {
string = string.replace(".", ":");
String timeData[] = string.split(":");
// 分离出分、秒并转换为整型
int minute = Integer.parseInt(timeData);
int second = Integer.parseInt(timeData);
int millisecond = Integer.parseInt(timeData);
// 计算上一行与下一行的时间转换为毫秒数
int currentTime = (minute * 60 + second) * 1000 + millisecond * 10;
return currentTime;
}
private void addTimeToList(String string) {
Matcher matcher = Pattern.compile(
"\\[\\d{1,2}:\\d{1,2}([\\.:]\\d{1,2})?\\]").matcher(string);
if (matcher.find()) {
String str = matcher.group();
mTimeList.add(new LrcHandle().timeHandler(str.substring(1,
str.length() - 1)));
}
}
}
一般歌词文件的格式大概如下:
其单位是毫秒,正值表示整体提前,负值相反。这是用于总体调整显示快慢的。
但也不一定,有时候并没有前面那些ar:等标识符,所以我们这里也提供了另一种解析方式。
歌词文件中的时间格式则比较统一:等等,00:表示分钟,00.表示秒数,.50表示毫秒数,当然,我们最后是要将它们转化为毫秒数处理才比较方便。
处理完歌词文件并得到我们想要的数据后,我们就要考虑如何在手机上滚动显示我们的歌词并且与我们得到的时间同步了。
先是布局文件:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity" >
<Button
android:id="@+id/button"
android:layout_width="60dip"
android:layout_height="60dip"
android:text="@string/停止" />
<com.example.slidechange.WordView
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/button" />
WordView是自定义的TextView,它继承自TextView:
public class WordView extends TextView {
private List mWordsList = new ArrayList();
private Paint mLoseFocusPaint;
private Paint mOnFocusePaint;
private float mX = 0;
private float mMiddleY = 0;
private float mY = 0;
private static final int DY = 50;
private int mIndex = 0;
public WordView(Context context) throws IOException {
super(context);
init();
}
public WordView(Context context, AttributeSet attrs) throws IOException {
super(context, attrs);
init();
}
public WordView(Context context, AttributeSet attrs, int defStyle)
throws IOException {
super(context, attrs, defStyle);
init();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawColor(Color.BLACK);
Paint p = mLoseFocusPaint;
p.setTextAlign(Paint.Align.CENTER);
Paint p2 = mOnFocusePaint;
p2.setTextAlign(Paint.Align.CENTER);
canvas.drawText(mWordsList.get(mIndex), mX, mMiddleY, p2);
int alphaValue = 25;
float tempY = mMiddleY;
for (int i = mIndex - 1; i >= 0; i--) {
tempY -= DY;
if (tempY < 0) {
break;
}
p.setColor(Color.argb(255 - alphaValue, 245, 245, 245));
canvas.drawText(mWordsList.get(i), mX, tempY, p);
alphaValue += 25;
}
alphaValue = 25;
tempY = mMiddleY;
for (int i = mIndex + 1, len = mWordsList.size(); i < len; i++) {
tempY += DY;
if (tempY > mY) {
break;
}
p.setColor(Color.argb(255 - alphaValue, 245, 245, 245));
canvas.drawText(mWordsList.get(i), mX, tempY, p);
alphaValue += 25;
}
mIndex++;
}
@Override
protected void onSizeChanged(int w, int h, int ow, int oh) {
super.onSizeChanged(w, h, ow, oh);
mX = w * 0.5f;
mY = h;
mMiddleY = h * 0.3f;
}
@SuppressLint("SdCardPath")
private void init() throws IOException {
setFocusable(true);
LrcHandle lrcHandler = new LrcHandle();
lrcHandler.readLRC("/sdcard/陪我去流浪.lrc");
mWordsList = lrcHandler.getWords();
mLoseFocusPaint = new Paint();
mLoseFocusPaint.setAntiAlias(true);
mLoseFocusPaint.setTextSize(22);
mLoseFocusPaint.setColor(Color.WHITE);
mLoseFocusPaint.setTypeface(Typeface.SERIF);
mOnFocusePaint = new Paint();
mOnFocusePaint.setAntiAlias(true);
mOnFocusePaint.setColor(Color.YELLOW);
mOnFocusePaint.setTextSize(30);
mOnFocusePaint.setTypeface(Typeface.SANS_SERIF);
}
}
最主要的是覆盖TextView的onDraw()和onSizeChanged()。
在onDraw()中我们重新绘制TextView,这就是实现歌词滚动实现的关键。歌词滚动的实现思路并不复杂:将上一句歌词向上移动,当前歌词字体变大,颜色变黄突出显示。
我们需要设置位移量DY = 50。颜色和字体大小我们可以通过设置Paint来实现。
我们注意到,在我实现的效果中,距离当前歌词越远的歌词,就会变透明,这个可以通过p.setColor(Color.argb(255 - alphaValue, 245, 245, 245))来实现。
接着就是主代码:
public class MainActivity extends Activity {
private WordView mWordView;
private List mTimeList;
private MediaPlayer mPlayer;
@SuppressLint("SdCardPath")
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button button = (Button) findViewById(R.id.button);
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
mPlayer.stop();
finish();
}
});
mWordView = (WordView) findViewById(R.id.text);
mPlayer = new MediaPlayer();
mPlayer.reset();
LrcHandle lrcHandler = new LrcHandle();
try {
lrcHandler.readLRC("/sdcard/陪我去流浪.lrc");
mTimeList = lrcHandler.getTime();
mPlayer.setDataSource("/sdcard/陪我去流浪.mp3");
mPlayer.prepare();
} catch (IOException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalStateException e) {
e.printStackTrace();
}
final Handler handler = new Handler();
mPlayer.start();
new Thread(new Runnable() {
int i = 0;
@Override
public void run() {
while (mPlayer.isPlaying()) {
handler.post(new Runnable() {
@Override
public void run() {
mWordView.invalidate();
}
});
try {
Thread.sleep(mTimeList.get(i + 1) - mTimeList.get(i));
} catch (InterruptedException e) {
}
i++;
if (i == mTimeList.size() - 1) {
mPlayer.stop();
break;
}
}
}
}).start();
}
}
歌词的显示需要重新开启一个线程,因为主线程是播放歌曲的。
代码很简单,功能也很简单,最主要的是多多尝试,多多修改,就能明白代码的原理了。 再听...更滑稽...四川美女多,男人怕老婆,被婆娘耙耳朵还乐呵呵........认真看着来听竹海唱的味道真不错.后面喊的明白了,竹海被耙耳朵啦哈哈哈哈.......;P ;P ;P :lol :lol :lol :victory: 欣赏了!!:D 再来欣赏,呵呵~~ 好听,欣赏!学习!问候老师! 原帖由 天天爱乐 于 2008-2-2 21:02 发表 http://www.audioapp.cn/images/common/back.gif
再听...更滑稽...四川美女多,男人怕老婆,被婆娘耙耳朵还乐呵呵........认真看着来听竹海唱的味道真不错.后面喊的明白了,竹海被耙耳朵啦哈哈哈哈.......;P ;P ;P :lol :lol :lol
“耙耳朵”是爱老婆关心老婆得不得了的意思!;P 好歌,竹子唱得好。感觉个别地方节奏调整一下也许会更好,一些落音也可以考虑动一下。 欣赏学习很川味的歌曲!:) :victory: 原帖由 羊羊 于 2008-2-15 15:38 发表 http://www.audioapp.cn/images/common/back.gif
好歌,竹子唱得好。感觉个别地方节奏调整一下也许会更好,一些落音也可以考虑动一下。
羊老师可以具体点调整一下。 播放器该不是问题吧?我这里听得好好的!:victory: :victory: 超级恶心一首歌!!!!!!!不知好在那 楼上的怎么了?
感觉这是音频应用里超级棒的一首歌!哪儿都好呀~~:o 原帖由 haoaini 于 2008-2-27 08:41 发表 http://www.audioapp.cn/images/common/back.gif
超级恶心一首歌!!!!!!!不知好在那
说话无过,顶贴有功!;P
原帖由 竹海来风 于 2008-2-27 13:30 发表 http://www.audioapp.cn/images/common/back.gif
说话无过,顶贴有功!;P
横批:到此一游
:P
[ 本帖最后由 清歌 于 2008-2-27 22:00 编辑 ] 原帖由 竹海来风 于 2008-2-26 13:45 发表 http://www.audioapp.cn/images/common/back.gif
羊老师可以具体点调整一下。
说了不一定对啊。
一是曲顺,但缺少记忆点,散了点,是不是可以在“耳朵耙,耙耳朵”上做点文章,让人记住一点什么。
二是后半段节奏没形成对比,所以感觉有点拖踏,试想“耳朵耙,耙耳朵,自己的婆娘自己哄”,前六字喊出来(四川话本身就带旋律),后八字唱,而且重复一次,又会怎样。
三是“百事顺畅,家和美,夜夜都睡热被窝”这句旋律象多余的,很是孤单。
四是一些落音,前后句紧跟,不是太好。
胡乱说几句,没细研究,供参考。 真棒,祝贺来风。 原帖由 羊羊 于 2008-2-28 08:39 发表 http://www.audioapp.cn/images/common/back.gif
说了不一定对啊。
一是曲顺,但缺少记忆点,散了点,是不是可以在“耳朵耙,耙耳朵”上做点文章,让人记住一点什么。
二是后半段节奏没形成对比,所以感觉有点拖踏,试想“耳朵耙,耙耳朵,自己的婆娘自己哄 ...
我给张德高说过一句话:
“说四川话的人,只要他记得住这歌词就几乎能唱这个旋律。”
做这歌之前我就想:我这首歌一定要引起说四川话的人的共鸣,同时也要引起能听得懂四川话的人的共鸣。 这点我做到了!!!
羊老师说的-“耳朵耙,耙耳朵.....”的旋律就是歌曲中最直白的四川
话之一(完全是按方言的抑扬顿挫来套的旋律)。我真的很怀疑这旋律是不是我写的,我常常感觉是所有说四川话的人们共同来完成的。
谢谢羊老师顶贴,您确确实实是我最好的朋友:) 听了,不错,支持一把 绝妙好歌,把四川民歌和川剧高腔音调巧妙揉合,川味浓郁,风趣幽默。说四川话的人,只要他记得住这歌词就几乎能唱这个旋律。顶死你!:funk: :victory: :handshake 谢谢兴平和老土:) 如果是把这首歌交给喜剧明星表演唱的话,定会趣味无穷:lol
页:
[1]