本地音乐播放器

基本功能:

1.播放音乐跟歌词同步

2.根据手势,移动歌词

3.音乐和拖动条同步

点击下载地址

下面贴出核心的歌词视图代码:

/**
 * 歌词视图
 */
public class LRCView extends View implements
		android.view.GestureDetector.OnGestureListener {
	private String Tag = "LRCView";
	private Context context;
	// 手势监听类
	private GestureDetector gesDet;
	private List<LrcBean> lrcList;
	private LrcBean lrcBean;
	private Paint curPaint;
	private Paint nearPaint;
	// 当前行数
	private int curLine = 0;
	// 字体大小
	private int wSize = 0;
	// 行间隔
	private int wInterval = 15;
	// 当前时间
	private long curTime = 0;
	// 移动歌词
	private boolean moveTouch = false;

	public LRCView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		// TODO Auto-generated constructor stub
		this.context = context;
		init();
	}

	public LRCView(Context context, AttributeSet attrs) {
		super(context, attrs);
		// TODO Auto-generated constructor stub
		this.context = context;
		init();
	}

	public LRCView(Context context) {
		super(context);
		// TODO Auto-generated constructor stub
		this.context = context;
		init();
	}

	public List<LrcBean> getLrcList() {
		return lrcList;
	}

	public void setLrcList(List<LrcBean> lrcList) {
		this.lrcList = lrcList;
	}

	public int getwSize() {
		return wSize;
	}

	public void setwSize(int wSize) {
		this.wSize = wSize;
		// 重新修改字体大小 间距
		curPaint.setTextSize(wSize);
		nearPaint.setTextSize(wSize > 15 ? wSize - 5 : 6);
		wInterval = wSize > 15 ? wSize - 10 : 6;
	}

	public int getCurLine() {
		return curLine;
	}

	public void setCurLine(int curLine) {
		this.curLine = curLine;
	}

	public boolean isMoveTouch() {
		return moveTouch;
	}

	public void setMoveTouch(boolean moveTouch) {
		this.moveTouch = moveTouch;
	}

	public void init() {
		curPaint = new Paint();
		// 设置文本对其 居中
		curPaint.setTextAlign(Paint.Align.CENTER);
		// 设置颜色
		curPaint.setColor(Color.GREEN);
		// 抗锯齿
		curPaint.setAntiAlias(true);
		// 设定使用图像抖动处理
		curPaint.setDither(true);
		// 透明度
		curPaint.setAlpha(180);
		// 字体大小
		curPaint.setTextSize(wSize);

		nearPaint = new Paint();
		nearPaint.setTextAlign(Paint.Align.CENTER);
		nearPaint.setColor(Color.BLUE);
		nearPaint.setAntiAlias(true);
		nearPaint.setDither(true);
		nearPaint.setAlpha(180);
		nearPaint.setTextSize(wSize > 15 ? wSize - 5 : 6);

		gesDet = new GestureDetector(this);
	}

	@Override
	protected void onDraw(Canvas canvas) {
		// TODO Auto-generated method stub
		super.onDraw(canvas);
		// 判空处理 ,当触摸屏幕滑动手势的时候 停止播放
		if (null == lrcList) {
			return;
		}

		// 当前正在唱的行
		lrcBean = lrcList.get(curLine);
		canvas.drawText(lrcBean.getLrcSentence(), this.getWidth() / 2,
				this.getHeight() / 2, curPaint);

		// 当移动歌词时 显示线条
		long cur = Utils.getCurTimeMillions();
		if (moveTouch) {
			float[] arr = Utils.getLinesArr(getWidth(), getHeight());
			canvas.drawLines(arr, 0, arr.length, curPaint);
			// 暂停播放
			((MainActivity) context).setPausePlayer();
		} else {
			// 播放
			((MainActivity) context).setStartPlayer();
		}

		// 一半的行数,跟顶和底留一行的空隙
		int half = (int) (getHeight() / 2 / (wSize + wInterval));
		// 绘制上下的行
		for (int i = 1; i < half; i++) {
			if (curLine - i >= 0) {
				lrcBean = lrcList.get(curLine - i);
				canvas.drawText(lrcBean.getLrcSentence(), getWidth() / 2,
						getHeight() / 2 - (wSize + wInterval) * i, nearPaint);
			}
			if (curLine + i < lrcList.size()) {
				lrcBean = lrcList.get(curLine + i);
				canvas.drawText(lrcBean.getLrcSentence(), getWidth() / 2,
						getHeight() / 2 + (wSize + wInterval) * i, nearPaint);
			}
		}
	}

	@Override
	public boolean onTouchEvent(MotionEvent event) {
		// TODO Auto-generated method stub
		// 将事件交给手势管理
		gesDet.onTouchEvent(event);
		return true;
	}

	// 手指按下
	@Override
	public boolean onDown(MotionEvent e) {
		// TODO Auto-generated method stub
		return false;
	}

	// 手指飞速滑动
	/**
	 * e1:初次触发 e2:每次触发 x ,y:滑动的移动速度(上滑为 负,而歌词行数为正)
	 */
	@Override
	public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
			float velocityY) {
		// TODO Auto-generated method stub
		// 隐藏拖动条
		((MainActivity) context).setVisibSB(false);
		// 行变化量
		int line = 0;
		// 行数变化量限制
		line = (int) (velocityY / 3);
		// 最大的变化行数 为5
		if (Math.abs(line) > 5) {
			line = -Math.abs(line) / line * 5;
		}
		Log.d(Tag, "Fling:" + line);
		if (curLine + line >= 0 && curLine + line < lrcList.size()) {
			((MainActivity) context).setCurLine(line);
		}

		curTime = Utils.getCurTimeMillions();
		return false;
	}

	// 手指滑动 100s返回一个状态 一般手指距离 10-30
	/**
	 * e1:初次触发 e2:每次触发 x ,y:两次滑动的间距(start-end)
	 */
	@Override
	public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
			float distanceY) {
		// TODO Auto-generated method stub
		((MainActivity) context).setVisibSB(false);
		int line = 0;
		// 行数变化量限制
		line = (int) (distanceY / 3);
		// 最大的变化行数 为5
		if (Math.abs(line) > 5) {
			line = Math.abs(line) / line * 5;
		}
		Log.d(Tag, "Scroll:" + line);
		if (curLine + line >= 0 && curLine + line < lrcList.size()) {
			((MainActivity) context).setCurLine(line);
		}

		curTime = Utils.getCurTimeMillions();
		return false;
	}

	// 手指单机抬起
	@Override
	public boolean onSingleTapUp(MotionEvent e) {
		// TODO Auto-generated method stub
		if (((MainActivity) context).isVisibSB()) {
			((MainActivity) context).setVisibSB(false);
		} else {
			((MainActivity) context).setVisibSB(true);
		}
		return false;
	}

	@Override
	public void onLongPress(MotionEvent e) {
		// TODO Auto-generated method stub

	}

	@Override
	public void onShowPress(MotionEvent e) {
		// TODO Auto-generated method stub

	}
}

