Android自定义View制作仪表盘界面
作者:mictoy_朱 发布时间:2021-10-05 06:45:01
前言
最近我跟自定义View杠上了,甚至说有点上瘾到走火入魔了。身为菜鸟的我自然要查阅大量的资料,学习大神们的代码,这不,前两天正好在郭神在微信公众号里推送一片自定义控件的文章——一步步实现精美的钟表界面。正适合我这种菜鸟来学习,闲着没事,我就差不多依葫芦画瓢也写了一个自定义表盘View,现在纯粹最为笔记记录下来。先展示下效果图:
下面进入正题
自定义表盘属性
老规矩,先在attrs文件里添加表盘自定义属性
<declare-styleable name="WatchView">
<attr name="watchRadius" format="dimension"/> //表盘半径
<attr name="watchPadding" format="dimension"/> //表盘相对控件边框距离
<attr name="watchScalePadding" format="dimension"/> //刻度相对表盘距离
<attr name="watchScaleColor" format="color|reference"/> //常规刻度颜色
<attr name="watchScaleLength" format="dimension|reference"/> //常规刻度长度
<attr name="watchHourScaleColor" format="dimension|reference"/> //整点刻度颜色
<attr name="watchHourScaleLength" format="dimension|reference"/> //整点刻度长度
<attr name="hourPointColor" format="color|reference"/> //时针颜色
<attr name="hourPointLength" format="dimension|reference"/> //时针长度
<attr name="minutePointColor" format="color|reference"/> //分针颜色
<attr name="minutePointLength" format="dimension|reference"/> //分针长度
<attr name="secondPointColor" format="color|reference"/> //秒针颜色
<attr name="secondPointLength" format="dimension|reference"/> //秒针长度
<attr name="timeTextSize" format="dimension|reference"/> //表盘字体大小
<attr name="timeTextColor" format="color|reference"/> //表盘字体颜色
</declare-styleable>
在自定义View的构造方法种获取自定义属性
先将属性变量声明如下:
<span style="font-size:14px;"> /**表盘边距*/
private float mWatchPadding = 5;
/**表盘与刻度边距*/
private float mWatchScalePadding = 5;
/**表盘半径*/
private float mWatchRadius = 250;
/**表盘刻度长度*/
private float mWatchScaleLength;
/**表盘刻度颜色*/
private int mWatchScaleColor = Color.BLACK;
/**表盘整点刻度长度*/
private float mHourScaleLength = 8;
/**表盘整点刻度颜色*/
private int mHourScaleColor = Color.BLUE;
/**表盘时针颜色*/
private int mHourPointColor = Color.BLACK;
/**表盘时针长度*/
private float mHourPointLength = 100;
/**表盘分针颜色*/
private int mMinutePointColor = Color.BLACK;
/**表盘分针长度*/
private float mMinutePointLength = 130;
/**表盘秒针颜色*/
private int mSecondPointColor = Color.RED;
/**表盘秒针长度*/
private float mSecondPointLength = 160;
/**表盘尾部指针长度*/
private float mEndPointLength;
/**表盘数字颜色*/
private int mTimeTextColor = Color.BLACK;
/**表盘数字大小*/
private int mTimeTextSize = 15;</span>
在构造方法种获取自定义属性
<span style="font-size:14px;"> public WatchView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray array = context.obtainStyledAttributes(attrs,R.styleable.WatchView);
int n = array.getIndexCount();
for (int i = 0;i<n;i++){
int attr = array.getIndex(i);
switch (attr){
case R.styleable.WatchView_watchRadius:
mWatchRadius = array.getDimensionPixelSize(attr,MyUtil.dip2px(context,60));
break;
case R.styleable.WatchView_watchPadding:
mWatchPadding = array.getDimensionPixelSize(attr,MyUtil.dip2px(context,5));
break;
case R.styleable.WatchView_watchScalePadding:
mWatchScalePadding = array.getDimensionPixelSize(attr,MyUtil.dip2px(context,3));
break;
case R.styleable.WatchView_watchScaleLength:
mWatchScaleLength = array.getDimensionPixelSize(attr,MyUtil.dip2px(context,5));
break;
case R.styleable.WatchView_watchScaleColor:
mWatchScaleColor = array.getColor(attr, Color.parseColor("#50000000"));
break;
case R.styleable.WatchView_watchHourScaleLength:
mHourScaleLength = array.getDimensionPixelSize(attr,MyUtil.dip2px(context,10));
break;
case R.styleable.WatchView_watchHourScaleColor:
mHourScaleColor = array.getColor(attr,Color.BLACK);
break;
case R.styleable.WatchView_hourPointLength:
mHourPointLength = array.getDimensionPixelSize(attr,MyUtil.dip2px(context,35));
break;
case R.styleable.WatchView_hourPointColor:
mHourPointColor = array.getColor(attr,Color.BLACK);
break;
case R.styleable.WatchView_minutePointLength:
mMinutePointLength = array.getDimensionPixelSize(attr,MyUtil.dip2px(context,40));
break;
case R.styleable.WatchView_minutePointColor:
mMinutePointColor = array.getColor(attr,Color.BLACK);
break;
case R.styleable.WatchView_secondPointLength:
mSecondPointLength = array.getDimensionPixelSize(attr,MyUtil.dip2px(context,50));
break;
case R.styleable.WatchView_secondPointColor:
mSecondPointColor = array.getColor(attr,Color.BLUE);
break;
case R.styleable.WatchView_timeTextSize:
mTimeTextSize = array.getDimensionPixelSize(attr,MyUtil.dip2px(context,15));
break;
case R.styleable.WatchView_timeTextColor:
mTimeTextColor = array.getColor(attr,Color.BLACK);
break;
}
}
array.recycle();
}</span>
设置控件大小
这里当然就是重写onMeasure方法啦,这里我们处理的简单点,如下面代码所示,当我们将控件的宽高都设定为wrap_content(即MeasureSpec.UNSPECIFED)时,我们将宽高设定为默认值(wrapContentSize)和圆盘半径+圆盘边距(mWatchRadius+mWatchPadding)之间取最大值,其他情况下就取系统自取值。当然作为一个严谨的控件,仅仅这样处理肯定是不行的。项目中,我们要根据我们的需求自行修改里面的代码以适配。
<span style="font-size:14px;"> @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int wrapContentSize = 1000;
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
if (widthMode == MeasureSpec.UNSPECIFIED && heightMode == MeasureSpec.UNSPECIFIED){
wrapContentSize = (int) Math.max(wrapContentSize,mWatchRadius+mWatchPadding);
setMeasuredDimension(wrapContentSize,wrapContentSize);
}else {
setMeasuredDimension(widthSize,heightSize);
}
}</span>
重写onDraw方法
来到最关键真正画表盘时刻了。一步一步来,首先初始化我们的画笔(我的习惯,写一个initPaint方法)
<span style="font-size:14px;"> private void initPaint(){
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setColor(Color.WHITE);
mPaint.setStyle(Paint.Style.FILL);
}</span>
为了不显赘述,方便理解,我直接展示代码,在代码中解释
开画之前我们先将画笔移动到控件中心点位置,如下:
<span style="font-size:14px;">@Override
protected void onDraw(Canvas canvas) {
canvas.translate(getWidth()/2,getHeight()/2);
}</span>
第一步,画表盘
<span style="font-size:14px;"> /**
* 画表盘
* @param canvas
*/
private void paintWatchBoard(Canvas canvas){
initPaint();
canvas.save();
canvas.drawCircle(0,0,mWatchRadius,mPaint); //画圆盘
canvas.restore();
}</span>
注:每次画图之前都要先调用canvas.save()方法,保存画笔属性,画完之后要调用canvas.restore()方法,重置画笔属性
这里就不一一展示每次画完之后的效果图了。
第二步,画刻度+整点时间数字(刻度从12点方向开始画)
<span style="font-size:14px;"> /**
* 画刻度及整点数字
* @param canvas
*/
private void paintScale(Canvas canvas){
int lineLength; //刻度线长度
canvas.save();
for (int i = 0;i<60;i++){
if (i%5 == 0){//整点刻度下画笔相关属性
mPaint.setStrokeWidth(MyUtil.dip2px(getContext(),1.5f));
mPaint.setColor(mHourScaleColor);
lineLength = MyUtil.dip2px(getContext(),8);
canvas.drawLine(0,-mWatchRadius+mWatchScalePadding,0,-mWatchRadius+mWatchScalePadding+lineLength,mPaint);
mPaint.setColor(mTimeTextColor);
mPaint.setTextSize(mTimeTextSize);
canvas.drawText(mTimes[i/5],-mTimeTextSize/2,-mWatchRadius+mWatchScalePadding + lineLength+mTimeTextSize,mPaint);//整点的位置标上整点时间数字
}else {//非整点刻度下画笔相关属性
mPaint.setStrokeWidth(MyUtil.dip2px(getContext(),0.8f));
mPaint.setColor(mWatchScaleColor);
lineLength = MyUtil.dip2px(getContext(),5);
canvas.drawLine(0,-mWatchRadius+mWatchScalePadding,0,-mWatchRadius+mWatchScalePadding+lineLength,mPaint);
}
canvas.rotate(6);//每次画完一个刻度线,画笔顺时针旋转6度(360/60,相邻两刻度之间的角度差为6度)
}
canvas.restore();
}</span>
其中,整点数字我用了罗马数字来表示
<span style="font-size:14px;">private String[] mTimes = {"XII","Ⅰ","Ⅱ","Ⅲ","Ⅳ","Ⅴ","Ⅵ","Ⅶ","Ⅷ","Ⅸ","Ⅹ","XI"};</span>
第三步,画时针、分针、秒针以及其它修饰图
考虑到时针、分针和秒针大小长度各不一样,我这里特意定义了三支画笔来分别画时针、分针和秒针。
同样的,先初始化指针画笔:
<span style="font-size:14px;">/**
* 初始化指针
*/
private void initPointPaint(){
mHourPaint = new Paint();
mHourPaint.setAntiAlias(true);
mHourPaint.setStyle(Paint.Style.FILL);
mHourPaint.setStrokeWidth(16);
mHourPaint.setColor(mHourPointColor);
mMinutePaint = new Paint();
mMinutePaint.set(mHourPaint);
mMinutePaint.setStrokeWidth(12);
mMinutePaint.setColor(mMinutePointColor);
mSecondPaint = new Paint();
mSecondPaint.set(mHourPaint);
mSecondPaint.setStrokeWidth(7);
mSecondPaint.setColor(mSecondPointColor);
mEndPointLength = mWatchRadius/6; //(修饰部分)指针尾部长度,定义为表盘半径的六分之一
}</span>
画指针
<span style="font-size:14px;">/**
* 画指针
* @param canvas
*/
private void paintPoint(Canvas canvas){
initPointPaint();
Calendar c = Calendar.getInstance(); //取当前时间
int hour = c.get(Calendar.HOUR_OF_DAY);
int minute = c.get(Calendar.MINUTE);
int second = c.get(Calendar.SECOND);
//绘制时针
canvas.save();
canvas.rotate(hour*30);
canvas.drawLine(0,0,0,-mHourPointLength,mHourPaint);
canvas.drawLine(0,0,0,mEndPointLength,mHourPaint);
canvas.restore();
//绘制分针
canvas.save();
canvas.rotate(minute*6);
canvas.drawLine(0,0,0,-mMinutePointLength,mMinutePaint);
canvas.drawLine(0,0,0,mEndPointLength,mMinutePaint);
canvas.restore();
//绘制秒针
canvas.save();
canvas.rotate(second*6);
canvas.drawLine(0,0,0,-mSecondPointLength,mSecondPaint);
canvas.drawLine(0,0,0,mEndPointLength,mSecondPaint);
canvas.restore();
}</span>
OK,该有的差不多都有了,直接在onDraw中调用吧
<span style="font-size:14px;">@Override
protected void onDraw(Canvas canvas) {
canvas.translate(getWidth()/2,getHeight()/2);
paintWatchBoard(canvas); //画表盘
paintScale(canvas); //画刻度
paintPoint(canvas); //画指针
canvas.drawCircle(0,0,15,mSecondPaint); //为了美观,也让表盘更接近我们显示生活中的样子,我在圆盘中心画了一个大红圆点装饰秒针
postInvalidateDelayed(1000); //每隔一秒钟画一次
}</span>
(⊙v⊙)嗯,自定义View大功告成,我们在布局文件里调用看下效果吧
<span style="font-size:14px;"><?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:zhusp="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorAccent">
<com.wondertek.propertyanimatordemo.WatchView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
zhusp:timeTextSize="20dp"
zhusp:watchRadius="150dp"
zhusp:hourPointLength="80dp"
zhusp:minutePointLength="100dp"
zhusp:secondPointLength="115dp"/>
</RelativeLayout></span>
最后我这里的静态效果是这样的:
来源:http://blog.csdn.net/zsp765098084/article/details/53007272


