一.概述

  项目中设计到歌词显示的问题,这一块之前没有涉及过,只是套用过一个开源的项目,效果还行,于是想到拿来稍作修改,以适应项目需求.

二.歌词控件

  先来看下这个自定义控件写的歌词控件吧:

public class LrcView extends View implements ILrcView {
/**
* 所有的歌词
***/
private List<LrcRow> mLrcRows;
/**
* 无歌词数据的时候 显示的默认文字
**/
private static final String DEFAULT_TEXT = "*暂未获取到歌词*";
/**
* 默认文字的字体大小
**/
private static final float SIZE_FOR_DEFAULT_TEXT = CommonUtils.dip2px(MyApplication.getContext(), 28); /**
* 画高亮歌词的画笔
***/
private Paint mPaintForHighLightLrc;
/**
* 高亮歌词的默认字体大小
***/
private static final float DEFAULT_SIZE_FOR_HIGHT_LIGHT_LRC = CommonUtils.dip2px(MyApplication.getContext(), 32);
/**
* 高亮歌词当前的字体大小
***/
private float mCurSizeForHightLightLrc = DEFAULT_SIZE_FOR_HIGHT_LIGHT_LRC;
/**
* 高亮歌词的默认字体颜色
**/
private static final int DEFAULT_COLOR_FOR_HIGHT_LIGHT_LRC = 0xffffffff; /**
* 高亮歌词当前的字体颜色
**/
private int mCurColorForHightLightLrc = DEFAULT_COLOR_FOR_HIGHT_LIGHT_LRC; /**
* 画其他歌词的画笔
***/
private Paint mPaintForOtherLrc;
/**
* 其他歌词的默认字体大小
***/
private static final float DEFAULT_SIZE_FOR_OTHER_LRC = CommonUtils.dip2px(MyApplication.getContext(), 28);
/**
* 其他歌词当前的字体大小
***/
private float mCurSizeForOtherLrc = DEFAULT_SIZE_FOR_OTHER_LRC;
/**
* 其他歌词的默认字体颜色
**/
private static final int DEFAULT_COLOR_FOR_OTHER_LRC = 0x66ffffff;
/**
* 其他歌词当前的字体颜色
**/
private int mCurColorForOtherLrc = DEFAULT_COLOR_FOR_OTHER_LRC; /**
* 画时间线的画笔
***/
private Paint mPaintForTimeLine;
/***
* 时间线的颜色
**/
private static final int COLOR_FOR_TIME_LINE = 0xff999999;
/**
* 时间文字大小
**/
private static final int SIZE_FOR_TIME = CommonUtils.dip2px(MyApplication.getContext(), 12);
/**
* 是否画时间线
**/
private boolean mIsDrawTimeLine = false; /**
* 歌词间默认的行距
**/
private static final float DEFAULT_PADDING = CommonUtils.dip2px(MyApplication.getContext(), 17);
/**
* 歌词当前的行距
**/
private float mCurPadding = DEFAULT_PADDING; /**
* 歌词的最大缩放比例
**/
public static final float MAX_SCALING_FACTOR = 1.5f;
/**
* 歌词的最小缩放比例
**/
public static final float MIN_SCALING_FACTOR = 0.5f;
/**
* 默认缩放比例
**/
private static final float DEFAULT_SCALING_FACTOR = 1.0f;
/**
* 歌词的当前缩放比例
**/
private float mCurScalingFactor = DEFAULT_SCALING_FACTOR; /**
* 实现歌词竖直方向平滑滚动的辅助对象
**/
private Scroller mScroller;
/***
* 移动一句歌词的持续时间
**/
private static final int DURATION_FOR_LRC_SCROLL = 500;
/***
* 停止触摸时 如果View需要滚动 时的持续时间
**/
private static final int DURATION_FOR_ACTION_UP = 400; /**
* 控制文字缩放的因子
**/
private float mCurFraction = 0;
private int mTouchSlop; private Bitmap arrowBitmap; public LrcView(Context context) {
super(context); init(context);
} public LrcView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
} /**
* 初始化画笔等
*/
@Override
public void init(Context context) {
mScroller = new Scroller(getContext());
mPaintForHighLightLrc = new Paint();
// mPaintForHighLightLrc.setShadowLayer(5,0,0, Color.parseColor("#66ffffff"));
mPaintForHighLightLrc.setColor(mCurColorForHightLightLrc);
mPaintForHighLightLrc.setTextSize(mCurSizeForHightLightLrc);
mPaintForHighLightLrc.setAntiAlias(true); mPaintForOtherLrc = new Paint();
mPaintForOtherLrc.setColor(mCurColorForOtherLrc);
mPaintForOtherLrc.setTextSize(mCurSizeForOtherLrc);
mPaintForOtherLrc.setAntiAlias(true); mPaintForTimeLine = new Paint();
mPaintForTimeLine.setColor(COLOR_FOR_TIME_LINE);
mPaintForTimeLine.setTextSize(SIZE_FOR_TIME); mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); BitmapFactory.Options options = new BitmapFactory.Options();
options.inDensity = 30;
options.inTargetDensity = 30;
arrowBitmap = BitmapFactory.decodeResource(context.getResources(), R.raw.lrc_arrow, options);
} private int mTotleDrawRow; @Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mLrcRows == null || mLrcRows.size() == 0) {
//画默认的显示文字
mPaintForOtherLrc.setTextSize(SIZE_FOR_DEFAULT_TEXT);
float textWidth = mPaintForOtherLrc.measureText(DEFAULT_TEXT);
float textX = (getWidth() - textWidth) / 2;
canvas.drawText(DEFAULT_TEXT, textX, getHeight() / 2, mPaintForOtherLrc);
return;
}
if (mTotleDrawRow == 0) {
//初始化将要绘制的歌词行数
mTotleDrawRow = (int) (getHeight() / (mCurSizeForOtherLrc + mCurPadding)) + 4;
}
//因为不需要将所有歌词画出来
int minRaw = mCurRow - (mTotleDrawRow - 1) / 2;
int maxRaw = mCurRow + (mTotleDrawRow - 1) / 2;
minRaw = Math.max(minRaw, 0); //处理上边界
maxRaw = Math.min(maxRaw, mLrcRows.size() - 1); //处理下边界
//实现渐变的最大歌词行数
int count = Math.max(maxRaw - mCurRow, mCurRow - minRaw);
if (count == 0) {
return;
}
//两行歌词间字体颜色变化的透明度
int alpha = (0xFF - 0x11) / count;
//画出来的第一行歌词的y坐标
float rowY = getHeight() / 2 + minRaw * (mCurSizeForOtherLrc + mCurPadding);
for (int i = minRaw; i <= maxRaw; i++) { if (i == mCurRow) {//画高亮歌词
//因为有缩放效果,所有需要动态设置歌词的字体大小
float textSize = mCurSizeForOtherLrc + (mCurSizeForHightLightLrc - mCurSizeForOtherLrc) * mCurFraction;
mPaintForHighLightLrc.setTextSize(textSize); String text = mLrcRows.get(i).getContent();//获取到高亮歌词
float textWidth = mPaintForHighLightLrc.measureText(text);//用画笔测量歌词的宽度
if (textWidth > getWidth()) {
//如果歌词宽度大于view的宽,则需要动态设置歌词的起始x坐标,以实现水平滚动
canvas.drawText(text, mCurTextXForHighLightLrc, rowY, mPaintForHighLightLrc);
} else {
//如果歌词宽度小于view的宽,则让歌词居中显示
float textX = (getWidth() - textWidth) / 2;
canvas.drawText(text, textX, rowY, mPaintForHighLightLrc);
}
} else {
if (i == mLastRow) {//画高亮歌词的上一句
//因为有缩放效果,所有需要动态设置歌词的字体大小
float textSize = mCurSizeForHightLightLrc - (mCurSizeForHightLightLrc - mCurSizeForOtherLrc) * mCurFraction;
mPaintForOtherLrc.setTextSize(textSize);
} else {//画其他的歌词
mPaintForOtherLrc.setTextSize(mCurSizeForOtherLrc);
}
String text = mLrcRows.get(i).getContent();
float textWidth = mPaintForOtherLrc.measureText(text);
float textX = (getWidth() - textWidth) / 2;
//如果计算出的textX为负数,将textX置为0(实现:如果歌词宽大于view宽,则居左显示,否则居中显示)
textX = Math.max(textX, 0);
//实现颜色渐变 从0xFFFFFFFF 逐渐变为 0x11FFFFFF(颜色还是白色,只是透明度变化)
int curAlpha = 255 - (Math.abs(i - mCurRow) - 1) * alpha; //求出当前歌词颜色的透明度
//mPaintForOtherLrc.setColor(0x1000000*curAlpha+0xffffff);
canvas.drawText(text, textX, rowY, mPaintForOtherLrc);
}
//计算出下一行歌词绘制的y坐标
rowY += mCurSizeForOtherLrc + mCurPadding;
} //画时间线和时间
if (mIsDrawTimeLine) {
float y = getHeight() / 2 + getScrollY();
float x = getWidth();
canvas.drawBitmap(arrowBitmap, -20, y - 41, null);
canvas.drawText(mLrcRows.get(mCurRow).getTimeStr().substring(0, 5), x - 105, y + 13, mPaintForTimeLine);
canvas.drawLine(60, y, getWidth() - 110, y, mPaintForTimeLine);
} } /**
* 是否可拖动歌词
**/
private boolean canDrag = false;
/**
* 事件的第一次的y坐标
**/
private float firstY;
/**
* 事件的上一次的y坐标
**/
private float lastY;
private float lastX; @Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
firstY = event.getRawY();
lastX = event.getRawX();
break;
case MotionEvent.ACTION_MOVE:
if (mLrcRows == null || mLrcRows.size() == 0) {
return false;
}
if (!canDrag) {
if (Math.abs(event.getRawY() - firstY) > mTouchSlop && Math.abs(event.getRawY() - firstY) > Math.abs(event.getRawX() - lastX)) {
canDrag = true;
mIsDrawTimeLine = true;
mScroller.forceFinished(true);
stopScrollLrc();
mCurFraction = 1;
}
lastY = event.getRawY();
} if (canDrag) {
float offset = event.getRawY() - lastY;//偏移量
if (getScrollY() - offset < 0) {
if (offset > 0) {
offset = offset / 3;
}
} else if (getScrollY() - offset > mLrcRows.size() * (mCurSizeForOtherLrc + mCurPadding) - mCurPadding) {
if (offset < 0) {
offset = offset / 3;
}
}
scrollBy(getScrollX(), -(int) offset);
lastY = event.getRawY();
int currentRow = (int) (getScrollY() / (mCurSizeForOtherLrc + mCurPadding));
currentRow = Math.min(currentRow, mLrcRows.size() - 1);
currentRow = Math.max(currentRow, 0);
seekTo(mLrcRows.get(currentRow).getTime(), false, false);
return true;
}
lastY = event.getRawY();
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
if (!canDrag) {
if (onLrcClickListener != null) {
onLrcClickListener.onClick();
}
} else {
if (onSeekToListener != null && mCurRow != -1) {
onSeekToListener.onSeekTo(mLrcRows.get(mCurRow).getTime());
}
if (getScrollY() < 0) {
smoothScrollTo(0, DURATION_FOR_ACTION_UP);
} else if (getScrollY() > mLrcRows.size() * (mCurSizeForOtherLrc + mCurPadding) - mCurPadding) {
smoothScrollTo((int) (mLrcRows.size() * (mCurSizeForOtherLrc + mCurPadding) - mCurPadding), DURATION_FOR_ACTION_UP);
} canDrag = false;
mIsDrawTimeLine = false;
invalidate();
}
break;
}
return true;
} /**
* 为LrcView设置歌词List集合数据
*/
@Override
public void setLrcRows(List<LrcRow> lrcRows) {
reset();
this.mLrcRows = lrcRows;
invalidate();
} /**
* 当前高亮歌词的行号
**/
private int mCurRow = -1;
/**
* 上一次的高亮歌词的行号
**/
private int mLastRow = -1; @Override
public void seekTo(int progress, boolean fromSeekBar, boolean fromSeekBarByUser) {
if (mLrcRows == null || mLrcRows.size() == 0) {
return;
}
//如果是由seekbar的进度改变触发 并且这时候处于拖动状态,则返回
if (fromSeekBar && canDrag) {
return;
}
for (int i = mLrcRows.size() - 1; i >= 0; i--) { if (progress >= mLrcRows.get(i).getTime()) {
if (mCurRow != i) {
mLastRow = mCurRow;
mCurRow = i;
log("mCurRow=i=" + mCurRow);
if (fromSeekBarByUser) {
if (!mScroller.isFinished()) {
mScroller.forceFinished(true);
}
scrollTo(getScrollX(), (int) (mCurRow * (mCurSizeForOtherLrc + mCurPadding)));
} else {
smoothScrollTo((int) (mCurRow * (mCurSizeForOtherLrc + mCurPadding)), DURATION_FOR_LRC_SCROLL);
}
//如果高亮歌词的宽度大于View的宽,就需要开启属性动画,让它水平滚动
float textWidth = mPaintForHighLightLrc.measureText(mLrcRows.get(mCurRow).getContent());
log("textWidth=" + textWidth + "getWidth()=" + getWidth());
if (textWidth > getWidth()) {
if (fromSeekBarByUser) {
mScroller.forceFinished(true);
}
log("开始水平滚动歌词:" + mLrcRows.get(mCurRow).getContent());
startScrollLrc(getWidth() - textWidth, (long) (mLrcRows.get(mCurRow).getTotalTime() * 0.6));
}
invalidate();
}
break;
}
} } /**
* 控制歌词水平滚动的属性动画
***/
private ValueAnimator mAnimator; /**
* 开始水平滚动歌词
*
* @param endX 歌词第一个字的最终的x坐标
* @param duration 滚动的持续时间
*/ private void startScrollLrc(float endX, long duration) {
if (mAnimator == null) {
mAnimator = ValueAnimator.ofFloat(0, endX);
mAnimator.addUpdateListener(updateListener);
} else {
mCurTextXForHighLightLrc = 0;
mAnimator.cancel();
mAnimator.setFloatValues(0, endX);
}
mAnimator.setDuration(duration);
// mAnimator.setStartDelay((long) (duration * 0.2)); //延迟执行属性动画
mAnimator.start();
} /**
* 停止歌词的滚动
*/
private void stopScrollLrc() {
if (mAnimator != null) {
mAnimator.cancel();
}
mCurTextXForHighLightLrc = 0;
} /**
* 高亮歌词当前的其实x轴绘制坐标
**/
private float mCurTextXForHighLightLrc;
/***
* 监听属性动画的数值值的改变
*/
AnimatorUpdateListener updateListener = new AnimatorUpdateListener() { @Override
public void onAnimationUpdate(ValueAnimator animation) {
mCurTextXForHighLightLrc = (Float) animation.getAnimatedValue();
log("mCurTextXForHighLightLrc=" + mCurTextXForHighLightLrc);
invalidate();
}
}; /**
* 设置歌词的缩放比例
*/
@Override
public void setLrcScalingFactor(float scalingFactor) {
mCurScalingFactor = scalingFactor;
mCurSizeForHightLightLrc = DEFAULT_SIZE_FOR_HIGHT_LIGHT_LRC * mCurScalingFactor;
mCurSizeForOtherLrc = DEFAULT_SIZE_FOR_OTHER_LRC * mCurScalingFactor;
mCurPadding = DEFAULT_PADDING * mCurScalingFactor;
mTotleDrawRow = (int) (getHeight() / (mCurSizeForOtherLrc + mCurPadding)) + 3;
log("mRowTotal=" + mTotleDrawRow);
scrollTo(getScrollX(), (int) (mCurRow * (mCurSizeForOtherLrc + mCurPadding)));
invalidate();
mScroller.forceFinished(true);
} /**
* 重置
*/
@Override
public void reset() {
if (!mScroller.isFinished()) {
mScroller.forceFinished(true);
}
mLrcRows = null;
scrollTo(getScrollX(), 0);
invalidate();
} /**
* 平滑的移动到某处
*
* @param dstY
*/
private void smoothScrollTo(int dstY, int duration) {
int oldScrollY = getScrollY();
int offset = dstY - oldScrollY;
mScroller.startScroll(getScrollX(), oldScrollY, getScrollX(), offset, duration);
invalidate();
} @Override
public void computeScroll() {
if (!mScroller.isFinished()) {
if (mScroller.computeScrollOffset()) {
int oldY = getScrollY();
int y = mScroller.getCurrY();
if (oldY != y && !canDrag) {
scrollTo(getScrollX(), y);
}
mCurFraction = mScroller.timePassed() * 3f / DURATION_FOR_LRC_SCROLL;
mCurFraction = Math.min(mCurFraction, 1F);
invalidate();
}
}
} /**
* 返回当前的歌词缩放比例
*
* @return
*/
public float getmCurScalingFactor() {
return mCurScalingFactor;
} private OnSeekToListener onSeekToListener; public void setOnSeekToListener(OnSeekToListener onSeekToListener) {
this.onSeekToListener = onSeekToListener;
} public interface OnSeekToListener {
void onSeekTo(int progress);
} private OnLrcClickListener onLrcClickListener; public void setOnLrcClickListener(OnLrcClickListener onLrcClickListener) {
this.onLrcClickListener = onLrcClickListener;
} public interface OnLrcClickListener {
void onClick();
} public void log(Object o) {
Log.d("LrcView", o + "");
}
}
 * 在ViewGroup里面 scrollTo,scrollBy方法移动的是子View