下面是运行效果:

正常运行:

本地音乐播放器_第1张图片

单击屏幕(会显示拖动条):

本地音乐播放器_第2张图片

手势滑动(会显示线条):

本地音乐播放器_第3张图片


对这个进行一些说明吧。

在活动中开启了个死循环的线程,不断的去刷新这个歌词视图组件,因此组件内就是实现在特定的时间做出响应就可以了,主要是在onDraw()中绘制,对手势的处理是交给GestureDetector类进行处理的(直接在onTouchEvent()中写逻辑处理,在灵敏度和滑动屏幕的效果上,不是很好)。


本地音乐播放器_第4张图片

在GestureDetector的onFling()中得到的速度和GestureDetector的onScroll()得到的距离,在正负关系上是相反的,在变化上要一致。而且这两个方法执行起来,没有太大的差别(实际上两点触摸点的数据,不同的侧重),基本是交替运行的。


// 行数变化量限制
		line = (int) (velocityY / 3);
		// 最大的变化行数 为5
		if (Math.abs(line) > 5) {
			line = -Math.abs(line) / line * 5;
		}

手势滑动的时候,有不同的速度和距离,在适当的数据范围内(要排除那些数值偏大或者偏小的值),我们就可以认为手势移动了多少行,这个没有固定,多实验几次,觉得合理就可以了。


本地音乐播放器_第5张图片

计算得到绘制直接的数组,从而在手势滑动的时候,绘制当前行下面的线。


// 向上滚动 这个是根据歌词的时间长短来滚动,整体上移
		canvas.translate(0, -plus);
		// 先画当前行,之后再画他的前面和后面,这样就保持当前行在中间的位置
		try {
			canvas.drawText(Sentencelist.get(index).getContent(), width / 2,
					height / 2, CurrentPaint);
			float tempY = height / 2;
			// 画出本句之前的句子
			for (int i = index - 1; i >= 0; i--) {
				// Sentence sen = list.get(i);
				// 向上推移
				tempY = tempY - TextHeight;
				if (tempY < 0) {
					break;
				}
				canvas.drawText(Sentencelist.get(i).getContent(), width / 2,
						tempY, NotCurrentPaint);
			}
			tempY = height / 2;
			// 画出本句之后的句子
			for (int i = index + 1; i < Sentencelist.size(); i++) {
				// 往下推移
				tempY = tempY + TextHeight;
				if (tempY > height) {
					break;
				}
				canvas.drawText(Sentencelist.get(i).getContent(), width / 2,
						tempY, NotCurrentPaint);
			}
显然,对于歌词的移动,也可以通过动画移动来实现,这里没采用这种方法,权当提供一条思路和方法。

说明:

1.我的手机改了系统字体的,所以是这种效果,android原生系统是不带这种字体的。

你可能感兴趣的:(canvas,音乐,触摸屏)