废话少说,先上效果图:
自定义属性
//中间线颜色
private int mCenterlineColor;
//刻度线颜色
private int mLineColor;
//文字颜色
private int mTextColor;
//视频段的背景颜色
private int mVideoColor;
//视频轴的背景颜色
private int mBgColor;
//文字大小
private int mTextSize;
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.VideoLineView);
mCenterlineColor = typedArray.getColor(R.styleable.VideoLineView_centerlineColor, Color.RED);
mLineColor = typedArray.getColor(R.styleable.VideoLineView_lineColor, ContextCompat.getColor(getContext(),R.color.line_video));
mTextColor = typedArray.getColor(R.styleable.VideoLineView_txColor, ContextCompat.getColor(getContext(),R.color.white));
mVideoColor = typedArray.getColor(R.styleable.VideoLineView_videoColor, 0x770000FF);//默认带透明蓝色
mBgColor = typedArray.getColor(R.styleable.VideoLineView_bgColor, Color.WHITE);
mTextSize = typedArray.getDimensionPixelSize(R.styleable.VideoLineView_textSize,Functions.sp2px(getContext(),10));
typedArray.recycle();
在attr.xml 标明属性,并在构造函数中获取并设置默认值
宽高处理
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mHeight = measureVideoLine(heightMeasureSpec);
mWidth = measureVideoLine(widthMeasureSpec);
setMeasuredDimension(mWidth,mHeight);
}
private int measureVideoLine(int measureSpec) {
int result = 0;
int specMode = MeasureSpec. getMode(measureSpec);
int specSize = MeasureSpec. getSize(measureSpec);
switch (specMode) {
case MeasureSpec. AT_MOST:
result = Functions.dip2px(getContext(),32);//默认40dp
break;
case MeasureSpec. EXACTLY:
result = Math.max(specSize,Functions.dip2px(getContext(),32));
break;
}
return result;
}
开始绘制
@Override
protected void onDraw(Canvas canvas) {
paint.setColor(mLineColor);
paint.setStrokeWidth(Functions.dip2px(getContext(), 2));
//绘制刻度尺
canvas.drawLine(0, mHeight / 2, mWidth, mHeight / 2, paint);
//往左绘制
drawLine(canvas, paint, mTime.getTime(), mWidth / 2, true);
//往右绘制
drawLine(canvas, paint, mTime.getTime(), mWidth / 2, false);
//中间线最后绘制
paint.setColor(mCenterlineColor);
canvas.drawLine(mWidth / 2, 0, mWidth / 2, mHeight, paint);
}
private void drawLine(Canvas canvas, Paint paint, long time, float startX, boolean left) {
paint.setColor(mLineColor);
long residue = time % (60 * 1000 * intervalMinute);//是否能被十分钟整除,也就是是否移动了一个刻度
long nextTime = 0;//下次绘制的时间
float offset = Functions.dip2px(getContext(), intervalWidth);//一个刻度的距离
if (residue == 0) {
nextTime = left ? time - 60 * 1000 * intervalMinute : time + 60 * 1000 * intervalMinute;
startX = left ? startX - offset : startX + offset;
} else {//不够十分钟,也就是不到一个刻度
nextTime = left ? time - residue : time - residue + 60 * 1000 * intervalMinute;
//重新计算startX;
float v = offset / (60 * 10);//计算出每一秒的距离
startX = left ? startX - residue / 1000 * v : startX + (60 * 1000 * intervalMinute - residue) / 1000 * v;
}
if (startX < 0 || startX > mWidth) {
return;
}
int endY = nextTime % (3600 * 1000) == 0 ? 6 : 3;//以一个小时求余
paint.setStrokeWidth(Functions.dip2px(getContext(), 1));
paint.setColor(ContextCompat.getColor(getContext(), R.color.white));
canvas.drawLine(startX, mHeight / 2 + Functions.dip2px(getContext(), endY), startX, mHeight / 2 - Functions.dip2px(getContext(), endY), paint);
if (endY == 6) {//60分钟整点
Date t = new Date(nextTime);
paint.setColor(mTextColor);
paint.setTextSize(mTextSize);
paint.setAntiAlias(true);
Paint.FontMetrics fontMetrics = paint.getFontMetrics();
float value = fontMetrics.bottom - fontMetrics.top;
float v = paint.measureText(t.getHours() + ":00");
canvas.drawText(t.getHours() + ":00", startX - v / 2, mHeight / 2 + value + 8, paint);
}
drawLine(canvas, paint, nextTime, startX, left);
}
注释写得挺清晰啦,判断是否移动了一个刻度,如果是而且是向左绘制,下次绘制的时间(nextTime)为当前时间减去一个刻度时间(time - 60 * 1000 * intervalMinute);如果是而且是向右绘制,下次绘制的时间(nextTime)为当前时间加上一个刻度时间(time + 60 * 1000 * intervalMinute),绘制刻度线的横坐标(startx)也就相应移到一个刻度距离(startX - offset : startX + offset)
如果不是移动了一个刻度而且是向左绘制,下次绘制的时间(nextTime)为当前时间减去求余剩下的时间(time - residue);如果不是而且是向右绘制,下次绘制时间(nextTime)为当前时间加上一个刻度时间家去求余剩下时间(time - residue + 60 * 1000 * intervalMinute),绘制刻度线的横坐标(startx)也就相应移动距离(startX - residue / 1000 * v : startX + (60 * 1000 * intervalMinute - residue) / 1000 * v)。
然后递归调用,60分钟整点时绘制文字
触摸事件处理
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mStartX = event.getX();
stopRunTime();
break;
case MotionEvent.ACTION_MOVE:
case MotionEvent.ACTION_UP:
float distance = event.getX() - mStartX;
long zeng = getTimeByDistance(distance);
mStartX = event.getX();
mTime.setTime(mTime.getTime() - zeng);
invalidate();
if (event.getAction() == MotionEvent.ACTION_UP) {
if (videoLineViewChangeListener != null) {
if (mTime.getTime() > new Date().getTime()) {
Toast.makeText(getContext(), "不可以超过当前时间", Toast.LENGTH_LONG).show();
mTime.setTime(mTime.getTime() + zeng);//滑动条撤回原处
invalidate();
return true;
}
videoLineViewChangeListener.onChange(mTime, this);
}
startRunTime();
}
break;
}
return true;
}
private int getTimeByDistance(float distance) {
float offset_px = Functions.dip2px(getContext(), intervalWidth);
float secondsWidth = offset_px / (60 * 10);//每秒多少像素
return Math.round(distance / secondsWidth) * 1000;
}
监听ACTION_UP 事件 获取移动距离转为移动时间,并在videoLineViewChangeListener接口中返回当前时间
源码
public class VideoLineView extends View {
Paint paint = new Paint();
//中间线颜色
private int mCenterlineColor;
//刻度线颜色
private int mLineColor;
//文字颜色
private int mTextColor;
//视频段的背景颜色
private int mVideoColor;
//视频轴的背景颜色
private int mBgColor;
//文字大小
private int mTextSize;
//当前刻度时间
private Date mTime = new Date();
private int mHeight;
private int mWidth;
private int intervalWidth = 30;//每个刻度30dp宽度
private int intervalMinute = 10;//每个刻度表示10分钟
private List<VideoInfo> videoInfos = new ArrayList<VideoInfo>();
private float mStartX = 0;
private VideoLineViewChangeListener videoLineViewChangeListener;
private boolean autoRun = true;//是否自动改变时间
private Object data;//用来绑定用户数据
@SuppressLint("HandlerLeak")
private Handler handler = new Handler() {
};
private Runnable autoRunTime = new Runnable() {
@Override
public void run() {
if (mTime != null && mTime.getTime() < videoInfos.get(videoInfos.size() - 1).getEndTime().getTime()) {
mTime.setTime(mTime.getTime() + 1000);
invalidate();
handler.postDelayed(this, 1000);
}
}
};
public VideoLineView(Context context) {
this(context, null);
}
public VideoLineView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, -1);
}
public VideoLineView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.VideoLineView);
mCenterlineColor = typedArray.getColor(R.styleable.VideoLineView_centerlineColor, Color.RED);
mLineColor = typedArray.getColor(R.styleable.VideoLineView_lineColor, ContextCompat.getColor(getContext(), R.color.line_video));
mTextColor = typedArray.getColor(R.styleable.VideoLineView_txColor, ContextCompat.getColor(getContext(), R.color.white));
mVideoColor = typedArray.getColor(R.styleable.VideoLineView_videoColor, 0x770000FF);//默认带透明蓝色
mBgColor = typedArray.getColor(R.styleable.VideoLineView_bgColor, Color.WHITE);
mTextSize = typedArray.getDimensionPixelSize(R.styleable.VideoLineView_textSize, Functions.sp2px(getContext(), 10));
typedArray.recycle();
}
public Date getmTime() {
return mTime;
}
public void setmTime(Date mTime) {
if (mTime != null) {
this.mTime.setTime(mTime.getTime());
startRunTime();
}
}
public List<VideoInfo> getVideoInfos() {
return videoInfos;
}
public void setVideoInfos(List<VideoInfo> videoInfos) {
this.videoInfos = videoInfos;
if (videoInfos != null && !videoInfos.isEmpty()) {
setmTime(videoInfos.get(0).getStartTime());
}
}
public boolean isAutoRun() {
return autoRun;
}
public void setAutoRun(boolean autoRun) {
this.autoRun = autoRun;
}
public VideoLineViewChangeListener getVideoLineViewChangeListener() {
return videoLineViewChangeListener;
}
public void setVideolineChangeListener(VideoLineViewChangeListener videoLineViewChangeListener) {
this.videoLineViewChangeListener = videoLineViewChangeListener;
}
public Object getData() {
return data;
}
public VideoLineView setData(Object data) {
this.data = data;
return this;
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
mHeight = getHeight();
mWidth = getWidth();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mHeight = measureVideoLine(heightMeasureSpec);
mWidth = measureVideoLine(widthMeasureSpec);
setMeasuredDimension(mWidth, mHeight);
}
private int measureVideoLine(int measureSpec) {
int result = 0;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.AT_MOST:
result = Functions.dip2px(getContext(), 32);//默认40dp
break;
case MeasureSpec.EXACTLY:
result = Math.max(specSize, Functions.dip2px(getContext(), 32));
break;
}
return result;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mStartX = event.getX();
stopRunTime();
break;
case MotionEvent.ACTION_MOVE:
case MotionEvent.ACTION_UP:
float distance = event.getX() - mStartX;
long zeng = getTimeByDistance(distance);
mStartX = event.getX();
mTime.setTime(mTime.getTime() - zeng);
invalidate();
if (event.getAction() == MotionEvent.ACTION_UP) {
if (videoLineViewChangeListener != null) {
if (mTime.getTime() > new Date().getTime()) {
Toast.makeText(getContext(), "不可以超过当前时间", Toast.LENGTH_LONG).show();
mTime.setTime(mTime.getTime() + zeng);//滑动条撤回原处
invalidate();
return true;
}
videoLineViewChangeListener.onChange(mTime, this);
}
startRunTime();
}
break;
}
return true;
}
@Override
protected void onDraw(Canvas canvas) {
paint.setColor(mLineColor);
paint.setStrokeWidth(Functions.dip2px(getContext(), 2));
//绘制刻度尺
canvas.drawLine(0, mHeight / 2, mWidth, mHeight / 2, paint);
//往左绘制
drawLine(canvas, paint, mTime.getTime(), mWidth / 2, true);
//往右绘制
drawLine(canvas, paint, mTime.getTime(), mWidth / 2, false);
//中间线最后绘制
paint.setColor(mCenterlineColor);
canvas.drawLine(mWidth / 2, 0, mWidth / 2, mHeight, paint);
}
private void drawLine(Canvas canvas, Paint paint, long time, float startX, boolean left) {
paint.setColor(mLineColor);
long residue = time % (60 * 1000 * intervalMinute);//是否能被十分钟整除,也就是是否移动了一个刻度
long nextTime = 0;//下次绘制的时间
float offset = Functions.dip2px(getContext(), intervalWidth);//一个刻度的距离
if (residue == 0) {
nextTime = left ? time - 60 * 1000 * intervalMinute : time + 60 * 1000 * intervalMinute;
startX = left ? startX - offset : startX + offset;
} else {//不够十分钟,也就是不到一个刻度
nextTime = left ? time - residue : time - residue + 60 * 1000 * intervalMinute;
//重新计算startX;
float v = offset / (60 * 10);//计算出每一秒的距离
startX = left ? startX - residue / 1000 * v : startX + (60 * 1000 * intervalMinute - residue) / 1000 * v;
}
if (startX < 0 || startX > mWidth) {
return;
}
int endY = nextTime % (3600 * 1000) == 0 ? 6 : 3;//以一个小时求余
paint.setStrokeWidth(Functions.dip2px(getContext(), 1));
paint.setColor(ContextCompat.getColor(getContext(), R.color.white));
canvas.drawLine(startX, mHeight / 2 + Functions.dip2px(getContext(), endY), startX, mHeight / 2 - Functions.dip2px(getContext(), endY), paint);
if (endY == 6) {//60分钟整点
Date t = new Date(nextTime);
paint.setColor(mTextColor);
paint.setTextSize(mTextSize);
paint.setAntiAlias(true);
Paint.FontMetrics fontMetrics = paint.getFontMetrics();
float value = fontMetrics.bottom - fontMetrics.top;
float v = paint.measureText(t.getHours() + ":00");
canvas.drawText(t.getHours() + ":00", startX - v / 2, mHeight / 2 + value + 8, paint);
}
drawLine(canvas, paint, nextTime, startX, left);
}
/**
* 根据时间获取该时间在当前数轴的X坐标
*
* @param date
* @return
*/
private float getXByTime(Date date) {
float offset_px = Functions.dip2px(getContext(), intervalWidth);
float secondsWidth = offset_px / (60 * 10);
long s = (date.getTime() - mTime.getTime()) / 1000;
float x = mWidth / 2.0f + s * secondsWidth;
return x;
}
private int getTimeByDistance(float distance) {
float offset_px = Functions.dip2px(getContext(), intervalWidth);
float secondsWidth = offset_px / (60 * 10);//每秒多少像素
return Math.round(distance / secondsWidth) * 1000;
}
/**
* 开启自动计时 前提autoRun=true
*/
public void startRunTime() {
handler.removeCallbacks(autoRunTime);
if (autoRun && videoInfos != null && !videoInfos.isEmpty()) {
handler.postDelayed(autoRunTime, 1000);
}
}
public void stopRunTime() {
handler.removeCallbacks(autoRunTime);
}
}