上一篇写了使用MediaPlayer播放音乐,http://blog.csdn.net/huweigoodboy/article/details/39861539

代码地址:https://github.com/huweigoodboy/SweetMusicPlayer

如今来将一下载入本地歌词。好了,还是用那张图。

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaHV3ZWlnb29kYm95/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="">

一。从内存卡上匹配歌词

将会从下面路径匹配

1)  SweetMusicPlayer/Lyrics/

2)  歌曲同级文件夹下

3)  歌曲父级文件夹/lryics(Lryic加不加s,首字母大小与否又分情况)

LrcContent

package com.huwei.sweetmusicplayer.models;

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

package com.huwei.sweetmusicplayer.models;

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.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import android.util.Log; import com.huwei.sweetmusicplayer.comparator.LrcComparator;
import com.huwei.sweetmusicplayer.datamanager.MusicManager;
import com.huwei.sweetmusicplayer.util.OnlineLrcUtil;
import com.huwei.sweetmusicplayer.util.TimeUtil; public class LrcProcess {
private List<LrcContent> lrclists; public LrcProcess() {
super();
lrclists = new ArrayList<LrcContent>();
lrclists.clear();
} public String loadLrc(Song song) {
String path = song.getPath();
StringBuffer stringBuffer = new StringBuffer();
// 得到歌词文件路径
String lrcPathString = path.substring(0, path.lastIndexOf("."))
+ ".lrc";
int index = lrcPathString.lastIndexOf("/"); String parentPath;
String lrcName;
// if(index!=-1){
parentPath = lrcPathString.substring(0, index);
lrcName = lrcPathString.substring(index);
// }
File file = new File(lrcPathString); // 匹配SweetMusicPlayer/Lyrics
if (!file.exists()) {
file = new File(OnlineLrcUtil.getInstance().getLrcPath(
song.getTitle(), song.getArtist()));
}
Log.i("Path", file.getAbsolutePath().toString()); // 匹配Lyrics
if (!file.exists()) {
file = new File(parentPath + "/../" + "Lyrics/" + lrcName);
}
Log.i("Path", file.getAbsolutePath().toString()); // 匹配lyric
if (!file.exists()) {
file = new File(parentPath + "/../" + "lyric/" + lrcName);
}
Log.i("Path", file.getAbsolutePath().toString()); // 匹配Lyric
if (!file.exists()) {
file = new File(parentPath + "/../" + "Lyric/" + lrcName);
} Log.i("Path", file.getAbsolutePath().toString()); // 匹配lyrics
if (!file.exists()) {
file = new File(parentPath + "/../" + "lyrics/" + lrcName);
}
Log.i("Path", file.getAbsolutePath().toString()); if (!file.exists()) {
stringBuffer.append(MusicManager.OperateState.READLRCFILE_FAIL);
return stringBuffer.toString();
} try {
FileInputStream fin = new FileInputStream(file);
InputStreamReader isr = new InputStreamReader(fin, "utf-8");
BufferedReader br = new BufferedReader(isr); String s;
boolean isLrc = false;
while ((s = br.readLine()) != null) {
// if(isLrc){ s = s.replace("[", ""); // 去掉左边括号 String lrcData[] = s.split("]"); // 这句是歌词
if (lrcData[0].matches("^\\d{2}:\\d{2}.\\d+$")) {
int len = lrcData.length;
int end = lrcData[len - 1].matches("^\\d{2}:\\d{2}.\\d+$") ? len
: len - 1; for (int i = 0; i < end; i++) {
LrcContent lrcContent = new LrcContent();
int lrcTime = TimeUtil.getLrcMillTime(lrcData[i]);
lrcContent.setLrcTime(lrcTime);
if (lrcData.length == end)
lrcContent.setLrcStr(""); // 空白行
else
lrcContent.setLrcStr(lrcData[len - 1]); lrclists.add(lrcContent);
} } }
// 按时间排序
Collections.sort(lrclists, new LrcComparator()); if (lrclists.size() == 0) {
stringBuffer.append(MusicManager.OperateState.READLRC_LISTNULL);
} else {
stringBuffer.append(MusicManager.OperateState.READLRC_SUCCESS);
}
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
// stringBuffer.append("未找到歌词文件");
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
// stringBuffer.append("不支持的编码");
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
// stringBuffer.append("IO错误");
} return stringBuffer.toString();
} public List<LrcContent> getLrclists() {
return lrclists;
} }