* 在View里面scrollTo,scrollBy方法移动的是View里面绘制的内容
* 要点:
* 1:歌词的上下平移用什么实现?
* 用Scroller实现,Scroller只是一个工具而已,
* 真正实现滚动效果的还是View的scrollTo方法
* 2:歌词的水平滚动怎么实现?
* 通过属性动画ValueAnimator控制高亮歌词绘制的x轴起始坐标 歌词与播放进度联动,只需要调用seekTo方法,原理是拿播放进度和歌词每一行前的时间作比较,只要播放进度超前,那么就滚动到歌词的相应时间上显示,理论上,只要歌词文本的时间是精确的,那么歌词就会随着
播放进度一直滚动. 触摸滑动和点击事件都是在onTouchEvent中处理的,原理是记录手指按下和抬起这段时间内Y方向的偏移量,把这个偏移量与每行文字的高度作比较,看滚动到歌词的哪个部分,然后再把播放进度调到对应的时间位置,这样
就实现了歌词进度与播放进度的完全绑定了. 当然,这些都是建立在歌词解析完成,并且获取到的前提之下,因此,歌词解析也是很重要的部分.

三.歌词解析

下载的歌词文件,看过的都知道是一种时间刻度+歌词字符串的形式;例如

这样的,既然如此,那么只需要按"["和"]"来截取字符串就可以了.来看下歌词解析类吧(歌词解析不少网友都分享过):

