音乐播放器
来源:互联网 发布:如何使用花生壳域名 编辑:程序博客网 时间: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
- android音乐播放器播放音乐卡
- Android 音乐播放 类似音乐播放器
- flash音乐播放器
- 常见音乐播放器
- WEB音乐播放器
- Google 音乐播放器
- 单片机音乐播放器
- 简易音乐播放器
- 网页音乐播放器
- 音乐定时播放器
- android 音乐播放器
- 音乐播放器代码
- 音乐播放器01
- 个人音乐播放器
- YOYOPlayer音乐播放器
- Qt音乐播放器
- Android 音乐播放器
- android 音乐播放器
- 使用OTL连接数据库有感篇(一)
- JavaScript——学习笔记(一)
- oracle 忘记密码处理
- SpringMVC 文件上传
- POJ 1703Find them, Catch them
- 音乐播放器
- php中\r \r\n \t的区别
- Cordova-----2、创建Cordova项目
- 最强 Android Studio 使用小技巧和快捷键(二)
- 关于jmeter中跨线程组 变量值传递的方法
- #python 线程,协程
- 用户画像数据建模方法(转)
- 【论文笔记】Region-based Convolutional Networks for Accurate Object Detection and Segmentation
- Ubuntu 彻底删除 MYSQL 然后重装 MYSQL