努力尽今夕,少年犹可夸。这篇文章主要讲述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>
最后我这里的静态效果是这样的:
文章图片
尾声
写完了, 最后来加点逼格
文章图片
, 学无止境, 循序渐进。既然选择了远方, 便只顾风雨兼程
参考资料: 自定义View新手实战-一步步实现精美的钟表界面
【Android笔记自定义View之制作表盘界面】
推荐阅读
- android studio添加jar包及so文件问题
- CSS如何使用grid属性(布局图解示例)
- 如何理解路由器中最长的前缀匹配(详细图解)
- 创建用于使用Python下载YouTube视频的GUI
- CSS中的高级选择器用法解释和指南
- CSS网站页面布局与美化实例教程(div+css)
- Twitter面试经验分享和解析|S1
- PHP如何使用date_diff()函数(代码示例)
- SCAN和CSCAN磁盘调度算法之间有什么区别()