二,歌词解析

先摘取一段歌词

ti:安静]

[ar:周杰伦]

[al:范特西]

[by:Midas]

[00:03.16] 

[00:04.50]周杰伦-安静

[00:14.50]词:周杰伦 曲:周杰伦 编:钟兴民

[00:25.17]  

[02:27.48][00:27.02]仅仅剩下钢琴陪我谈了一天

[02:32.76][00:32.51]睡着的大提琴 安静的旧旧的

[02:39.60][00:38.67] 

[02:40.74][00:40.48]我想你已表现的很明确

[02:46.04][00:45.7]我懂我也知道 你没有舍不得

[02:53.23][00:52.53] 

[02:54.04][00:53.76]你说你也会难过我不相信

[03:00.59][01:00.36]牵着你陪着我 也仅仅是以前

[03:06.63][01:06.24]希望他是真的比我还要爱你

[03:13.29][01:12.96]我才会逼自己离开

每次遍历一行。首先要把“[”替换成" ",去匹配哪些是时间部分,正则匹配“^\\d{2}:\\d{2}.\\d+$”,然后split("]"),得到一个数组data[],最后一个是内容。前面是歌词。遍历数组,装入时间歌词到list。

时间处理:转成毫秒

遍历全部行后,对list依照时间排序。

代码在上面LrcProgress。

三,LrcView控件

分为下面状态:

public static String READLRC_SUCCESS="READLRC_SUCCESS";//读取本地歌词成功

public static String READLRC_LISTNULL="READLRC_LISTNULL";//读取歌词list为null

public static String READLRC_ONLINE="READLRC_ONLINE";//正在从网络载入歌词

public static String READLRCFILE_FAIL="READLRCFILE_FAIL";//读取歌词文件失败

public static String READLRCONLINE_FAIL="READLRCONLINE_FAIL";//从网络载入歌词失败

依据不同的状态绘制不同的内容。

LrcView继承自ScrollView,然后再加一层LinearLayout。歌词绘制在TextView上,依照播放时间滚动。就能够保证当前播放的歌词在屏幕中间了。

关于自己定义控件,要注意对onMeasure(),onLayout(),onDraw()比較好的理解,有时候遇到onDraw()不能运行,记得加上setWillNotDraw(false),这里直接继承自ScrollView,就不须要考虑那么多了。

这里须要依据播放时间计算当前播放位置,歌词所在行,然后不同的时候,就去更新歌词界面。

调整歌词进度:

触摸监听时,ACTION_MOVE去绘制歌词进度预览(包含调整到的时间预览)。ACTION_UP时调整到相应的进度。

package com.huwei.sweetmusicplayer.ui.widgets;

import java.util.List;

import com.huwei.sweetmusicplayer.datamanager.MusicManager;
import com.huwei.sweetmusicplayer.models.LrcContent;
import com.huwei.sweetmusicplayer.util.TimeUtil; import android.content.Context;
import android.content.Intent;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Typeface;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewTreeObserver.OnScrollChangedListener;
import android.widget.ScrollView;
import android.widget.TextView;
import android.view.View.OnTouchListener; public class LrcView extends ScrollView implements OnScrollChangedListener,OnTouchListener{
private float width; //歌词视图宽度
private float height; //歌词视图高度
private Paint currentPaint; //当前画笔对象
private Paint notCurrentPaint; //非当前画笔对象
private final float textHeight=40; //文本高度
private final float textSize=36; //高亮文本大小
private final float notTextSize=30; //非高亮文本大小
private int index; //歌词list集合下标 private String lrcState;
private LrcTextView lrcTextView;
private List<LrcContent> lrcLists; private int scrollY;
private boolean canDrawLine=false;
private int pos=-1; //手指按下后歌词要到的位置
private Paint linePaint; private boolean canTouchLrc=false; //能否够触摸并调整歌词 private int count=0; //绘制载入点的次数 private Context mContext; public LrcView(Context context) {
this(context,null);
// TODO Auto-generated constructor stub
} public LrcView(Context context, AttributeSet attrs) {
this(context, attrs,0);
// TODO Auto-generated constructor stub
} public LrcView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// TODO Auto-generated constructor stub mContext=context; this.setOnTouchListener(this); init(); } public List<LrcContent> getLrcLists() {
return lrcLists;
} public void setLrcLists(List<LrcContent> lrcLists) {
this.lrcLists = lrcLists; //推断歌词界面能否够触摸
if(lrcLists==null||lrcLists.size()==0) canTouchLrc=false;
else canTouchLrc=true;
//设置index=-1
this.index=-1; LayoutParams params1=new LayoutParams(LayoutParams.MATCH_PARENT,LayoutParams.MATCH_PARENT);
lrcTextView=new LrcTextView(this.getContext());
lrcTextView.setLayoutParams(params1); this.removeAllViews();
this.addView(lrcTextView); } public int getIndex() {
return index;
} public void setIndex(int index) {
//歌曲位置发生变化,并且手指不是调整歌词位置的状态
if(this.index!=index&&pos==-1){
this.scrollTo(0, (int)(index*textHeight));
} this.index = index; } @Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
// TODO Auto-generated method stub
super.onSizeChanged(w, h, oldw, oldh); this.width=w;
this.height=h;
} public int getIndexByLrcTime(int currentTime){
for(int i=0;i<lrcLists.size();i++){
if(currentTime<lrcLists.get(i).getLrcTime()){
return i-1;
}
}
return lrcLists.size()-1;
} public void clear(){
lrcLists=null;
} public String getLrcState() {
return lrcState;
} public void setLrcState(String lrcState) {
this.lrcState = lrcState;
this.invalidate();
} class LrcTextView extends TextView{
public LrcTextView(Context context) {
this(context,null);
// TODO Auto-generated constructor stub
} public LrcTextView(Context context, AttributeSet attrs) {
this(context, attrs,0);
// TODO Auto-generated constructor stub
} public LrcTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// TODO Auto-generated constructor stub
this.setWillNotDraw(false);
} //绘制歌词
@Override
protected void onDraw(Canvas canvas) {
// TODO Auto-generated method stub
super.onDraw(canvas); Log.i("LrcTextView onDraw","LrcTextView onDraw"); if(canvas==null) return; int tempY=(int) height/2; if(MusicManager.OperateState.READLRC_LISTNULL.equals(lrcState)){
canvas.drawText("歌词内容为空", width/2, tempY, notCurrentPaint);
return;
}else if(MusicManager.OperateState.READLRCFILE_FAIL.equals(lrcState)){
canvas.drawText("未找到歌词文件", width/2, tempY, notCurrentPaint);
return;
}
else if(MusicManager.OperateState.READLRC_SUCCESS.equals(lrcState)){ //绘制歌词
for(int i=0;i<lrcLists.size();i++,tempY+=textHeight){
if(i==index){
canvas.drawText(lrcLists.get(i).getLrcStr(), width/2, tempY, currentPaint);
}else if(i==pos){
canvas.drawText(lrcLists.get(i).getLrcStr(), width/2, tempY, linePaint);
}else{
canvas.drawText(lrcLists.get(i).getLrcStr(), width/2, tempY, notCurrentPaint);
}
} return;
}else if(MusicManager.OperateState.READLRC_ONLINE.equals(lrcState)){
String drawContentStr="在线匹配歌词"; for(int i=0;i<count;i++){
drawContentStr+=".";
} count++;
if(count>=6) count=0; canvas.drawText(drawContentStr, width/2, tempY, notCurrentPaint); handler.sendEmptyMessageDelayed(1, 500);
return;
}else if(MusicManager.OperateState.READLRCONLINE_FAIL.equals(lrcState)){
canvas.drawText("从网络载入歌词失败", width/2, tempY, notCurrentPaint);
return;
} } @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// TODO Auto-generated method stub
super.onMeasure(widthMeasureSpec, heightMeasureSpec); heightMeasureSpec=(int) (height+textHeight*(lrcLists.size()-1));
setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);
} }; @Override
protected void onDraw(Canvas canvas) {
// TODO Auto-generated method stub
super.onDraw(canvas); if(canDrawLine){
canvas.drawLine(0, scrollY+height/2, width, scrollY+height/2, linePaint);
canvas.drawText(TimeUtil.toTime(lrcLists.get(pos).getLrcTime()), 42, scrollY+height/2-2, linePaint);
}
} private void init(){
setFocusable(true); //设置该控件能够有焦点
this.setWillNotDraw(false); //高亮歌词部分
currentPaint=new Paint();
currentPaint.setAntiAlias(true); //设置抗锯齿
currentPaint.setTextAlign(Paint.Align.CENTER); //设置文本居中 //非高亮歌词部分
notCurrentPaint=new Paint();
notCurrentPaint.setAntiAlias(true);
notCurrentPaint.setTextAlign(Paint.Align.CENTER); //
linePaint=new Paint();
linePaint.setAntiAlias(true);
linePaint.setTextAlign(Paint.Align.CENTER); //设置画笔颜色
currentPaint.setColor(Color.argb(210, 251, 248, 29));
notCurrentPaint.setColor(Color.argb(140, 255, 255, 255));
linePaint.setColor(Color.RED); currentPaint.setTextSize(textSize);
currentPaint.setTypeface(Typeface.SERIF); notCurrentPaint.setTextSize(notTextSize);
notCurrentPaint.setTypeface(Typeface.DEFAULT); linePaint.setTextSize(textSize);
linePaint.setTypeface(Typeface.SERIF); } @Override
public void invalidate() {
// TODO Auto-generated method stub
super.invalidate(); lrcTextView.invalidate();
} @Override
public void onScrollChanged() {
// TODO Auto-generated method stub } @Override
public boolean onTouch(View v, MotionEvent event) {
// TODO Auto-generated method stub //界面不能被触摸
if(!canTouchLrc) return true; switch(event.getAction()){
case MotionEvent.ACTION_MOVE:
scrollY=this.getScrollY();
pos=(int) (this.getScrollY()/textHeight); canDrawLine=true;
this.invalidate(); Log.i("LrcView", "ACTION_DOWN");
break;
case MotionEvent.ACTION_UP:
MusicManager.getInstance().setProgress(lrcLists.get(pos).getLrcTime()); canDrawLine=false;
pos=-1;
this.invalidate();
break;
} return false;
} private Handler handler=new Handler(){ @Override
public void handleMessage(Message msg) {
// TODO Auto-generated method stub invalidate();
} };
}

