zoukankan      html  css  js  c++  java
  • 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了.

  • 相关阅读:
    pku3486Computers 动态规划
    pku2229sumsets(zjgsu,分花)
    pku2663Tri Tiling递推题
    pku1015Jury Compromise 动态规划
    pku3508Hide That Number一道加密算法题
    pku动态规划题目列表
    浅谈XXE攻击
    PHP核心配置详解
    SSRF漏洞用到的其他协议(dict协议,file协议)
    php中使用CURL之php curl详解
  • 原文地址:https://www.cnblogs.com/fuyaozhishang/p/7297746.html
Copyright © 2011-2022 走看看