音乐播放器

来源:互联网 发布:如何使用花生壳域名 编辑:程序博客网 时间:2024/06/05 18:52

这是我以前的一个个人项目。那段时间工作不忙,就写了个音乐播放器。基本功能都有:总列表、喜爱列表、自建列表、艺术家列表、在线搜索歌曲和歌词、歌曲下载、歌词刷新和高亮显示等等。Android基础知识也基本都囊括了,包括四大组件、多线程断点下载、自定义View、SQLite、文件存储、XML生成和解析、OKHttp、GSON等等。先上图:

还有一些其它功能没有截图。

个人认为,歌词刷新这块是个难点,就把代码贴出来看看:

public class LyricView extends TextView {    private Context context;    private int height;    private int halfHeight;    private boolean hasLyric = false;    private String noLyricStr;    private Animation animation;    private Paint normalPaint; //非当前歌词画笔    private Paint centerBasePaint; //当前歌词基础画笔    private Paint centerFlashPaint; //当前歌词flash画笔    private int flag = -1; //如果flag和当前lycIndex一致,说明歌词位置还没有变,那么就不刷新,否则刷新。    private int lyricIndex = -1; //当前歌词的位置    private int LineMargin = 102; //每一行的间隔    private int verticalOffset = 60;//竖直偏移    private String lyricStr;//将要画的歌词行    private float startX;//将要画的歌词的x坐标    private float startY;//将要画的歌词的y坐标    private long position;//歌曲播放位置    private LyricBean bean;    private ArrayList<LyricBean> lyricList;    public LyricView(Context context) {        super(context);        this.context = context;        init();    }    public LyricView(Context context, AttributeSet attr) {        super(context, attr);        this.context = context;        init();    }    public LyricView(Context context, AttributeSet attr, int i) {        super(context, attr, i);        this.context = context;        init();    }    private void init() {        hasLyric = false;        noLyricStr = context.getString(R.string.no_lyric);        animation = AnimationUtils.loadAnimation(context, R.anim.lyric_translate);        setFocusable(true);        // 非当前歌词部分        normalPaint = new Paint();        normalPaint.setAntiAlias(true);        normalPaint.setTextSize(35);        // 当前歌词基础部分        centerBasePaint = new Paint();        centerBasePaint.setAntiAlias(true);        centerBasePaint.setTextSize(45);        // 当前歌词flash部分        centerFlashPaint = new Paint();        centerFlashPaint.setAntiAlias(true);        centerFlashPaint.setTextSize(45);        initLyricColor();    }    // 1为黑色,2为红色,3为绿色,4为蓝色,5为白色    private void initLyricColor() {        int color = App.getLyricColor(context);        switch (color) {            case 1:                normalPaint.setColor(Color.BLACK);                centerBasePaint.setColor(Color.BLACK);                centerFlashPaint.setColor(Color.RED);                break;            case 2:                normalPaint.setColor(Color.RED);                centerBasePaint.setColor(Color.RED);                centerFlashPaint.setColor(Color.GREEN);                break;            case 3:                normalPaint.setColor(Color.GREEN);                centerBasePaint.setColor(Color.GREEN);                centerFlashPaint.setColor(Color.RED);                break;            case 4:                normalPaint.setColor(Color.BLUE);                centerBasePaint.setColor(Color.BLUE);                centerFlashPaint.setColor(Color.RED);                break;            case 5:                normalPaint.setColor(Color.WHITE);                centerBasePaint.setColor(Color.WHITE);                centerFlashPaint.setColor(Color.RED);                break;            default:                normalPaint.setColor(Color.WHITE);                centerBasePaint.setColor(Color.WHITE);                centerFlashPaint.setColor(Color.RED);                break;        }    }    @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        if (hasLyric) {            drawAllLine(canvas);            drawCenterLine(canvas);        } else {            startX = (getWidth() - normalPaint.measureText(noLyricStr)) / 2;            canvas.drawText(noLyricStr, startX, halfHeight - 80, normalPaint);        }    }    private void drawAllLine(Canvas canvas) {        if (lyricIndex < 0) {            lyricIndex = 0;        }        // 画当前行歌词        lyricStr = lyricList.get(lyricIndex).getLrcBody();        startX = (getWidth() - centerBasePaint.measureText(lyricStr)) / 2;        startY = halfHeight - verticalOffset;        canvas.drawText(lyricStr, startX, startY, centerBasePaint);        // 画当前行之前的歌词        float tempY = halfHeight;        for (int i = lyricIndex - 1; i >= 0; i--) {            // 向上推移            tempY = tempY - LineMargin;            if (tempY < 0) {                break;            }            lyricStr = lyricList.get(i).getLrcBody();            startX = (getWidth() - normalPaint.measureText(lyricStr)) / 2;            startY = tempY - verticalOffset;            canvas.drawText(lyricStr, startX, startY, normalPaint);            // canvas.translate(0, LineMargin);        }        // 画当前行之后的歌词        tempY = halfHeight;        for (int i = lyricIndex + 1; i < lyricList.size(); i++) {            // 往下推移            tempY = tempY + LineMargin;            if (tempY > (height - 160)) {                break;            }            lyricStr = lyricList.get(i).getLrcBody();            startX = (getWidth() - normalPaint.measureText(lyricStr)) / 2;            startY = tempY - verticalOffset;            canvas.drawText(lyricStr, startX, startY, normalPaint);            // canvas.translate(0, LineMargin);        }    }    private void drawCenterLine(Canvas canvas) {        if (lyricIndex < 0) {            lyricIndex = 0;        }        canvas.save();        bean = lyricList.get(lyricIndex);        lyricStr = bean.getLrcBody();        float lyricWidth = centerFlashPaint.measureText(lyricStr);//歌词行的宽度        startX = (getWidth() - lyricWidth) / 2;        startY = halfHeight - verticalOffset;        long time = position - bean.getBeginTime();//当前歌词行已播放的时间        double percent = (time * 1.0 + 10) / bean.getLineTime();//10ms偏移,让flash效果比播放进度稍微超前        if (percent > 1) {            percent = 1;        } else if (percent < 0) {            percent = 0;        }        // flash效果        Paint.FontMetrics fm = centerFlashPaint.getFontMetrics();        int textHeight = (int) Math.ceil(fm.bottom - fm.top) + 20;//当前歌词行的高度,加20是为了保证将文字的上下边缘全部切进来        canvas.clipRect(startX, halfHeight - verticalOffset - textHeight / 2, (float) (startX + lyricWidth * percent), halfHeight - verticalOffset + textHeight / 2);        // 画当前歌词        canvas.drawText(lyricStr, startX, startY, centerFlashPaint);        canvas.restore();    }    private void initView() {        flag = -1;        lyricIndex = 0;        postInvalidate();    }    public void setLyricList(ArrayList<LyricBean> lyricList) {        if (lyricList != null && lyricList.size() > 0) {            hasLyric = true;            initView();        } else {            hasLyric = false;            setNoLyric();        }        this.lyricList = lyricList;    }    public void setNoLyric() {        hasLyric = false;        postInvalidate();    }    public void setHasLyric(boolean hasLyric) {        this.hasLyric = hasLyric;    }    public void refresh(int position) {        if (!hasLyric) {            return;        }        this.position = position;        updateLyricIndex(position);        if (flag != lyricIndex) {            flag = lyricIndex;            this.startAnimation(animation);        } else {            postInvalidate();        }    }    private void updateLyricIndex(int position) {        int size = lyricList.size();        for (int i = 0; i < size; i++) {            if (lyricList.get(i).getBeginTime() > position) {                lyricIndex = i - 1;                return;            }        }        if (position >= lyricList.get(lyricList.size() - 1).getBeginTime()) {            lyricIndex = lyricList.size() - 1;        }    }    public void clear() {        if (lyricList != null) {            lyricList.clear();        }        lyricList = null;        normalPaint = null;        centerBasePaint = null;        centerFlashPaint = null;        if (animation != null) {            animation.cancel();        }        animation = null;    }    protected void onSizeChanged(int w, int h, int ow, int oh) {        super.onSizeChanged(w, h, ow, oh);        height = h;        halfHeight = height / 2;    }    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        super.onMeasure(widthMeasureSpec, heightMeasureSpec);        int width = MeasureSpec.getSize(widthMeasureSpec);        int height = MeasureSpec.getSize(heightMeasureSpec);        setMeasuredDimension(width, height);    }}
其中,drawAllLine是画出所有行的歌词,drawCenterLine是画出中间高亮行的歌词,歌词从左向右变红的效果是用canvas.clipRect方法实现的。
上拉下拉回弹的效果的实现:
public class SwipeScrollView extends ViewGroup {    private View target;    private View scrollChild;    private int verticalLength = 0;    private int dragState = 0;    private ViewDragHelper viewDragHelper;    public SwipeScrollView(Context context) {        this(context, null);        init();    }    public SwipeScrollView(Context context, AttributeSet attrs) {        super(context, attrs);        init();    }    public SwipeScrollView(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        init();    }    private void init() {        viewDragHelper = ViewDragHelper.create(this, 1.0f, new ViewDragHelperCallBack());    }    private void ensureTarget() {        if (target == null) {            if (getChildCount() > 1) {                throw new IllegalStateException("SwipeBackLayout must contains only one direct child");            }            target = getChildAt(0);            if (scrollChild == null && target != null) {                if (target instanceof ViewGroup) {                    findScrollView((ViewGroup) target);                } else {                    scrollChild = target;                }            }        }    }    /**     * Find out the scrollable child view from a ViewGroup.     *     * @param viewGroup     */    private void findScrollView(ViewGroup viewGroup) {        scrollChild = viewGroup;        if (viewGroup.getChildCount() > 0) {            int count = viewGroup.getChildCount();            View child;            for (int i = 0; i < count; i++) {                child = viewGroup.getChildAt(i);                if (child instanceof AbsListView || child instanceof ScrollView || child instanceof ViewPager || child instanceof WebView) {                    scrollChild = child;                    return;                }            }        }    }    @Override    protected void onLayout(boolean changed, int l, int t, int r, int b) {        int width = getMeasuredWidth();        int height = getMeasuredHeight();        if (getChildCount() == 0) {            return;        }        View child = getChildAt(0);        int childWidth = width - getPaddingLeft() - getPaddingRight();        int childHeight = height - getPaddingTop() - getPaddingBottom();        int childLeft = getPaddingLeft();        int childTop = getPaddingTop();        int childRight = childLeft + childWidth;        int childBottom = childTop + childHeight;        child.layout(childLeft, childTop, childRight, childBottom);    }    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        super.onMeasure(widthMeasureSpec, heightMeasureSpec);        if (getChildCount() > 1) {            throw new IllegalStateException("SwipeBackLayout must contains only one direct child.");        }        if (getChildCount() > 0) {            int measureWidth = MeasureSpec.makeMeasureSpec(getMeasuredWidth() - getPaddingLeft() - getPaddingRight(), MeasureSpec.EXACTLY);            int measureHeight = MeasureSpec.makeMeasureSpec(getMeasuredHeight() - getPaddingTop() - getPaddingBottom(), MeasureSpec.EXACTLY);            getChildAt(0).measure(measureWidth, measureHeight);        }    }    @Override    protected void onSizeChanged(int w, int h, int oldw, int oldh) {        super.onSizeChanged(w, h, oldw, oldh);        verticalLength = h;    }    @Override    public boolean onInterceptTouchEvent(MotionEvent ev) {        boolean handled = false;        ensureTarget();        if (isEnabled()) {            handled = viewDragHelper.shouldInterceptTouchEvent(ev);        } else {            viewDragHelper.cancel();        }        return !handled ? super.onInterceptTouchEvent(ev) : handled;    }    @Override    public boolean onTouchEvent(MotionEvent event) {        viewDragHelper.processTouchEvent(event);        return true;    }    @Override    public void computeScroll() {        if (viewDragHelper.continueSettling(true)) {            ViewCompat.postInvalidateOnAnimation(this);        }    }    public boolean canChildScrollUp() {        return ViewCompat.canScrollVertically(scrollChild, 1);    }    public boolean canChildScrollDown() {        return ViewCompat.canScrollVertically(scrollChild, -1);    }    private class ViewDragHelperCallBack extends ViewDragHelper.Callback {        @Override        public boolean tryCaptureView(View child, int pointerId) {            return child == target;        }        @Override        public int getViewVerticalDragRange(View child) {            return verticalLength;        }        @Override        public int clampViewPositionVertical(View child, int top, int dy) {            int topBound;            int bottomBound;            int result = 0;            if (dy > 0 && !canChildScrollDown() && top > 0) {//下拉                topBound = child.getTop();                bottomBound = verticalLength;                result = Math.min(Math.max(top, topBound), bottomBound);            } else if (dy < 0 && !canChildScrollUp() && top < 0) {//上拉                topBound = -verticalLength;                bottomBound = child.getTop();                result = Math.min(Math.max(top, topBound), bottomBound);            } else if (dy < 0 && !canChildScrollDown() && top > 0) {//回到顶部                topBound = -verticalLength;                bottomBound = child.getTop();                result = Math.min(Math.max(top, topBound), bottomBound);            } else if (dy > 0 && !canChildScrollUp() && top < 0) {//回到底部                topBound = child.getTop();                bottomBound = verticalLength;                result = Math.min(Math.max(top, topBound), bottomBound);            }            return result;        }        @Override        public void onViewDragStateChanged(int state) {            if (state == dragState) {                return;            }            dragState = state;        }        @Override        public void onViewReleased(View releasedChild, float xvel, float yvel) {            if (viewDragHelper.settleCapturedViewAt(0, 0)) {                ViewCompat.postInvalidateOnAnimation(SwipeScrollView.this);            }        }    }}
其实主要就是利用了ViewDragHelper这个类,这个类很强大,能处理很多复杂的手势。

其它的代码比较多,就不一一贴出了。

0 0