猜你喜欢
- 现在以一个例子来介绍mybatis的动态SQL和模糊查询:通过多条件查询用户记录,条件为姓名模糊匹配,并且年龄在某两个值之间。新建表d_us
- 如题,主要使用AsReadOnly这个方法就可以了List<int> a = new List<int> {1, 2
- 理解圆弧绘制GDI+中对于圆弧的绘制,是以给定的长方形(System.Drawing.Rectangle 结构)为边界绘制的椭圆的
- 概述Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,
- Json的简介JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式。它基于ECMAScript的一个
- 1.下载安装OpenCVhttps://opencv.org/releases/选择合适的平台安装包下载,然后双击安装,也就是解压的过程。这
- Java面向对象编程之多态一.对于多态的理解:通俗点理解,多态其实就是一词多义,就是一种方法的多种状态,即不同的类对象,调用同一个方法名,有
- 本文实例为大家分享了android自定义环形对比图的具体代码,供大家参考,具体内容如下1.首先在res/values里创建一个attr.xm
- 一、实现效果本篇文章实现了简单的图片轮播,初始化3张资源图片,初始化3秒更换一次图片背景,轮换播放。二、知识点Thread线程start()
- 大多数开发人员现在还在使用if else的过程结构,曾看过jdon的banq大哥写的一篇文章,利用command,aop模式替代if els
- 基础编程模型和数据抽象把描述和实现算法所用到的语言特性,软件库和操作系统特性总称为基础编程模型。编写递归代码注意的点:1. 递归总有一个最简
- 这篇文章主要介绍了spring cloud gateway请求跨域问题解决方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定
- 一,内部类访问成员1,内部类可以直接访问外部类的成员,包括私有。2,外部类要访问内部类,必须建立内部类对象。class Outer{int
- QueryWrapper条件构造之apply、last、select场景: 查询数据库限制条数时mysql上的limit使用 Qu
- 前言此前部门内的一个线上系统上线后内存一路飙高、一段时间后直接占满。协助开发人员去分析定位,发现内存中某个Object的量远远超出了预期的范
- 创建字符串常见的构造 String 的方式// 方式一String str = "Hello Bit";// 方式二St
- Spring Boot从天而降Spring Boot是企业级开发的整体整合解决方案,在现在企业项目开发中使用非常普遍,Spring Boot
- 意图:想将项目用到的两个dll库文件(CryptEnDe.dll和ICSharpCode.SharpZipLib.dll)一同编译进exe中
- MyBatis-Generator自动生成映射文件生成的方式一共有三种1、使用cmd命令方式生成首先在generator.xml中指定数据库
- JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式,易于阅读和编写,同时也易于机器解析和生成。同X