package org.kymjs.music.ui.widget;
import java.util.List;
import org.kymjs.kjframe.utils.DensityUtils;
import org.kymjs.music.Config;
import org.kymjs.music.utils.Player;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Align;
import android.graphics.PointF;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
public class LrcView extends View implements ILrcView {
/** normal display mode */
public final static int DISPLAY_MODE_NORMAL = 0;
/** seek display mode */
public final static int DISPLAY_MODE_SEEK = 1;
/** scale display mode ,scale font size */
public final static int DISPLAY_MODE_SCALE = 2;
private List<LrcRow> mLrcRows;
private final int mMinSeekFiredOffset = 10; // min offset for fire seek
// action,
// px;
private int mHignlightRow = 0; // current singing row , should be
// highlighted.
private final int mHignlightRowColor = Color.YELLOW;
private final int mNormalRowColor = Color.WHITE;
private final int mSeekLineColor = Color.CYAN;
private final int mSeekLineTextColor = Color.CYAN;
private int mSeekLineTextSize = DensityUtils.sp2px(getContext(), 10);
private final int mMinSeekLineTextSize = DensityUtils
.sp2px(getContext(), 8);
private final int mMaxSeekLineTextSize = DensityUtils.sp2px(getContext(),
13);
// font size of lrc
private int mLrcFontSize = DensityUtils.sp2px(getContext(), 17);
private final int mMinLrcFontSize = DensityUtils.sp2px(getContext(), 10);
private final int mMaxLrcFontSize = DensityUtils.sp2px(getContext(), 30);
private final int mPaddingY = 10; // padding of each row
private final int mSeekLinePaddingX = 0; // Seek line padding x
private int mDisplayMode = DISPLAY_MODE_NORMAL;
private LrcViewListener mLrcViewListener;
private String mLoadingLrcTip = Config.LRC_TEXT;
private float mLastMotionY;
private final PointF mPointerOneLastMotion = new PointF();
private final PointF mPointerTwoLastMotion = new PointF();
private boolean mIsFirstMove = false;
private String str;
private Paint mPaint;
public LrcView(Context context) {
super(context);
}
public LrcView(Context context, AttributeSet attr) {
super(context, attr);
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setTextSize(mLrcFontSize);
}
@Override
protected void onDraw(Canvas canvas) {
final int height = getHeight(); // height of this view
final int width = getWidth(); // width of this view
// default display
if (mLrcRows == null || mLrcRows.size() == 0) {
if (mLoadingLrcTip != null) {
// draw tip when no lrc.
mPaint.setColor(mHignlightRowColor);
mPaint.setTextSize(mLrcFontSize);
mPaint.setTextAlign(Align.CENTER);
canvas.drawText(mLoadingLrcTip, width / 2, height / 2
- mLrcFontSize, mPaint);
}
return;
}
int rowY = 0;
final int rowX = width / 2;
int rowNum = 0;
// 高亮度
String highlightText = mLrcRows.get(mHignlightRow).content;
str = mLrcRows.get(mHignlightRow).strTime;
int highlightRowY = height / 2 - mLrcFontSize;
mPaint.setColor(mHignlightRowColor);
mPaint.setTextSize(mLrcFontSize);
mPaint.setTextAlign(Align.CENTER);
canvas.drawText(highlightText, rowX, highlightRowY, mPaint);
// 显示时间还有线
if (mDisplayMode == DISPLAY_MODE_SEEK) {
// 当前时间移动时,显示歌词线
mPaint.setColor(mSeekLineColor);
canvas.drawLine(mSeekLinePaddingX, highlightRowY, width
- mSeekLinePaddingX, highlightRowY, mPaint);
mPaint.setColor(mSeekLineTextColor);
mPaint.setTextSize(mSeekLineTextSize);
mPaint.setTextAlign(Align.LEFT);
canvas.drawText(mLrcRows.get(mHignlightRow).strTime, 0,
highlightRowY, mPaint);
}
mPaint.setColor(mNormalRowColor);
mPaint.setTextSize(mLrcFontSize);
mPaint.setTextAlign(Align.CENTER);
rowNum = mHignlightRow - 1;
rowY = highlightRowY - mPaddingY - mLrcFontSize;
while (rowY > -mLrcFontSize && rowNum >= 0) {
String text = mLrcRows.get(rowNum).content;
canvas.drawText(text, rowX, rowY, mPaint);
rowY -= (mPaddingY + mLrcFontSize);
rowNum--;
}
rowNum = mHignlightRow + 1;
rowY = highlightRowY + mPaddingY + mLrcFontSize;
while (rowY < height && rowNum < mLrcRows.size()) {
String text = mLrcRows.get(rowNum).content;
canvas.drawText(text, rowX, rowY, mPaint);
rowY += (mPaddingY + mLrcFontSize);
rowNum++;
}
}
@Override
public void seekLrc(int position) {
if (mLrcRows == null || position < 0 || position > mLrcRows.size()) {
return;
}
LrcRow lrcRow = mLrcRows.get(position);
mHignlightRow = position;
invalidate();
if (mLrcViewListener != null) {
mLrcViewListener.onLrcSeeked(position, lrcRow);
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (mLrcRows == null || mLrcRows.size() == 0) {
return super.onTouchEvent(event);
}
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mLastMotionY = event.getY();
mIsFirstMove = true;
invalidate();
break;
case MotionEvent.ACTION_MOVE:
if (event.getPointerCount() == 2) {
doScale(event);
return true;
}
// single pointer mode ,seek
if (mDisplayMode == DISPLAY_MODE_SCALE) {
// if scaling but pointer become not two ,do nothing.
return true;
}
doSeek(event);
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
if (mDisplayMode == DISPLAY_MODE_SEEK) {
seekLrc(mHignlightRow);
// 指定播放的位置(以毫秒为单位)
Player.getPlayer().seekTo(timeConvert(str));
}
mDisplayMode = DISPLAY_MODE_NORMAL;
invalidate();
break;
}
return true;
}
private static int timeConvert(String timeString) {
timeString = timeString.replace('.', ':');
String[] times = timeString.split(":");
// mm:ss:SS
return Integer.valueOf(times[0]) * 60 * 1000
+ Integer.valueOf(times[1]) * 1000 + Integer.valueOf(times[2]);
}
@Override
public void setListener(LrcViewListener l) {
mLrcViewListener = l;
}
// 对外暴漏默认显示的文本
public void setLoadingTipText(String text) {
mLoadingLrcTip = text;
}
public String getLoadingTipText() {
return mLoadingLrcTip;
}
private void doScale(MotionEvent event) {
if (mDisplayMode == DISPLAY_MODE_SEEK) {
// if Seeking but pointer become two, become to scale mode
mDisplayMode = DISPLAY_MODE_SCALE;
return;
}
// two pointer mode , scale font
if (mIsFirstMove) {
mDisplayMode = DISPLAY_MODE_SCALE;
invalidate();
mIsFirstMove = false;
setTwoPointerLocation(event);
}
int scaleSize = getScale(event);
if (scaleSize != 0) {
setNewFontSize(scaleSize);
invalidate();
}
setTwoPointerLocation(event);
}
private void doSeek(MotionEvent event) {
float y = event.getY();
float offsetY = y - mLastMotionY; // touch offset.
if (Math.abs(offsetY) < mMinSeekFiredOffset) {
// move to short ,do not fire seek action
return;
}
mDisplayMode = DISPLAY_MODE_SEEK;
int rowOffset = Math.abs((int) offsetY / mLrcFontSize); // highlight row
// offset.
if (offsetY < 0) {
// finger move up
mHignlightRow += rowOffset;
} else if (offsetY > 0) {
// finger move down
mHignlightRow -= rowOffset;
}
mHignlightRow = Math.max(0, mHignlightRow);
mHignlightRow = Math.min(mHignlightRow, mLrcRows.size() - 1);
if (rowOffset > 0) {
mLastMotionY = y;
invalidate();
}
}
private void setTwoPointerLocation(MotionEvent event) {
mPointerOneLastMotion.x = event.getX(0);
mPointerOneLastMotion.y = event.getY(0);
mPointerTwoLastMotion.x = event.getX(1);
mPointerTwoLastMotion.y = event.getY(1);
}
private void setNewFontSize(int scaleSize) {
mLrcFontSize += scaleSize;
mSeekLineTextSize += scaleSize;
mLrcFontSize = Math.max(mLrcFontSize, mMinLrcFontSize);
mLrcFontSize = Math.min(mLrcFontSize, mMaxLrcFontSize);
mSeekLineTextSize = Math.max(mSeekLineTextSize, mMinSeekLineTextSize);
mSeekLineTextSize = Math.min(mSeekLineTextSize, mMaxSeekLineTextSize);
}
// get font scale offset
private int getScale(MotionEvent event) {
float x0 = event.getX(0);
float y0 = event.getY(0);
float x1 = event.getX(1);
float y1 = event.getY(1);
float maxOffset = 0; // max offset between x or y axis,used to decide
// scale size
boolean zoomin = false;
float oldXOffset = Math.abs(mPointerOneLastMotion.x
- mPointerTwoLastMotion.x);
float newXoffset = Math.abs(x1 - x0);
float oldYOffset = Math.abs(mPointerOneLastMotion.y
- mPointerTwoLastMotion.y);
float newYoffset = Math.abs(y1 - y0);
maxOffset = Math.max(Math.abs(newXoffset - oldXOffset),
Math.abs(newYoffset - oldYOffset));
if (maxOffset == Math.abs(newXoffset - oldXOffset)) {
zoomin = newXoffset > oldXOffset ? true : false;
} else {
zoomin = newYoffset > oldYOffset ? true : false;
}
if (zoomin)
return (int) (maxOffset / 10);
else
return -(int) (maxOffset / 10);
}
// 设置歌词行数
@Override
public void setLrc(List<LrcRow> lrcRows) {
mLrcRows = lrcRows;
invalidate();
}
// 设置正在播放的时间段
@Override
public void seekLrcToTime(long time) {
if (mLrcRows == null || mLrcRows.size() == 0) {
return;
}
if (mDisplayMode != DISPLAY_MODE_NORMAL) {
// touching
return;
}
// find row
for (int i = 0; i < mLrcRows.size(); i++) {
LrcRow current = mLrcRows.get(i);
LrcRow next = i + 1 == mLrcRows.size() ? null : mLrcRows.get(i + 1);
if ((time >= current.time && next != null && time < next.time)
|| (time > current.time && next == null)) {
seekLrc(i);
return;
}
}
}
}