Android自定义View制作仪表盘界面

 更新时间:2016年11月04日 08:44:18   作者:mictoy_朱  
这篇文章主要介绍了Android自定义View制作仪表盘界面的相关资料,首先需要自定义仪表盘的属性,在构造方法种获取自定义属性,本文介绍的非常详细,具有参考借鉴价值,需要的朋友可以参考下

前言

最近我跟自定义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>

最后我这里的静态效果是这样的:

相关文章

  • Android超详细讲解组件LinearLayout的使用

    Android超详细讲解组件LinearLayout的使用

    LinearLayout又称作线性布局,是一种非常常用的布局。正如它的名字所描述的一样,这个布局会将它所包含的控件在线性方向上依次排列。既然是线性排列,肯定就不仅只有一个方向,这里一般只有两个方向:水平方向和垂直方向
    2022-03-03
  • Android自动测试工具Monkey的实现方法

    Android自动测试工具Monkey的实现方法

    本文主要介绍Android Monkey 实现方法,Monkey测试是一种为了测试软件的稳定性、健壮性的快速有效的方法,具有非常重要的参考价值,希望对小伙伴有所帮助
    2016-07-07
  • Android可签到日历控件的实现方法

    Android可签到日历控件的实现方法

    这篇文章主要为大家详细介绍了Android可签到日历控件的实现方法,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-05-05
  • Android如何自定义View实现横向的双水波纹进度条

    Android如何自定义View实现横向的双水波纹进度条

    最近有个需求需要实现自定义加载进度条,于是深入研究了一下,这篇文章主要给大家介绍了关于Android如何自定义View实现横向的双水波纹进度条的相关资料,需要的朋友可以参考下
    2021-11-11
  • Android编程之软件的安装和卸载方法

    Android编程之软件的安装和卸载方法

    这篇文章主要介绍了Android编程之软件的安装和卸载方法,涉及Android编程实现软件的安装、权限修改及卸载的相关技巧,具有一定参考借鉴价值,需要的朋友可以参考下
    2015-12-12
  • Android实现自动变换大小的ViewPager

    Android实现自动变换大小的ViewPager

    ViewPager使用适配器类将数据和view的处理分离,ViewPager的适配器叫PagerAdapter,这是一个抽象类,不能实例化,所以它有两个子类:FragmentPagerAdapter 和 FragmentStatePagerAdapter,这两个都是处理页面为Fragment的情况
    2022-11-11
  • android自定义按钮示例(重写imagebutton控件实现图片按钮)

    android自定义按钮示例(重写imagebutton控件实现图片按钮)

    由于项目这种类型的图片按钮比较多,所以重写了ImageButton类,现在把代码分享给大家,需要的朋友可以参考下
    2014-03-03
  • Android AOP注解Annotation详解(一)

    Android AOP注解Annotation详解(一)

    这篇文章主要介绍了Android AOP注解Annotation详细介绍的相关资料,Annotation是代码里的特殊标记,这些标记可以在编译、类加载、运行时被读取,并执行相应的处理,需要的朋友可以参考下
    2017-03-03
  • Android调用微信登陆、分享、支付

    Android调用微信登陆、分享、支付

    这篇文章主要介绍了Android调用微信登陆、分享、支付的相关资料,需要的朋友可以参考下
    2015-12-12
  • Android实现桌面快捷方式实例代码

    Android实现桌面快捷方式实例代码

    大家好,本篇文章主要讲的是Android实现桌面快捷方式实例代码,感兴趣的同学赶快来看一看吧,对你有帮助的话记得收藏一下,方便下次浏览
    2021-12-12

最新评论