带你体验Android自定义圆形刻度罗盘 仪表盘 实现指针动态改变
带你体验Android自定义圆形刻度罗盘 仪表盘 实现指针动态改变
近期有一个自定义View的功能,类似于仪表盘的模型,可以将指针动态指定到某一个刻度上,话不多说,先上图
先说下思路
1.先获取自定义的一些属性,初始化一些资源
2.在onMeasure中测量控件的具体大小
3.然后就在onDraw中先绘制有渐变色的圆弧形色带
4.再绘制几个大的刻度和刻度值
5.再绘制两个大刻度之间的小刻度
6.再绘制处于正中间的圆和三角形指针
7.最后绘制实时值
其实这也从侧面体现了一个自定义view的流程
1.继承View,重写构造方法
2.加载自定义属性和其它资源
3.重写onMeasure方法去确定控件的大小
4.重写onDraw方法去绘制
5.如果有点击事件的话,还得重写onTouchEvent或者dispatchTouchEvent去处理点击事件
来上代码吧,具体注释已经写的很详细了
- public class NoiseboardView extends View {
- final String TAG = "NoiseboardView";
- private int mRadius; // 圆弧半径
- private int mBigSliceCount; // 大份数
- private int mScaleCountInOneBigScale; // 相邻两个大刻度之间的小刻度个数
- private int mScaleColor; // 刻度颜色
- private int mScaleTextSize; // 刻度字体大小
- private String mUnitText = ""; // 单位
- private int mUnitTextSize; // 单位字体大小
- private int mMinValue; // 最小值
- private int mMaxValue; // 最大值
- private int mRibbonWidth; // 色条宽
- private int mStartAngle; // 起始角度
- private int mSweepAngle; // 扫过角度
- private int mPointerRadius; // 三角形指针半径
- private int mCircleRadius; // 中心圆半径
- private float mRealTimeValue = 0.0f; // 实时值
- private int mBigScaleRadius; // 大刻度半径
- private int mSmallScaleRadius; // 小刻度半径
- private int mNumScaleRadius; // 数字刻度半径
- private int mViewColor_green; // 字体颜色
- private int mViewColor_yellow; // 字体颜色
- private int mViewColor_orange; // 字体颜色
- private int mViewColor_red; // 字体颜色
- private int mViewWidth; // 控件宽度
- private int mViewHeight; // 控件高度
- private float mCenterX;//中心点圆坐标x
- private float mCenterY;//中心点圆坐标y
- private Paint mPaintScale;//圆盘上大小刻度画笔
- private Paint mPaintScaleText;//圆盘上刻度值画笔
- private Paint mPaintCirclePointer;//绘制中心圆,指针
- private Paint mPaintValue;//绘制实时值
- private Paint mPaintRibbon;//绘制色带
- private RectF mRectRibbon;//存储色带的矩形数据
- private Rect mRectScaleText;//存储刻度值的矩形数据
- private Path path;//绘制指针的路径
- private int mSmallScaleCount; // 小刻度总数
- private float mBigScaleAngle; // 相邻两个大刻度之间的角度
- private float mSmallScaleAngle; // 相邻两个小刻度之间的角度
- private String[] mGraduations; // 每个大刻度的刻度值
- private float initAngle;//指针实时角度
- private SweepGradient mSweepGradient ;//设置渐变
- private int[] color = new int[7];//渐变颜色组
- public NoiseboardView(Context context) {
- this(context, null);
- }
- public NoiseboardView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
- public NoiseboardView(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- //自定义属性
- TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.NoiseboardView, defStyleAttr, 0);
- mRadius = a.getDimensionPixelSize(R.styleable.NoiseboardView_radius, dpToPx(80));
- mBigSliceCount = a.getInteger(R.styleable.NoiseboardView_bigSliceCount, 5);
- mScaleCountInOneBigScale = a.getInteger(R.styleable.NoiseboardView_sliceCountInOneBigSlice, 5);
- mScaleColor = a.getColor(R.styleable.NoiseboardView_scaleColor, Color.WHITE);
- mScaleTextSize = a.getDimensionPixelSize(R.styleable.NoiseboardView_scaleTextSize, spToPx(12));
- mUnitText = a.getString(R.styleable.NoiseboardView_unitText);
- mUnitTextSize = a.getDimensionPixelSize(R.styleable.NoiseboardView_unitTextSize, spToPx(14));
- mMinValue = a.getInteger(R.styleable.NoiseboardView_minValue, 0);
- mMaxValue = a.getInteger(R.styleable.NoiseboardView_maxValue, 150);
- mRibbonWidth = a.getDimensionPixelSize(R.styleable.NoiseboardView_ribbonWidth, 0);
- a.recycle();
- init();
- }
- private void init() {
- //起始角度是从水平正方向即(钟表3点钟方向)开始从0算的,扫过的角度是按顺时针方向算
- mStartAngle = 175;
- mSweepAngle = 190;
- mPointerRadius = mRadius / 3 * 2;
- mCircleRadius = mRadius / 17;
- mSmallScaleRadius = mRadius - dpToPx(10);
- mBigScaleRadius = mRadius - dpToPx(18);
- mNumScaleRadius = mRadius - dpToPx(20);
- mSmallScaleCount = mBigSliceCount * 5;
- mBigScaleAngle = mSweepAngle / (float) mBigSliceCount;
- mSmallScaleAngle = mBigScaleAngle / mScaleCountInOneBigScale;
- mGraduations = getMeasureNumbers();
- //确定控件的宽度 padding值,在构造方法执行完就被赋值
- mViewWidth = getPaddingLeft() + mRadius * 2 + getPaddingRight() + dpToPx(4);
- mViewHeight = mViewWidth;
- mCenterX = mViewWidth / 2.0f;
- mCenterY = mViewHeight / 2.0f;
- mPaintScale = new Paint();
- mPaintScale.setAntiAlias(true);
- mPaintScale.setColor(mScaleColor);
- mPaintScale.setStyle(Paint.Style.STROKE);
- mPaintScale.setStrokeCap(Paint.Cap.ROUND);
- mPaintScaleText = new Paint();
- mPaintScaleText.setAntiAlias(true);
- mPaintScaleText.setColor(mScaleColor);
- mPaintScaleText.setStyle(Paint.Style.STROKE);
- mPaintCirclePointer = new Paint();
- mPaintCirclePointer.setAntiAlias(true);
- mRectScaleText = new Rect();
- path = new Path();
- mPaintValue = new Paint();
- mPaintValue.setAntiAlias(true);
- mPaintValue.setStyle(Paint.Style.STROKE);
- mPaintValue.setTextAlign(Paint.Align.CENTER);
- mPaintValue.setTextSize(mUnitTextSize);
- initAngle = getAngleFromResult(mRealTimeValue);
- mViewColor_green = getResources().getColor(R.color.green_value);
- mViewColor_yellow = getResources().getColor(R.color.yellow_value);
- mViewColor_orange = getResources().getColor(R.color.orange_value);
- mViewColor_red = getResources().getColor(R.color.red_value);
- color[0] = mViewColor_red;
- color[1] = mViewColor_red;
- color[2] = mViewColor_green;
- color[3] = mViewColor_green;
- color[4] = mViewColor_yellow;
- color[5] = mViewColor_orange;
- color[6] = mViewColor_red;
- //色带画笔
- mPaintRibbon = new Paint();
- mPaintRibbon.setAntiAlias(true);
- mPaintRibbon.setStyle(Paint.Style.STROKE);
- mPaintRibbon.setStrokeWidth(mRibbonWidth);
- mSweepGradient = new SweepGradient(mCenterX, mCenterY,color,null);
- mPaintRibbon.setShader(mSweepGradient);//设置渐变 从X轴正方向取color数组颜色开始渐变
- if (mRibbonWidth > 0) {
- int r = mRadius - mRibbonWidth / 2 + dpToPx(1) ;
- mRectRibbon = new RectF(mCenterX - r, mCenterY - r, mCenterX + r, mCenterY + r);
- }
- }
- /**
- * 确定每个大刻度的值
- * @return
- */
- private String[] getMeasureNumbers() {
- String[] strings = new String[mBigSliceCount + 1];
- for (int i = 0; i <= mBigSliceCount; i++) {
- if (i == 0) {
- strings[i] = String.valueOf(mMinValue);
- } else if (i == mBigSliceCount) {
- strings[i] = String.valueOf(mMaxValue);
- } else {
- strings[i] = String.valueOf(((mMaxValue - mMinValue) / mBigSliceCount) * i);
- }
- }
- return strings;
- }
- /**
- * <dt>UNSPECIFIED : 0 << 30 = 0</dt>
- * <dd>
- * 父控件没有对子控件做限制,子控件可以是自己想要的尺寸
- * 其实就是子空间在布局里没有设置宽高,但布局里添加控件都要设置宽高,所以这种情况暂时没碰到
- * </dd>
- *
- * <dt>EXACTLY : 1 << 30 = 1073741824</dt>
- * <dd>
- * 父控件给子控件决定了确切大小,子控件将被限定在给定的边界里。
- * 如果是填充父窗体(match_parent),说明父控件已经明确知道子控件想要多大的尺寸了,也是这种模式
- * </dd>
- *
- * <dt>AT_MOST : 2 << 30 = -2147483648</dt>
- * <dd>
- * 在布局设置wrap_content,父控件并不知道子控件到底需要多大尺寸(具体值),
- * 需要子控件在onMeasure测量之后再让父控件给他一个尽可能大的尺寸以便让内容全部显示
- * 如果在onMeasure没有指定控件大小,默认会填充父窗体,因为在view的onMeasure源码中,
- * AT_MOST(相当于wrap_content )和EXACTLY (相当于match_parent )两种情况返回的测量宽高都是specSize,
- * 而这个specSize正是父控件剩余的宽高,所以默认onMeasure方法中wrap_content 和match_parent 的效果是一样的,都是填充剩余的空间。
- * </dd>
- *
- * @param widthMeasureSpec
- * @param heightMeasureSpec
- */
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- int widthMode = MeasureSpec.getMode(widthMeasureSpec);//从约束规范中获取模式
- int widthSize = MeasureSpec.getSize(widthMeasureSpec);//从约束规范中获取尺寸
- int heightMode = MeasureSpec.getMode(heightMeasureSpec);
- int heightSize = MeasureSpec.getSize(heightMeasureSpec);
- //在布局中设置了具体值
- if (widthMode == MeasureSpec.EXACTLY)
- mViewWidth = widthSize;
- //在布局中设置 wrap_content,控件就取能完全展示内容的宽度(同时需要考虑屏幕的宽度)
- if (widthMode == MeasureSpec.AT_MOST)
- mViewWidth = Math.min(mViewWidth, widthSize);
- if (heightMode == MeasureSpec.EXACTLY) {
- mViewHeight = heightSize;
- } else {
- float[] point1 = getCoordinatePoint(mRadius, mStartAngle);
- float[] point2 = getCoordinatePoint(mRadius, mStartAngle + mSweepAngle);
- float maxY = Math.max(Math.abs(point1[1]) - mCenterY, Math.abs(point2[1]) - mCenterY);
- float f = mCircleRadius + dpToPx(2) + dpToPx(25) ;
- float max = Math.max(maxY, f);
- mViewHeight = (int) (max + mRadius + getPaddingTop() + getPaddingBottom() + dpToPx(2) * 2);
- if (heightMode == MeasureSpec.AT_MOST)
- mViewHeight = Math.min(mViewHeight, heightSize);
- }
- //保存测量宽度和测量高度
- setMeasuredDimension(mViewWidth, mViewHeight);
- }
- @Override
- protected void onDraw(Canvas canvas) {
- // 绘制色带
- canvas.drawArc(mRectRibbon, 170, 199, false, mPaintRibbon);
- mPaintScale.setStrokeWidth(dpToPx(2));
- for (int i = 0; i <= mBigSliceCount; i++) {
- //绘制大刻度
- float angle = i * mBigScaleAngle + mStartAngle;
- float[] point1 = getCoordinatePoint(mRadius, angle);
- float[] point2 = getCoordinatePoint(mBigScaleRadius, angle);
- canvas.drawLine(point1[0], point1[1], point2[0], point2[1], mPaintScale);
- //绘制圆盘上的数字
- mPaintScaleText.setTextSize(mScaleTextSize);
- String number = mGraduations[i];
- mPaintScaleText.getTextBounds(number, 0, number.length(), mRectScaleText);
- if (angle % 360 > 135 && angle % 360 < 215) {
- mPaintScaleText.setTextAlign(Paint.Align.LEFT);
- } else if ((angle % 360 >= 0 && angle % 360 < 45) || (angle % 360 > 325 && angle % 360 <= 360)) {
- mPaintScaleText.setTextAlign(Paint.Align.RIGHT);
- } else {
- mPaintScaleText.setTextAlign(Paint.Align.CENTER);
- }
- float[] numberPoint = getCoordinatePoint(mNumScaleRadius, angle);
- if (i == 0 || i == mBigSliceCount) {
- canvas.drawText(number, numberPoint[0], numberPoint[1] + (mRectScaleText.height() / 2), mPaintScaleText);
- } else {
- canvas.drawText(number, numberPoint[0], numberPoint[1] + mRectScaleText.height(), mPaintScaleText);
- }
- }
- //绘制小的子刻度
- mPaintScale.setStrokeWidth(dpToPx(1));
- for (int i = 0; i < mSmallScaleCount; i++) {
- if (i % mScaleCountInOneBigScale != 0) {
- float angle = i * mSmallScaleAngle + mStartAngle;
- float[] point1 = getCoordinatePoint(mRadius, angle);
- float[] point2 = getCoordinatePoint(mSmallScaleRadius, angle);
- mPaintScale.setStrokeWidth(dpToPx(1));
- canvas.drawLine(point1[0], point1[1], point2[0], point2[1], mPaintScale);
- }
- }
- if (mRealTimeValue <= 40) {
- mPaintValue.setColor(mViewColor_green);
- mPaintCirclePointer.setColor(mViewColor_green);
- } else if (mRealTimeValue > 40 && mRealTimeValue <= 90) {
- mPaintValue.setColor(mViewColor_yellow);
- mPaintCirclePointer.setColor(mViewColor_yellow);
- } else if (mRealTimeValue > 90 && mRealTimeValue <= 120) {
- mPaintValue.setColor(mViewColor_orange);
- mPaintCirclePointer.setColor(mViewColor_orange);
- } else {
- mPaintValue.setColor(mViewColor_red);
- mPaintCirclePointer.setColor(mViewColor_red);
- }
- //绘制中心点的圆
- mPaintCirclePointer.setStyle(Paint.Style.STROKE);
- mPaintCirclePointer.setStrokeWidth(dpToPx(4));
- canvas.drawCircle(mCenterX, mCenterY, mCircleRadius + dpToPx(3), mPaintCirclePointer);
- //绘制三角形指针
- path.reset();
- mPaintCirclePointer.setStyle(Paint.Style.FILL);
- float[] point1 = getCoordinatePoint(mCircleRadius / 2, initAngle + 90);
- path.moveTo(point1[0], point1[1]);
- float[] point2 = getCoordinatePoint(mCircleRadius / 2, initAngle - 90);
- path.lineTo(point2[0], point2[1]);
- float[] point3 = getCoordinatePoint(mPointerRadius, initAngle);
- path.lineTo(point3[0], point3[1]);
- path.close();
- canvas.drawPath(path, mPaintCirclePointer);
- // 绘制三角形指针底部的圆弧效果
- canvas.drawCircle((point1[0] + point2[0]) / 2, (point1[1] + point2[1]) / 2, mCircleRadius / 2, mPaintCirclePointer);
- //绘制实时值
- canvas.drawText(trimFloat(mRealTimeValue)+" "+ mUnitText, mCenterX, mCenterY - mRadius / 3 , mPaintValue);
- }
- private int dpToPx(int dp) {
- return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics());
- }
- private int spToPx(int sp) {
- return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, getResources().getDisplayMetrics());
- }
- /**
- * 依圆心坐标,半径,扇形角度,计算出扇形终射线与圆弧交叉点的xy坐标
- */
- public float[] getCoordinatePoint(int radius, float cirAngle) {
- float[] point = new float[2];
- double arcAngle = Math.toRadians(cirAngle); //将角度转换为弧度
- if (cirAngle < 90) {
- point[0] = (float) (mCenterX + Math.cos(arcAngle) * radius);
- point[1] = (float) (mCenterY + Math.sin(arcAngle) * radius);
- } else if (cirAngle == 90) {
- point[0] = mCenterX;
- point[1] = mCenterY + radius;
- } else if (cirAngle > 90 && cirAngle < 180) {
- arcAngle = Math.PI * (180 - cirAngle) / 180.0;
- point[0] = (float) (mCenterX - Math.cos(arcAngle) * radius);
- point[1] = (float) (mCenterY + Math.sin(arcAngle) * radius);
- } else if (cirAngle == 180) {
- point[0] = mCenterX - radius;
- point[1] = mCenterY;
- } else if (cirAngle > 180 && cirAngle < 270) {
- arcAngle = Math.PI * (cirAngle - 180) / 180.0;
- point[0] = (float) (mCenterX - Math.cos(arcAngle) * radius);
- point[1] = (float) (mCenterY - Math.sin(arcAngle) * radius);
- } else if (cirAngle == 270) {
- point[0] = mCenterX;
- point[1] = mCenterY - radius;
- } else {
- arcAngle = Math.PI * (360 - cirAngle) / 180.0;
- point[0] = (float) (mCenterX + Math.cos(arcAngle) * radius);
- point[1] = (float) (mCenterY - Math.sin(arcAngle) * radius);
- }
- Log.e("getCoordinatePoint","radius="+radius+",cirAngle="+cirAngle+",point[0]="+point[0]+",point[1]="+point[1]);
- return point;
- }
- /**
- * 通过实时数值得到指针角度
- */
- private float getAngleFromResult(float result) {
- if (result > mMaxValue)
- return 360.0f;
- return mSweepAngle * (result - mMinValue) / (mMaxValue - mMinValue) + mStartAngle;
- }
- /**
- * float类型如果小数点后为零则显示整数否则保留
- */
- public static String trimFloat(float value) {
- if (Math.round(value) - value == 0) {
- return String.valueOf((long) value);
- }
- return String.valueOf(value);
- }
- public float getRealTimeValue() {
- return mRealTimeValue;
- }
- /**
- * 实时设置读数值
- * @param realTimeValue
- */
- public void setRealTimeValue(float realTimeValue) {
- if (realTimeValue > mMaxValue) return;
- mRealTimeValue = realTimeValue;
- initAngle = getAngleFromResult(mRealTimeValue);
- invalidate();
- }
- }
具体代码请看Github
没有梯子请点击这里下载
带你体验Android自定义圆形刻度罗盘 仪表盘 实现指针动态改变的更多相关文章
- Android 自定义圆形图片 CircleImageView
1.效果预览 1.1.布局中写自定义圆形图片的路径即可 1.2.然后看一看图片效果 1.3.原图是这样的 @mipmap/ic_launcher 2.使用过程 2.1.CircleImageView源 ...
- Android 自定义圆形旋转进度条,仿微博头像加载效果
微博 App 的用户头像有一个圆形旋转进度条的加载效果,看上去效果非常不错,如图所示: 据说 Instagram 也采用了这种效果.最近抽空研究了一下,最后实现的效果是这样: 基本上能模拟出个大概,代 ...
- android 自定义圆形进度条
一.通过动画实现 定义res/anim/loading.xml如下: [html] view plain copy print ? <?xml version="1.0" ...
- Android自定义圆形图片工具类(CTRL+C加CTRL+V直接使用)
先贴一下工具类的代码!可直接复制粘贴 public class RoundImageView extends ImageView { private Paint mPaint; //画笔 privat ...
- [转]android 自定义圆形imageview控件
android布局 首先,定义定义圆形Imageview类: import android.content.Context; import android.graphics.Bitmap; imp ...
- Android自定义圆形ProgressBar
闲来无事做了一个自定义的进度条,大致效果图如下: progressbar.gif 废话不多说,下面直接上代码: 自定义控件代码CircleProgressBar.java: public class ...
- android自定义圆形图片和遇到的问题
画圆遇到的问题:图片单位不一样,导致图片只能显示出圆的一部分:看代码: public class MyCircleIamge extends ImageView { private Context c ...
- Android自定义圆形进度条,完成类似LOFTER效果
1.http://stackoverflow.com/questions/3760381/rotating-image-animation-list-or-animated-rotate-androi ...
- android 自定义 radiobutton 文字颜色随选中状态而改变
主要是写一个 color selector 在res/建一个文件夹取名color res/color/color_radiobutton.xml <?xml version="1.0& ...
随机推荐
- 【SpringBoot】自动配置
一.取值 1.1 @Value 1.2 ConfigurationProperties 二.导入配置文件 2.1 @PropertySource 三.配置文件的加载 3.1 默认的加载顺序 3.2 外 ...
- 【Git】三、版本回退&撤消修改&文件删除
提要 //查看git操作日志 $ git log //单行格式查看操作日志 $ git log --pretty=oneline //还原操作到上一次版本,有几个^就上几次 $ git reset - ...
- Flutter——Container组件(容器组件)
名称 功能 alignment topCenter:顶部居中对齐 topLeft:顶部左对齐 topRight:顶部右对齐 center:水平垂直居中对齐 centerLeft:垂直居中水平居左对齐 ...
- $(selector).each() 和$each() 的区别
今天在做项目的时候, 后台数据需要循环遍历出来, 想到each, 结果,竟然不记得语法了 现在来回顾一下,而搜索了一下,竟然发现有两种each 一种就是$(selector).each() ...
- asp.net使用FileUpload控件上传图片且重命名
我在根目录下创建了一个Images图片存放文件夹,上传的图片都在这 下面贴代码 if (FileUpload1.HasFile) { string filename = FileUpload1.Fil ...
- 05_ Flume多级Agent之间串联案例
多级agent之间串联: 从tail命令获取数据发送到avro端口,另一个节点可配置一个avro源来获取数据,发送外部存储 启动两个Agent节点: 使用scp命令拷贝flume安装包到另一台虚拟机; ...
- Persistent Bookcase CodeForces - 707D (dfs 离线处理有根树模型的问题&&Bitset)
Persistent Bookcase CodeForces - 707D time limit per test 2 seconds memory limit per test 512 megaby ...
- No provider available from registry出错
dubbo+zookeeper进行分布式远程调用时No provider available from registry出错 查看dubbo服务:http://192.168.0.100:8080/d ...
- redis.conf 文件解释
# Redis示例配置文件 # 注意单位问题:当需要设置内存大小的时候,可以使用类似1k.5GB.4M这样的常见格式: # # 1k => 1000 bytes # 1kb => 1024 ...
- API接口防止参数篡改和重放攻击
{近期领导要求我对公司业务的支付类的ocr接口做研究,是否存在支付接口重放攻击,so.....} API重放攻击(Replay Attacks)又称重播攻击.回放攻击.他的原理就是把之前窃听到的数据原 ...