自定义View的操作肯定是在onDraw方法中实现的,View的左上角是坐标的(0,0)点
private static final int SPACING = 40; // 每边距离View边缘的间距
private static final int SCALE_LINE_LENGTH = 24; // 刻度尺每条线的长度
private static final int SCALE_LINE_UNIT = 10; // 需要把刻度尺分成几等份
// 每等份刻度尺的间隔
float unit = (getWidth() - SPACING * 2) / SCALE_LINE_UNIT;
for (int i = 0; i < 11; i++) {
// 需要画线的x坐标值
float x = unit * i + SPACING - offset①;
// 画刻度线
canvas.drawLine(x, SCALE_LINE_LENGTH, x, SCALE_LINE_LENGTH * 2, mScaleLinePaint);
// 画刻度值
int tt;
int t = temp② + 5 * i - 25;
// 此公式在0°前后会出现正负,所以需要进行下面的判断,保证最后的数为0 - 360之间的数
if (t < 0) {
tt = 360 + t;
} else if (t > 360) {
tt = t - 360;
} else {
tt = t;
}
// 画对应的刻度值
canvas.drawText(OrientationUtils.calculateDegree(tt)③, x, SCALE_LINE_LENGTH, mTextPaint);
}
上面的代码中我打了三个标记,分别是①offset,②temp,③OrientationUtils.calculateDegree(tt),这是主要的步骤,具体的值是怎么来的,下面一步步来说明
原理解析里说了,Demo中是分成了10等份,每等份是5°,最小刻度单位是1°,所以,显示在屏幕上的一共有50°,每个刻度的距离就算出来了,是 View宽度 / 50,至于偏移了多少距离,是根据 mValues%5(mValues是从外部传进来的当前方位的角度,后面出现的mValue都是该值,这个计算出来的值就是当前值较之末尾为0或5的值偏移了多少单位)乘以每个刻度的距离得出来的。
// 偏移值
int offset = getWidth() / 50 * (mValue % 5);
temp值的作用只有一个,那就是得到比当前值(也就是mValue )小的末尾为0或5的数(后面统称为05),如果当前值正好为05,那这个temp值就等于mValue。为什么要这么做呢?因为刻度上显示的单位刻度就是5°,所以只会显示05,如果不是这个数,那就找它左边的数(也就是比它小,05),这个数是一个基准,是画线的中心点,也是for循环中最中心的一个点
要得到比当前值(05除外)小的05,首先要知道这个数的个位比5大还是小,5是一个基准,举个例子最直观,例如:74的05就是70,76的05就是75。那么,这个05怎么得到呢?还是举个例子,例如,当前值为12,需要先判断它的个位是大于5还是小于5,这个个位数值怎么拿,通过公式:y = x % 10 得到,将12代入,得到的个位数就是2,小于5,就通过公式: y = x / 10 * 10 得到temp值为10。有人肯定会疑惑,这个公式算出来不是应该还是12吗?这个x是int类型(因为本Demo把方位角强转为int了,原型是float类型)。如果当前值为16,代入公式得到的个位数是6,大于5,需要另外一个公式:y = x / 10 * 10 + 5 得到temp值为15。如果当前值就是05,那么temp值就是等于当前值。
// 商
int merchant = mValue / 10;
// 余数
int remainder = mValue % 10;
// 偏移值
int offset = getWidth() / 50 * (mValue % 5);
// 当前值换算成(末尾为5或者0)的值
int temp;
if (remainder > 0) {
if (remainder < 5) {
temp = merchant * 10;
} else if (remainder > 5) {
temp = merchant * 10 + 5;
} else {
temp = mValue;
}
} else {
temp = mValue;
}
这个简单,画个起始点,然后再画另外两个点,连起来就行了,直接上代码
// 画下面的标志(三角形)
// 起点
mSignPath.moveTo(getWidth() / 2, SCALE_LINE_LENGTH * 2 + 10);
// 另外两个点
mSignPath.lineTo(getWidth() / 2 - SCALE_LINE_LENGTH, SCALE_LINE_LENGTH * 3 + 10);
mSignPath.lineTo(getWidth() / 2 + SCALE_LINE_LENGTH, SCALE_LINE_LENGTH * 3 + 10);
// 闭合
mSignPath.close();
canvas.drawPath(mSignPath, mSignPaint);
关键的代码都介绍的差不多了,剩下的 OrientationUtils.calculateDegree(tt) 其实就是一个工具类,用来把当前的方位角度换算成自己要显示的信息,直接上代码
public class OrientationUtils {
/**
* 根据角度计算方位角度(方向传感器)
*
* @param degree 角度
* @return 方位角度
*/
public static String calculateDegree(int degree) {
if (degree > 0 && degree < 90) {
if (degree == 45) {
return "东北";
}
return degree + "°";
} else if (degree > 90 && degree < 180) {
if (degree == 135) {
return "东南";
}
return (degree - 90) + "°";
} else if (degree > 180 && degree < 270) {
if (degree == 225) {
return "西南";
}
return (degree - 180) + "°";
} else if (degree > 270 && degree < 360) {
if (degree == 315) {
return "西北";
}
return (degree - 270) + "°";
} else if (degree == 0 || degree == 360) {
return "正北";
} else if (degree == 90) {
return "正东";
} else if (degree == 180) {
return "正南";
} else if (degree == 270) {
return "正西";
}
return "";
}
}
package com.xx.electroniccompass.view;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.View;
import com.xx.electroniccompass.util.OrientationUtils;
/**
* 日期:2018/7/19
* 描述:刻度尺
*
* @author XX
*/
public class ScaleLineView extends View {
private static final int SPACING = 40; // 每边距离View边缘的间距
private static final int SCALE_LINE_LENGTH = 24; // 刻度尺每条线的长度
private static final int SCALE_LINE_UNIT = 10; // 需要把刻度尺分成几等份
private Paint mScaleLinePaint;
private Paint mSignPaint;
private Paint mTextPaint;
private Path mSignPath;
private int mValue;
public ScaleLineView(Context context) {
this(context, null);
}
public ScaleLineView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public ScaleLineView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
mScaleLinePaint = new Paint();
mScaleLinePaint.setColor(Color.WHITE);
mScaleLinePaint.setAntiAlias(true);
mScaleLinePaint.setStyle(Paint.Style.STROKE);
mScaleLinePaint.setStrokeWidth(2);
mTextPaint = new Paint();
mTextPaint.setColor(Color.WHITE);
mTextPaint.setAntiAlias(true);
mTextPaint.setStyle(Paint.Style.FILL);
mTextPaint.setTextAlign(Paint.Align.CENTER);
mTextPaint.setStrokeWidth(2);
mTextPaint.setTextSize(24);
mSignPaint = new Paint();
mSignPaint.setColor(Color.RED);
mSignPaint.setAntiAlias(true);
mSignPaint.setStyle(Paint.Style.FILL_AND_STROKE);
mSignPath = new Path();
setBackgroundColor(Color.argb(50, 0, 0, 0));
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec));
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// 每等份刻度尺的间隔
float unit = (getWidth() - SPACING * 2) / SCALE_LINE_UNIT;
// 画下面的一条横线
// canvas.drawLine(SPACING, SCALE_LINE_LENGTH * 2, getWidth() - SPACING, SCALE_LINE_LENGTH * 2, mScaleLinePaint);
// 画下面的标志(三角形)
// 起点
mSignPath.moveTo(getWidth() / 2, SCALE_LINE_LENGTH * 2 + 10);
// 另外两个点
mSignPath.lineTo(getWidth() / 2 - SCALE_LINE_LENGTH, SCALE_LINE_LENGTH * 3 + 10);
mSignPath.lineTo(getWidth() / 2 + SCALE_LINE_LENGTH, SCALE_LINE_LENGTH * 3 + 10);
// 闭合
mSignPath.close();
canvas.drawPath(mSignPath, mSignPaint);
// 商
int merchant = mValue / 10;
// 余数
int remainder = mValue % 10;
// 偏移值
int offset = getWidth() / 50 * (mValue % 5);
// 当前值换算成(末尾为5或者0)的值
int temp;
if (remainder > 0) {
if (remainder < 5) {
temp = merchant * 10;
} else if (remainder > 5) {
temp = merchant * 10 + 5;
} else {
temp = mValue;
}
} else {
temp = mValue;
}
for (int i = 0; i < 11; i++) {
// 需要画线的x坐标值
float x = unit * i + SPACING - offset;
// 画刻度线
canvas.drawLine(x, SCALE_LINE_LENGTH, x, SCALE_LINE_LENGTH * 2, mScaleLinePaint);
// 画刻度值
int tt;
int t = temp + 5 * i - 25;
// 此公式在0°前后会出现正负,所以需要进行下面的判断,保证最后的数为0 - 360之间的数
if (t < 0) {
tt = 360 + t;
} else if (t > 360) {
tt = t - 360;
} else {
tt = t;
}
canvas.drawText(OrientationUtils.calculateDegree(tt), x, SCALE_LINE_LENGTH, mTextPaint);
}
}
private int measureHeight(int measureSpec) {
int result;
int mode = MeasureSpec.getMode(measureSpec);
int size = MeasureSpec.getSize(measureSpec);
if (mode == MeasureSpec.EXACTLY) {
result = size;
} else {
result = 75;
if (mode == MeasureSpec.AT_MOST) {
result = Math.min(result, size);
}
}
return result;
}
private int measureWidth(int measureSpec) {
int result;
int mode = MeasureSpec.getMode(measureSpec);
int size = MeasureSpec.getSize(measureSpec);
if (mode == MeasureSpec.EXACTLY) {
result = size;
} else {
result = 75;
if (mode == MeasureSpec.AT_MOST) {
result = Math.min(result, size);
}
}
return result;
}
/**
* 设置当前值
*
* @param value 当前值
*/
public void setValues(int value) {
this.mValue = value;
invalidate();
}
}
本Demo只是很简单的实现,大家可以自己做拓展,包括属性设置、功能优化等,至于怎么根据方向传感器获取当前方位角信息,大家自己百度一下吧,网上资料有很多。最后,感谢大家的阅读,有什么问题可以留言…