public class DefaultLrcParser implements ILrcParser {
private static final DefaultLrcParser istance = new DefaultLrcParser(); public static final DefaultLrcParser getIstance() {
return istance;
} private DefaultLrcParser() {
} /***
* 将歌词文件里面的字符串 解析成一个List<LrcRow>
*/
@Override
public List<LrcRow> getLrcRows(String str) { if (TextUtils.isEmpty(str)) {
return null;
}
BufferedReader br = new BufferedReader(new StringReader(str)); List<LrcRow> lrcRows = new ArrayList<>();
String lrcLine;
try {
while ((lrcLine = br.readLine()) != null) {
List<LrcRow> rows = LrcRow.createRows(lrcLine);
if (rows != null && rows.size() > 0) {
lrcRows.addAll(rows);
}
}
Collections.sort(lrcRows);
int len = lrcRows.size();
for (int i = 0; i < len - 1; i++) {
lrcRows.get(i).setTotalTime(lrcRows.get(i + 1).getTime() - lrcRows.get(i).getTime());
}
lrcRows.get(len - 1).setTotalTime(5000);
} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
if (br != null) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
} return lrcRows;
}

歌词的实体类:

/**
* 每行歌词的实体类,实现了Comparable接口,方便List<LrcRow>的sort排序
*
* @author Ligang 2014/8/19
*/
public class LrcRow implements Comparable<LrcRow> { /**
* 开始时间 为00:10:00
***/
private String timeStr;
/**
* 开始时间 毫米数 00:10:00 为10000
**/
private int time;
/**
* 歌词内容
**/
private String content;
/**
* 该行歌词显示的总时间
**/
private int totalTime; public long getTotalTime() {
return totalTime;
} public void setTotalTime(int totalTime) {
this.totalTime = totalTime;
} public String getTimeStr() {
return timeStr;
} public void setTimeStr(String timeStr) {
this.timeStr = timeStr;
} public int getTime() {
return time;
} public void setTime(int time) {
this.time = time;
} public String getContent() {
return content;
} public void setContent(String content) {
this.content = content;
} public LrcRow() {
super();
} public LrcRow(String timeStr, int time, String content) {
super();
this.timeStr = timeStr;
this.time = time;
this.content = content;
} /**
* 将歌词文件中的某一行 解析成一个List<LrcRow>
* 因为一行中可能包含了多个LrcRow对象
* 比如 [03:33.02][00:36.37]当鸽子不再象征和平 ,就包含了2个对象
*
* @param lrcLine
* @return
*/
public static final List<LrcRow> createRows(String lrcLine) {
if (!lrcLine.startsWith("[")) {
return null;
}
//最后一个"]"
int lastIndexOfRightBracket = lrcLine.lastIndexOf("]");
//歌词内容
String content = lrcLine.substring(lastIndexOfRightBracket + 1, lrcLine.length());
//截取出歌词时间,并将"[" 和"]" 替换为"-" [offset:0]
Log.e("歌词","lrcLine=" + lrcLine);
// -03:33.02--00:36.37-
String times = lrcLine.substring(0, lastIndexOfRightBracket + 1).replace("[", "-").replace("]", "-");
String[] timesArray = times.split("-");
List<LrcRow> lrcRows = new ArrayList<LrcRow>();
for (String tem : timesArray) {
if (TextUtils.isEmpty(tem.trim())) {
continue;
}
//
try {
LrcRow lrcRow = new LrcRow(tem, formatTime(tem), content);
lrcRows.add(lrcRow);
} catch (Exception e) {
Log.w("LrcRow", e.getMessage());
}
}
return lrcRows;
} /****
* 把歌词时间转换为毫秒值 如 将00:10.00 转为10000
*
* @param timeStr
* @return
*/
private static int formatTime(String timeStr) {
timeStr = timeStr.replace('.', ':');
String[] times = timeStr.split(":"); return Integer.parseInt(times[0]) * 60 * 1000
+ Integer.parseInt(times[1]) * 1000
+ Integer.parseInt(times[2]);
} @Override
public int compareTo(LrcRow anotherLrcRow) {
return (int) (this.time - anotherLrcRow.time);
} @Override
public String toString() {
return "LrcRow [timeStr=" + timeStr + ", time=" + time + ", content="
+ content + "]";
} }

