最近在作收音机项目须要绘制一个 FM 刻度尺,刚开始考虑了一下现有的开源库,后来发现都不太知足 UI 小哥哥的要求,因而决定本身画一个吧。实现的 Demo 效果如上所示。主要包含大中小三种长度的刻度线,部分刻度整数值和一根指示器。这样就完美实现了一个 FM 刻度尺。下面大体介绍一下具体的作法。只想看代码的同窗能够直奔 Github 地址git
我是经过继承 View
重写相关类来实现自定义 View的。最重要的就是实现三个相关方法:github
重写 onMeasure(),并调用父类 onMeasure()时:canvas
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(setMeasureWidth(widthMeasureSpec), setMeasureHeight(heightMeasureSpec));
}
private int setMeasureHeight(int spec) {
int mode = MeasureSpec.getMode(spec);
int size = MeasureSpec.getSize(spec);
int result = Integer.MAX_VALUE;
switch (mode) {
case MeasureSpec.AT_MOST:
size = Math.min(result, size);
break;
case MeasureSpec.EXACTLY:
break;
default:
size = result;
break;
}
return size;
}
private int setMeasureWidth(int spec) {
int mode = MeasureSpec.getMode(spec);
int size = MeasureSpec.getSize(spec);
int result = Integer.MAX_VALUE;
switch (mode) {
case MeasureSpec.AT_MOST:
size = Math.min(result, size);
break;
case MeasureSpec.EXACTLY:
break;
default:
size = result;
break;
}
return size;
}
复制代码
说明ide
MeasureSpec.getSize()会解析 MeasureSpec 值获得父容器 width 或者 height。spa
MeasureSpec.getMode()会获得三个int类型的值分别为:MeasureSpec.EXACTLY MeasureSpec.AT_MOST,MeasureSpec.UNSPECIFIED。rest
首先咱们须要初始化画笔code
private Paint mLinePaint;//刻度线画笔
private Paint mTextPaint;//指示数字画笔
private Paint mRulerPaint;//指示线画笔
private void init() {
mLinePaint = new Paint();
mLinePaint.setColor(getResources().getColor(R.color.grey));
//抗锯齿
mLinePaint.setAntiAlias(true);
mLinePaint.setStyle(Paint.Style.STROKE);
mLinePaint.setStrokeWidth(1);
mTextPaint = new Paint();
mTextPaint.setColor(getResources().getColor(R.color.grey));
mTextPaint.setAntiAlias(true);
mTextPaint.setStyle(Paint.Style.FILL);
mTextPaint.setStrokeWidth(2);
mTextPaint.setTextSize(24);
mRulerPaint = new Paint();
mRulerPaint.setAntiAlias(true);
mRulerPaint.setStyle(Paint.Style.FILL_AND_STROKE);
mRulerPaint.setColor(getResources().getColor(R.color.ruler_line));
mRulerPaint.setStrokeWidth(3);
}
复制代码
开始绘制:orm
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.save();
//绘制刻度线
for (int i = min; i <= max; i++) {
if (i % 10 == 0) {
canvas.drawLine(20, 0, 20, 140, mLinePaint);
String text = i / 10 + "";
Rect rect = new Rect();
float txtWidth = mTextPaint.measureText(text);
mTextPaint.getTextBounds(text, 0, text.length(), rect);
if (i / 10 % 2 == 1 && i / 10 != 107) {
canvas.drawText(text, 20 - txtWidth / 2, 72 + rect.height() + 74, mTextPaint);
}
if (i / 10 == 108) {
canvas.drawText(text, 20 - txtWidth / 2, 72 + rect.height() + 74, mTextPaint);
}
} else if (i % 5 == 0) {
canvas.drawLine(20, 30, 20, 110, mLinePaint);
} else {
canvas.drawLine(20, 54, 20, 86, mLinePaint);
}
canvas.translate((float) 8, 0);
}
canvas.restore();
//绘制指示线
canvas.drawLine(position, 0, position, 140, mRulerPaint);
mTextPaint.setTextSize(24);
}
复制代码
上面的代码分别画出了三种长度不一样的刻度线、刻度数字和指示器的线。就这样咱们完成了刻度尺的绘制。可是只有一个刻度尺是不够的,咱们还须要重写 onTouchEvent 对点击和滑动事件作出响应。若是咱们须要在滑动时得到刻度尺对应的数值还须要定义相应对监听接口。cdn
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_MOVE:
float x = event.getX();
if (x < MIN_POSITION) {
setPosition(MIN_POSITION);
} else if (x > MAX_POSITION) {
setPosition(MAX_POSITION);
} else {
setPosition((int) x);
}
//移动指示条
if (mMove != null) {
mMove.onMove(Double.parseDouble(String.format("%.1f", getFmChannel())));
}
Log.d("TAG", "position:" + position);
Log.d("TAG", "channel:" + getFmChannel());
case MotionEvent.ACTION_CANCEL:
//只停在0.1(刻度线上)的位置
setFmChanel(Double.parseDouble(String.format("%.1f", getFmChannel())));
Log.d("停下来后", "channel:" + Double.parseDouble(String.format("%.1f", getFmChannel())));
break;
default:
}
return true;
}
public void setPosition(int i) {
position = i;
invalidate();
}
public void setFmChanel(double fmChanel) {
int temp = (int) ((fmChanel - 87) * 80) + 20;
setPosition(temp);
}
public double getFmChannel() {
return ((position - 20.0) / 80.0 + 87.0);
}
复制代码
这样咱们对刻度尺就是一个能够滑动指示器的刻度尺了。我在 ViewPager 中使用这个刻度尺的过程当中遇到了一个问题:没法顺利滑动刻度尺了。这是由于和父控件滑动事件冲突,只须要重写 dispatchTouchEvent
方法就能够解决,代码以下:blog
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
//解决刻度尺和viewPager的滑动冲突
//当滑动刻度尺时,告知父控件不要拦截事件,交给子view处理
getParent().requestDisallowInterceptTouchEvent(true);
return super.dispatchTouchEvent(ev);
}
复制代码
若是咱们须要实时监听刻度尺滑动时的值就须要设置相应监听接口。代码以下:
/** * 定义监听接口 */
public interface OnMoveActionListener {
void onMove(double x);
}
/** * 为每一个接口设置监听器 */
public void setOnMoveActionListener(OnMoveActionListener move) {
mMove = move;
}
复制代码
这样就实现了一个能够滑动指示器、实时监听刻度表数值、跳转至特定数值的刻度尺。
整个刻度尺的实现主要包括刻度线相关元素绘制和滑动事件处理。刻度线绘制看起来麻烦,实际只要理清思路,将对应位置的对应长度的线画出来便可。这次提到的刻度尺可扩展性较差,须要的同窗能够在次基础上从新修改使用。
Github:github.com/gs666/Ruler… 欢迎你们提issue 和 star~
掘金主页:juejin.im/user/5bffbd… 欢迎关注~