下一篇智能匹配在线歌词: http://blog.csdn.net/huweigoodboy/article/details/39878063

android音乐播放器开发 SweetMusicPlayer 智能匹配本地歌词的更多相关文章

  1. android音乐播放器开发 SweetMusicPlayer 智能负载直插式歌词

    在一份书面的使用MediaPlayer播放音乐, http://blog.csdn.net/huweigoodboy/article/details/39862773.假设没有本地歌词怎么办?如今来将 ...

  2. android音乐播放器开发 SweetMusicPlayer 播放本地音乐

    上一篇写了载入歌曲列表,http://blog.csdn.net/huweigoodboy/article/details/39856411,如今来总结下播放本地音乐. 一,MediaPlayer 首 ...

  3. android音乐播放器开发 SweetMusicPlayer 实现思路

    一,实现效果 眼下还不是特别完好,主要有下面几个功能, 1,载入歌曲列表(实现a-z字母检索) 2,播放本地音乐 3.智能匹配本地歌词 4.智能载入在线歌词(事实上不算智能.发现歌词迷api提供的歌词 ...

  4. android音乐播放器开发 SweetMusicPlayer 载入歌曲列表

    上一篇写了播放器的总体实现思路,http://blog.csdn.net/huweigoodboy/article/details/39855653,如今来总结下载入歌曲列表. 代码地址:https: ...

  5. android音乐播放器开发 SweetMusicPlayer 摇一摇换歌

    上一篇写了怎样在线匹配歌词,http://blog.csdn.net/huweigoodboy/article/details/39878063,如今来讲讲摇一摇功能开发. 相同用了一个Service ...

  6. android音乐播放器开发教程

    android音乐播放器开发教程 Android扫描sd卡和系统文件 Android 关于录音文件的编解码 实现米聊 微信一类的录音上传的功能 android操作sdcard中的多媒体文件——音乐列表 ...

  7. Android音乐播放器开发

    今日看书,看到这个播放器,我就写了个例子,感觉还行,这个播放器能播放后缀是.MP3的音乐,这个例子在main.xml设置listView的时候,注意:android:id="@+id/and ...

  8. Android音乐播放器源码(歌词.均衡器.收藏.qq5.0菜单.通知)

    一款Android音乐播放器源码,基本功能都实现了 qq5.0菜单(歌词.均衡器.收藏.qq5.0菜单.通知) 只有向右滑动出现,菜单键和指定按钮都还没有添加. 源码下载:http://code.66 ...

  9. 一款非常简单的android音乐播放器源码分享给大家

    一款非常简单的android音乐播放器源码分享给大家,该应用虽然很小,大家常用的播放器功能基本实现了,可能有点还不够完善,大家也可以自己完善一下,源码在源码天堂那里已经有了,大家可以到那里下载学习吧. ...

随机推荐

  1. 4星|《DK商业百科》:主要商业思想与事件的概括

    全书分为以下6章:1:企业的起步与发展:2:领导和人力资源:3:理财:4:战略和运营:5:营销管理:6:生产与生产后.每章有拆分为成多个比较小的专题,阐述相关专题的主要的商业思想与实践. 基本是作者按 ...

  2. MFC_2.4 组合框和图片控件

    组合框和图片控件 1.拖控件 图片属性更改Type 为Bitmap 名字也要改,不能为IDC_STATIC 绑定变量控件,重命名. 2.初始化 // 设置一个定时器,用于更新图片 SetTimer(0 ...

  3. Vue的特性

    1.数据驱动视图 <div id="app"> <p> {{ message }}<p> </div> var app = new ...

  4. 09Microsoft SQL Server 表数据插入,更新,删除

    Microsoft SQL Server 表数据插入,更新,删除 向表中插入数据 INSERT INTO insert into tb1 values(0004,'张凤凤') insert into ...

  5. 17Oracle Database 维护

    Oracle Database 维护 备份 还原

  6. 微信支付开发 c#

    代码demo下载地址: https://pay.weixin.qq.com/wiki/doc/api/native.php?chapter=11_1

  7. JAVA基础——链表结构之单链表

    链表:一种数据存储结构.学链表首先要搞懂数组,按朋友的话说,数组和链表的关系就相当于QQ2008和QQ2009. 除非要通过索引频繁访问各个数据,不然大多数情况下都可以用链表代替数组. 链表部分主要要 ...

  8. Android 各大网络请求库的比较及实战

    自己学习android也有一段时间了,在实际开发中,频繁的接触网络请求,而网络请求的方式很多,最常见的那么几个也就那么几个.本篇文章对常见的网络请求库进行一个总结. HttpUrlConnection ...

  9. Humidex POJ - 3299 (数学)

    题目大意 给定你三个变量中的两个输出剩下的那一个 题解 没有什么,就是把公式推出来即可,完全的数学题 代码 #include <iostream> #include <cmath&g ...

  10. python3中post请求里带list报错

    这个post请求的数据太长,一般data=,json=就够了. 但是今天这个一直报错,用json吧,报缺少参数,用data吧,报多余[. 后来改成data=,并把数据中的[] 用引号括起来," ...