解析成LrcRow后按照时间顺序排列,得到的集合作为一篇歌词的解析结果.

四.歌词显示

歌词显示的逻辑也有要注意的地方,下面画了个简图

   private class RequestLrc implements Runnable {

        private TrackEntity musicInfo;
private boolean stop; RequestLrc(TrackEntity info) {
this.musicInfo = info;
} public void stop() {
stop = true;
} @Override
public void run() {
String url;
if (musicInfo == null || musicInfo.getTrackId() == 0) {
return;
}
String action = MUSIC_DETAIL + musicInfo.getTrackId();
AscHttpHelper helper = new AscHttpHelper(getContext());
url = helper.wrapUrl(action, null);
Log.i(TAG, "请求url" + url);
String resposeString = HttpUtil.getResposeString(url);
Log.i(TAG, "请求结果" + resposeString);
Gson gson = new Gson();
SongDetailBean mDetailSong = gson.fromJson(resposeString, SongDetailBean.class);
if (mDetailSong == null || mDetailSong.data == null || mDetailSong.data.musicInfoList == null) {
return;
}
if (!stop) {
File file = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + LRC_PATH + musicInfo.getTrackId());
String lrc;
try {
lrc = HttpUtil.getResposeString(mDetailSong.data.musicInfoList.get(0).lyricUrl);
if (!TextUtils.isEmpty(lrc)) {
if (!file.exists()) {
file.createNewFile();
}
writeToFile(file, lrc);
}
} catch (Exception e) {
e.printStackTrace();
mTryGetLrc.post(new Runnable() {
@Override
public void run() {
mTryGetLrc.setVisibility(View.VISIBLE);
mLrcView.reset();
}
});
}
} }
} private synchronized void writeToFile(File file, String lrc) {
FileOutputStream outputStream = null;
try {
outputStream = new FileOutputStream(file);
outputStream.write(lrc.getBytes());
} catch (Exception e) {
e.printStackTrace();
mTryGetLrc.setVisibility(View.VISIBLE);
mLrcView.reset();
} finally {
if (outputStream != null) {
try {
outputStream.flush();
outputStream.close();
final List<LrcRow> list = getLrcRows();
mTryGetLrc.post(new Runnable() {
@Override
public void run() {
if (list != null && list.size() > 0) {
mTryGetLrc.setVisibility(View.INVISIBLE);
mLrcView.setLrcRows(list);
}
}
});
} catch (IOException e) {
e.printStackTrace();
mTryGetLrc.setVisibility(View.VISIBLE);
mLrcView.reset();
}
}
}
}

