最近在做一个时间设置功能,之前都是简单的用一个EditText来实现,并设置输入范围或者输入错误的警告信息,这样的方法虽然简单,但用户使用起来,显得繁琐,而且还动不动的蹦出来俩提示,一点都不友好。
因此这次换个新的设计吧——卡尺选择。
主要介绍一下这个View的主要几个绘制点:
1. 坐标轴:需要绘制X轴(横向卡尺)或Y轴(纵向卡尺)drawLine(Canvas canvas, Paint paint);
2. 刻度:需要绘制坐标轴上的刻度。drawScale(Canvas canvas, Paint paint);
绘制刻度时,需要的参数有坐标轴宽度以及刻度之间的间隔;
3. 当前刻度指针:drawScalePointer(Canvas canvas, Paint paint);
需要绘制在当前卡尺显示区域的中点。由于卡尺的这个指针每次卡尺滚动都需要重新绘制,所以每次都需要计算当前显示区域的中点位置,而这个中间位置是相对于卡尺起点位置的坐标;
4. 滑动卡尺支持scroll以及fling方式,且注意滑动的边界处理。
其他的就不多说了,直接上代码吧,代码中都有注释:
首先是卡尺绘制的基类,
package rkhy.com.ecg.view.scale;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.VelocityTracker;
import android.view.View;
import android.widget.Scroller;
import rkhy.com.ecg.R;
/**
* **************************************************
*
* @ 日 期:2018/1/5 15:33
* @ 作 者:shangming
* 卡尺基类
* **************************************************
*/
public abstract class BaseScaleView extends View {
protected int mScaleMin; // 刻度最小值
protected int mScaleMax; // 刻度最大值
protected int mScaleHeight; // 刻度高度
protected int mScaleNumHeight; // 整数刻度高度
protected int mScaleSpace; // 刻度间隔
protected int mScaleCount; //相对刻度起点滑动的刻度
protected int mViewWidth; //宽度
protected int mViewHeight; //高度
protected int mScrollPreX;
protected int mInitMiddleScalePointer; // 初始中间刻度指针
protected int mScrollViewWidth; // 滚动的View宽度
protected int mScaleMiddle; // 屏幕中间的刻度偏移量
protected Scroller mScroller;
protected OnScaleScrollListener mOnScaleScrollListener;
protected VelocityTracker mVelocityTracker;
public BaseScaleView(Context context) {
super(context);
init(context, null);
}
public BaseScaleView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public BaseScaleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
private void init(Context context, AttributeSet attrs) {
TypedArray ta = getContext().obtainStyledAttributes(attrs, R.styleable.scale_attrs);
mScaleMin = ta.getInteger(R.styleable.scale_attrs_scale_min, 10);
mScaleMax = ta.getInteger(R.styleable.scale_attrs_scale_max, 100);
mScaleHeight = ta.getDimensionPixelOffset(R.styleable.scale_attrs_scale_height, 20);
mScaleNumHeight = ta.getDimensionPixelOffset(R.styleable.scale_attrs_scale_number_height, 30);
mScaleSpace = ta.getDimensionPixelOffset(R.styleable.scale_attrs_scale_space, 15);
ta.recycle();
mScroller = new Scroller(context);
initView();
}
@Override
protected void onDraw(Canvas canvas) {
Paint paint = new Paint();
paint.setColor(getResources().getColor(R.color.headBgColor));
paint.setAntiAlias(true);
// 设定是否使用图像抖动处理,会使绘制出来的图片颜色更加平滑和饱满,图像更加清晰
paint.setDither(true);
paint.setStyle(Paint.Style.STROKE);
// 文字居中
paint.setTextAlign(Paint.Align.CENTER);
drawLine(canvas, paint);
drawScale(canvas, paint);
drawScalePointer(canvas, paint);
super.onDraw(canvas);
}
public void setCurrent(int scale) {
if (scale >= mScaleMin && scale <= mScaleMax) {
smoothTo(scale);
this.postInvalidate();
}
}
public void smoothScrollBy(int dx, int dy) {
mScroller.startScroll(mScroller.getFinalX(), mScroller.getFinalY(), dx, dy);
}
@Override
public void computeScroll() {
super.computeScroll();
// 判断Scroller是否执行完毕
if (mScroller.computeScrollOffset()) {
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
// 通过重绘来不断调用computeScroll
invalidate();
}
}
public void setOnScaleScrollListener(OnScaleScrollListener onScaleScrollListener) {
this.mOnScaleScrollListener = onScaleScrollListener;
}
/**
* 初始化参数
*/
protected abstract void initView();
/**
* 绘制刻度线
*
* @param canvas
* @param paint
*/
protected abstract void drawLine(Canvas canvas, Paint paint);
/**
* 绘制刻度
*
* @param canvas
* @param paint
*/
protected abstract void drawScale(Canvas canvas, Paint paint);
/**
* 绘制刻度指针
*
* @param canvas
* @param paint
*/
protected abstract void drawScalePointer(Canvas canvas, Paint paint);
/**
* 滑动到指定刻度
*
* @param scale
*/
public abstract void smoothTo(int scale);
public interface OnScaleScrollListener {
void onScaleScroll(int scale);
}
}
横向卡尺的实现:
package rkhy.com.ecg.view.scale;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.ViewGroup;
import rkhy.com.ecg.R;
/**
* **************************************************
*
* @ 日 期:2018/1/5 16:58
* @ 作 者:shangming
* 横向卡尺实现
* **************************************************
*/
public class HorizontalScaleView extends BaseScaleView {
public HorizontalScaleView(Context context) {
super(context);
}
public HorizontalScaleView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public HorizontalScaleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void initView() {
mViewWidth = (mScaleMax - mScaleMin) * mScaleSpace;
mViewHeight = mScaleHeight * 8 - 4;
ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(mViewWidth, mViewHeight);
this.setLayoutParams(layoutParams);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int height = MeasureSpec.makeMeasureSpec(mViewHeight, MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, height);
mScrollViewWidth = getMeasuredWidth();
mScaleMiddle = mScrollViewWidth / mScaleSpace / 2;
mInitMiddleScalePointer = mScaleMiddle + mScaleMin;
}
@Override
protected void drawLine(Canvas canvas, Paint paint) {
canvas.drawLine(0, mViewHeight, mViewWidth, mViewHeight, paint);
}
@Override
protected void drawScale(Canvas canvas, Paint paint) {
paint.setTextSize(mViewHeight / 4); // 刻度数字大小
paint.setStrokeWidth(2.0f);
for (int i = 0, num = mScaleMin; i <= mScaleMax - mScaleMin; i++) {
int x = i * mScaleSpace;
if (0 == (i % 10)) { // 10的整数倍
canvas.drawLine(x, mViewHeight, x, mViewHeight - mScaleNumHeight, paint);
canvas.drawText(num + "", x, mViewHeight - mScaleNumHeight - mScaleHeight - 2, paint); // 绘制整数数字
num += 10;
} else if (0 == (i % 5)) { // 5的整数倍
int height = mViewHeight - (mScaleHeight + (mScaleNumHeight - mScaleHeight) / 2);
canvas.drawLine(x, mViewHeight, x, height, paint);
} else { // 非整数
canvas.drawLine(x, mViewHeight, x, mViewHeight - mScaleHeight, paint);
}
}
}
@Override
protected void drawScalePointer(Canvas canvas, Paint paint) {
paint.setColor(getResources().getColor(R.color.red));
paint.setStrokeWidth(5.0f);
//根据滑动的距离,计算指针的位置【指针始终位于刻度中间】
int currentX = mScroller.getCurrX();
//滑动的刻度
mScaleCount = mInitMiddleScalePointer + (int) Math.rint((double) currentX / (double) mScaleSpace);
// 刻度越界处理
if (mScaleCount >= mScaleMax) {
mScaleCount = mScaleMax;
} else if (mScaleCount <= mScaleMin) {
mScaleCount = mScaleMin;
}
if (mOnScaleScrollListener != null) { //回调方法
mOnScaleScrollListener.onScaleScroll(mScaleCount);
}
int x = mScaleSpace * (mScaleMiddle + mScaleCount - mInitMiddleScalePointer);
// 滑动的距离越界处理
if (x >= mViewWidth) {
x = mViewWidth;
} else if (x <= 0) {
x = 0;
}
canvas.drawLine(x, mViewHeight, x, mViewHeight - mScaleNumHeight - mScaleHeight, paint);
}
@Override
public void smoothTo(int scale) {
if (scale < mScaleMin || scale > mScaleMax) {
return;
}
int dx = (scale - mScaleCount) * mScaleSpace;
smoothScrollBy(dx, 0);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
int x = (int) event.getX();
if (mVelocityTracker == null) {
//检查速度测量器,如果为null,获得一个
mVelocityTracker = VelocityTracker.obtain();
}
int index = event.getActionIndex();
int pointerId = event.getPointerId(index);
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mVelocityTracker.addMovement(event);
if (mScroller != null && !mScroller.isFinished()) {
mScroller.abortAnimation();
}
mScrollPreX = x;
break;
case MotionEvent.ACTION_MOVE:
mVelocityTracker.addMovement(event);
int dx = mScrollPreX - x;
if (dx < 0) {
if (mScaleCount <= mScaleMin) { // 滑到右边界
return super.onTouchEvent(event);
}
} else {
if (mScaleCount >= mScaleMax) { // 滑到左边界
return super.onTouchEvent(event);
}
}
smoothScrollBy(dx, 0);
mScrollPreX = x;
postInvalidate();
break;
case MotionEvent.ACTION_UP:
//设置速度单位:/50ms
mVelocityTracker.computeCurrentVelocity(50);
// 初始速度:*px/50ms
int initialVelocity = (int) mVelocityTracker.getXVelocity(pointerId);
if (Math.abs(initialVelocity) > 150) {
// 由于坐标轴正方向问题,要加负号。
doFling(-initialVelocity);
} else {
if (mScaleCount < mScaleMin) mScaleCount = mScaleMin;
if (mScaleCount > mScaleMax) mScaleCount = mScaleMax;
int finalX = (mScaleCount - mInitMiddleScalePointer) * mScaleSpace;
mScroller.setFinalX(finalX); //纠正指针位置
invalidate();
}
break;
}
return true;
}
private void doFling(int speed) {
if (mScroller == null) {
return;
}
mScroller.fling(this.getScrollX(), 0, speed, 0,
-(mScrollViewWidth / 2), mViewWidth - (mScrollViewWidth / 2),
0, 0);
}
}
最终效果: