Android 歌词显示
一.概述
项目中设计到歌词显示的问题,这一块之前没有涉及过,只是套用过一个开源的项目,效果还行,于是想到拿来稍作修改,以适应项目需求.
二.歌词控件
先来看下这个自定义控件写的歌词控件吧:
- 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 歌词显示的更多相关文章
- Android ImageView显示本地图片
Android ImageView 显示本地图片 布局文件 <?xml version="1.0" encoding="utf-8"?> <R ...
- unity,将camera设为don't clear在android上会显示不正常
将camera设置为don't clear,在pc和ios上显示没问题,但在android上显示不正常,改为only depth可以.
- Android中显示网页的多种方式
在android中显示页面主要有两种方式,一种是在Activity里面直接显示网页,另一种是调用浏览器显示网页.方式不同,使用的方法也不同,下面我们分别讲解. 一.在Activity里面直接显示网页 ...
- app开发历程——android手机显示服务器端图片思路
以前自己都不知道怎么去显示服务器端的图片,还好在apkbus论坛上找到一个特别简单的例子.虽然一天天忙忙碌碌,但是自己内心其实有一种想逃的心里,说不定哪天就会冒出来. 1.首先服务器端图片 这里的Im ...
- Android 图片显示
一.Android手机显示图片 若R.G.B每种颜色使用一个字节(8bit)表示,每幅图像可以有1670万种颜色:若R.G.B每种颜色使用两个字节(16bit)表示,每幅图像可以有10的12次方种颜色 ...
- html5图片上传时IOS和Android均显示摄像头拍照和图片选择
最近在做信开发时,发现<input type="file" />在IOS中可以拍照或从照片图库选择,而Android系统则显示资源管理器,无拍照选项,网上查找资料,改为 ...
- Android中显示gif动态图片
在android中显示一个静态图片比如png jpg等等都很方便,但是如果要显示一个gif 动态图片就需要进行一些处理. 本文是采用自定义view 然后进行重新onDraw方法来实现 首先自定义Vie ...
- Android系统显示原理
Android的显示过程可以概括为:Android应用程序把经过测量.布局.绘制后的surface缓存数据,通过SurfaceFlinger把数据渲染到屏幕上,通过Android的刷新机制来刷新数据. ...
- android textview 显示一行,且超出自动截断,显示"..."
android textview 显示一行,且超出自动截断,显示"..." <TextView android:layout_width="wrap_content ...
随机推荐
- 洛谷 P2947 [USACO09MAR]向右看齐Look Up【单调栈】
题目描述 Farmer John's N (1 <= N <= 100,000) cows, conveniently numbered 1..N, are once again stan ...
- Linux命令之ftp
ftp [-pinegvd] [host] pftp [-inegvd] [host] 用户通过ftp这个程序来使用Internet上的标准文件传输协议(FTP).本程序允许用户向远端网站发送文件,或 ...
- 04、Unity 5--全局光照技术
本文整理自Unity全球官方网站,原文:UNITY 5 - LIGHTING AND RENDERING 简介全局光照,简称GI,是一个用来模拟光的互动和反弹等复杂行为的算法,要精确的仿真全局光照非常 ...
- 【BZOJ 4567】【SCOI 2016】背单词
http://www.lydsy.com/JudgeOnline/problem.php?id=4567 贪心. 任何不用第一种情况的方案吃的泡椒数都小于\(n^2\),所以最小泡椒数的方案一定不包含 ...
- BZOJ 4756 [Usaco2017 Jan]Promotion Counting(线段树合并)
[题目链接] http://www.lydsy.com/JudgeOnline/problem.php?id=4756 [题目大意] 给出一棵树,对于每个节点,求其子树中比父节点大的点个数 [题解] ...
- 【枚举约数】Gym - 101412A - Ginkgo Numbers
给你一堆定义,问你在那个定义下,<p,q>是不是素数.其实那堆定义都不用管,只要看最下面给你的提示即可. 根据,只要把m^2+n^2当一个整体,去枚举(p^2+q^2)的约数即可,然后再枚 ...
- git远程仓库创建及权限管理(二)多个项目
本文介绍ubutu下使用gitolite实现多项目的权限管理1.安装git sudo apt-get install git 2.设置Git的user name和email: git config - ...
- Educational Codeforces Round 9 A. Grandma Laura and Apples 水题
A. Grandma Laura and Apples 题目连接: http://www.codeforces.com/contest/632/problem/A Description Grandm ...
- Web安全测试指南--信息泄露
5.4.1.源代码和注释: 编号 Web_InfoLeak_01 用例名称 源代码和注释检查测试 用例描述 在浏览器中检查目标系统返回的页面是否存在敏感信息. 严重级别 中 前置条件 1. 目标we ...
- VUE2.0学习总结
摘要: 年后公司项目开始上vue2.0,自己对学习进行了总结,希望对大家有帮助! VUE2.0学习 vue介绍 vue是什么? https://vuefe.cn/guide vue也是一个数据驱动框架 ...