五.体会与总结

千里之行,始于足下;任何看起来很棒的效果都是一行一行基础代码在发挥作用,他们相互关联,却又相互独立.

这两天来看,上面的歌词显示逻辑还是存在一些问题的,比如切换到下一首的时候,偶现显示了上一首歌曲的歌词.好的,我去修bug了.

Android 歌词显示的更多相关文章

  1. Android ImageView显示本地图片

    Android ImageView 显示本地图片 布局文件 <?xml version="1.0" encoding="utf-8"?> <R ...

  2. unity,将camera设为don't clear在android上会显示不正常

    将camera设置为don't clear,在pc和ios上显示没问题,但在android上显示不正常,改为only depth可以.

  3. Android中显示网页的多种方式

    在android中显示页面主要有两种方式,一种是在Activity里面直接显示网页,另一种是调用浏览器显示网页.方式不同,使用的方法也不同,下面我们分别讲解. 一.在Activity里面直接显示网页 ...

  4. app开发历程——android手机显示服务器端图片思路

    以前自己都不知道怎么去显示服务器端的图片,还好在apkbus论坛上找到一个特别简单的例子.虽然一天天忙忙碌碌,但是自己内心其实有一种想逃的心里,说不定哪天就会冒出来. 1.首先服务器端图片 这里的Im ...

  5. Android 图片显示

    一.Android手机显示图片 若R.G.B每种颜色使用一个字节(8bit)表示,每幅图像可以有1670万种颜色:若R.G.B每种颜色使用两个字节(16bit)表示,每幅图像可以有10的12次方种颜色 ...

  6. html5图片上传时IOS和Android均显示摄像头拍照和图片选择

    最近在做信开发时,发现<input type="file" />在IOS中可以拍照或从照片图库选择,而Android系统则显示资源管理器,无拍照选项,网上查找资料,改为 ...

  7. Android中显示gif动态图片

    在android中显示一个静态图片比如png jpg等等都很方便,但是如果要显示一个gif 动态图片就需要进行一些处理. 本文是采用自定义view 然后进行重新onDraw方法来实现 首先自定义Vie ...

  8. Android系统显示原理

    Android的显示过程可以概括为:Android应用程序把经过测量.布局.绘制后的surface缓存数据,通过SurfaceFlinger把数据渲染到屏幕上,通过Android的刷新机制来刷新数据. ...

  9. android textview 显示一行,且超出自动截断,显示"..."

    android textview 显示一行,且超出自动截断,显示"..." <TextView android:layout_width="wrap_content ...

