Android自定义View之滑动刻度尺
2017-03-12 本文已影响586人
愚蠢的高小星
纵向滑动刻度尺
VerticalScaleView.png横向滑动刻度尺
HorizontalScaleView.png实现思路:
onDraw方法中画出整个刻度尺和指针。重写onTouchEvent,根据手指的滑动不断改变坐标,并重新计算出刻度对应的坐标以及指针所指向坐标对应的刻度,然后重绘。具体的绘制方法参考源码,大致就是找到每个点的坐标然后用canvas的相应方法绘制,计算比较多但不复杂~
这里没有用Scroller实现滑动,因为Scroller滑动时整个View都会滑动,而我希望的效果是刻度滑动,中间的指针不动,尝试了几种滑动方法后发现还是重绘的效果最好。即通过手指滑动的偏移量计算新的坐标,然后根据新坐标重绘视图。
另外,使用VelocityTracker记录手指抬起的速度,实现惯性滑动的效果。源码如下:
public class VerticalScaleView extends View {
private final int SCALE_WIDTH_BIG = 4;//大刻度线宽度
private final int SCALE_WIDTH_SMALL = 2;//小刻度线宽度
private final int LINE_WIDTH = 6;//指针线宽度
private int rectPadding = 20;//圆角矩形间距
private int rectHeight;//圆角矩形高
private int maxScaleLength;//大刻度长度
private int midScaleLength;//中刻度长度
private int minScaleLength;//小刻度长度
private int scaleSpace;//刻度间距
private int scaleSpaceUnit;//每大格刻度间距
private int height, width;//view高宽
private int ruleWidth;//刻度尺宽
private int max;//最大刻度
private int min;//最小刻度
private int borderUp, borderDown;//上下边界值坐标
private float midY;//当前中心刻度y坐标
private float originMidY;//初始中心刻度y坐标
private float minY;//最小刻度y坐标,从最小刻度开始画刻度
private float lastY;
private float originValue;//初始刻度对应的值
private float currentValue;//当前刻度对应的值
private Paint paint;//画笔
private Context context;
private String descri = "身高";//描述
private String unit = "cm";//刻度单位
private VelocityTracker velocityTracker;//速度监测
private float velocity;//当前滑动速度
private float a = 1000000;//加速度
private boolean continueScroll;//是否继续滑动
private boolean isMeasured;
private OnValueChangeListener onValueChangeListener;
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (null != onValueChangeListener) {
float v = (float) (Math.round(currentValue * 10)) / 10;//保留一位小数
onValueChangeListener.onValueChanged(v);
}
}
};
public VerticalScaleView(Context context) {
super(context);
this.context = context;
init();
}
public VerticalScaleView(Context context, AttributeSet attrs) {
super(context, attrs);
this.context = context;
init();
}
public VerticalScaleView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
this.context = context;
init();
}
private void init() {
//初始化画笔
paint = new Paint();
paint.setAntiAlias(true);
paint.setDither(true);
}
//设置刻度范围
public void setRange(int min, int max) {
this.min = min;
this.max = max;
originValue = (max + min) / 2;
currentValue = originValue;
}
//设置刻度单位
public void setUnit(String unit) {
this.unit = unit;
}
//设置刻度描述
public void setDescri(String descri) {
this.descri = descri;
}
//设置value变化监听
public void setOnValueChangeListener(OnValueChangeListener onValueChangeListener) {
this.onValueChangeListener = onValueChangeListener;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (!isMeasured) {
width = getMeasuredWidth();
height = getMeasuredHeight();
ruleWidth = width * 2 / 3;
maxScaleLength = width / 5;
midScaleLength = width / 6;
minScaleLength = maxScaleLength / 2;
scaleSpace = height / 80 > 8 ? height / 80 : 8;
scaleSpaceUnit = scaleSpace * 10 + SCALE_WIDTH_BIG + SCALE_WIDTH_SMALL * 9;
rectHeight = scaleSpaceUnit / 2;
borderUp = height / 2 - ((min + max) / 2 - min) * scaleSpaceUnit;
borderDown = height / 2 + ((min + max) / 2 - min) * scaleSpaceUnit;
midY = (borderUp + borderDown) / 2;
originMidY = midY;
minY = borderDown;
isMeasured = true;
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//画刻度线
for (int i = min; i <= max; i++) {
//画刻度数字
canvas.rotate(90);
Rect rect = new Rect();
String str = String.valueOf(i);
paint.setColor(getResources().getColor(R.color.black));
paint.setTextSize(40);
paint.getTextBounds(str, 0, str.length(), rect);
int w = rect.width();
int h = rect.height();
canvas.drawText(str, minY - (i - min) * scaleSpaceUnit - w / 2 - SCALE_WIDTH_BIG / 2, -(ruleWidth - maxScaleLength - h - minScaleLength / 2), paint);
canvas.rotate(-90);
//画大刻度线
paint.setStrokeWidth(SCALE_WIDTH_BIG);
canvas.drawLine(ruleWidth, minY - (i - min) * scaleSpaceUnit, ruleWidth - maxScaleLength, minY - (i - min) * scaleSpaceUnit, paint);
if (i == min) {
continue;//最后一条不画中小刻度线
}
//画中刻度线
paint.setStrokeWidth(SCALE_WIDTH_SMALL);
canvas.drawLine(ruleWidth, minY - (i - min) * scaleSpaceUnit + scaleSpaceUnit / 2, ruleWidth - midScaleLength, minY - (i - min) * scaleSpaceUnit + scaleSpaceUnit / 2, paint);
//画小刻度线
for (int j = 1; j < 10; j++) {
if (j == 5) {
continue;
}
canvas.drawLine(ruleWidth, minY - (i - min) * scaleSpaceUnit + (SCALE_WIDTH_SMALL + scaleSpace) * j, ruleWidth - minScaleLength, minY - (i - min) * scaleSpaceUnit + (SCALE_WIDTH_SMALL + scaleSpace) * j, paint);
}
}
//画竖线
paint.setStrokeWidth(LINE_WIDTH);
paint.setColor(getResources().getColor(R.color.gray));
canvas.drawLine(ruleWidth + LINE_WIDTH / 2, minY + SCALE_WIDTH_BIG / 2, ruleWidth + LINE_WIDTH / 2, minY - (max - min) * scaleSpaceUnit - SCALE_WIDTH_BIG / 2, paint);
//画指针线
paint.setColor(getResources().getColor(R.color.colorPrimaryDark));
canvas.drawLine(0, height / 2, ruleWidth, height / 2, paint);
//画圆角矩形
paint.setStyle(Paint.Style.FILL);
RectF r = new RectF();
r.left = ruleWidth + rectPadding;
r.top = height / 2 - rectHeight / 2;
r.right = width;
r.bottom = height / 2 + rectHeight / 2;
canvas.drawRoundRect(r, 10, 10, paint);
//画小三角形指针
Path path = new Path();
path.moveTo(ruleWidth + rectPadding, height / 2 - scaleSpace);
path.lineTo(ruleWidth + rectPadding - 8, height / 2);
path.lineTo(ruleWidth + rectPadding, height / 2 + scaleSpace);
path.close();
canvas.drawPath(path, paint);
//绘制文字
paint.setColor(getResources().getColor(R.color.black));
Rect rect1 = new Rect();
paint.getTextBounds(descri, 0, descri.length(), rect1);
int w1 = rect1.width();
canvas.drawText(descri, width - (width - ruleWidth - 20) / 2 - w1 / 2, height / 2 - rectHeight / 2 - 10, paint);
//绘制当前刻度值数字
paint.setColor(getResources().getColor(R.color.white));
float v = (float) (Math.round(currentValue * 10)) / 10;//保留一位小数
String value = String.valueOf(v) + unit;
Rect rect2 = new Rect();
paint.getTextBounds(value, 0, value.length(), rect2);
int w2 = rect2.width();
int h2 = rect2.height();
canvas.drawText(value, width - (width - ruleWidth - 20) / 2 - w2 / 2, height / 2 + h2 / 2, paint);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
lastY = y;
continueScroll = false;
//初始化速度追踪
if (velocityTracker == null) {
velocityTracker = VelocityTracker.obtain();
} else {
velocityTracker.clear();
}
break;
case MotionEvent.ACTION_MOVE:
velocityTracker.addMovement(event);
int offsetY = (int) (lastY - y);
minY -= offsetY;
midY -= offsetY;
calculateCurrentScale();
invalidate();
lastY = y;
break;
case MotionEvent.ACTION_UP:
confirmBorder();
//当前滑动速度
velocityTracker.computeCurrentVelocity(1000);
velocity = velocityTracker.getYVelocity();
float minVelocity = ViewConfiguration.get(context).getScaledMinimumFlingVelocity();
if (Math.abs(velocity) > minVelocity) {
continueScroll = true;
continueScroll();
} else {
velocityTracker.recycle();
velocityTracker = null;
}
break;
case MotionEvent.ACTION_CANCEL:
velocityTracker.recycle();
velocityTracker = null;
break;
}
return true;
}
//计算当前刻度
private void calculateCurrentScale() {
float offsetTotal = originMidY - midY;
int offsetBig = (int) (offsetTotal / scaleSpaceUnit);//移动的大刻度数
float offsetS = offsetTotal % scaleSpaceUnit;
int offsetSmall = (new BigDecimal(offsetS / (scaleSpace + SCALE_WIDTH_SMALL)).setScale(0, BigDecimal.ROUND_HALF_UP)).intValue();//移动的小刻度数 四舍五入取整
float offset = offsetBig + offsetSmall * 0.1f;
if (originValue - offset > max) {
currentValue = max;
} else if (originValue - offset < min) {
currentValue = min;
} else {
currentValue = originValue - offset;
}
mHandler.sendEmptyMessage(0);
}
//指针线超出范围时 重置回边界处
private void confirmBorder() {
if (midY < borderUp) {
midY = borderUp;
minY = borderDown + (borderUp - borderDown) / 2;
postInvalidate();
} else if (midY > borderDown) {
midY = borderDown;
minY = borderDown - (borderUp - borderDown) / 2;
postInvalidate();
}
}
//手指抬起后继续惯性滑动
private void continueScroll() {
new Thread(new Runnable() {
@Override
public void run() {
float velocityAbs = 0;//速度绝对值
if (velocity > 0 && continueScroll) {
velocity -= 50;
minY += velocity * velocity / a;
midY += velocity * velocity / a;
velocityAbs = velocity;
} else if (velocity < 0 && continueScroll) {
velocity += 50;
minY -= velocity * velocity / a;
midY -= velocity * velocity / a;
velocityAbs = -velocity;
}
calculateCurrentScale();
confirmBorder();
postInvalidate();
if (continueScroll && velocityAbs > 0) {
post(this);
} else {
continueScroll = false;
}
}
}).start();
}
}