随机推荐

  1. Flask实战第57天:UEditor编辑器集成以及配置上传文件到七牛

    相关链接 UEditor:http://ueditor.baidu.com/website/​ 下载地址:http://ueditor.baidu.com/website/download.html# ...

  2. angularjs学习笔记3-directive中scope的绑定修饰符

    在angularjs中,一个directive返回一个对象,对象存在很多属性,并且可以在directive中自定义自己的scope,而使用自己的scope是为了防止一个directive被使用在多个地 ...

  3. 【dfs序+AC自动机+树状数组】BZOJ2434-[Noi2011]阿狸的打字机

    [题目大意] 输入一个字符串,其中:(1)a..z:在字符串末尾添加当前字符(2)P:输出当前字符串(3)B:从当前字符串末尾删去一个字符. 给出m组查询,输出第i个输出的字符串在第j个输出的字符串内 ...

  4. [JZOJ3484]密码

    题目大意: 给你一个很长的字符串a(|a|<=300000),一个比较短的字符串b(|b|<=200),请你搞一些破坏. 你可以从a的两边去掉一些字符使得b仍是a的一个字串,问有多少种方案 ...

  5. [标准性文档]WEB应用安全验证标准

    这是根据OWASP ASVS翻译过来的,可作为WEB应用开发的验收标准(未包含具体细节),值得注意的是,验收标准并非越严格越好,应根据企业自身的业务需求判定. WEB应用安全验证标准 安全验证等级的定 ...

  6. 工作中常用Lixu命令学习笔记

    对于Linux,我是菜鸟,也是在工作中了才开始慢慢接触,用Linux的人都我都会觉得屌屌的,现在把工作中常用的一些Linux命令记录一下,供以后学习和参考. cd 这可能是我觉得Linux最简单的一个 ...

  7. cocos2d-x 3.0 CCTime 的改动

    在cocos3.0 中,去掉了CCTimer这个类. 我们须要把之前的代码改动为 定义时间的变量是 struct  timeval  start 1.获取时间的方式是: gettimeofday(&a ...

  8. delphi VCL研究之消息分发机制-delphi高手突破读书笔记

    1.VCL 概貌 先看一下VCL类图的主要分支,如图4.1所示.在图中可以看到,TObject是VCL的祖先类,这也是Object Pascal语言所规定的.但实际上,TObject以及TObject ...

  9. pyPdf - 用Python方便的处理PDF文档

    pyPdf库 ( http://pybrary.net/pyPdf/ ) ,操作起来相当直接易懂,把代码贴在这儿,做个记录.  1 from pyPdf import PdfFileWriter, P ...

  10. sqlserver中事务总结:begin tran,rollback tran,commit tran

     第1个相关用法:摘自:https://shiyousan.com/post/f13d29b7-0d87-4168-bd8b-8b28b0991b5a 以下是出现错误的SQL部分语句: 此错误的原因是 ...