对于 Android 开发者来讲,自定义 View 是绕不开的一个坎。二对一自定义 View 自定义时钟必然是首选,那么咱们该如何绘制自定义时钟呢?本篇我结合 github 上一个有趣的三方库,来给你们讲讲如何做出咱们的第一个时钟php
对于全部的自定义 View 来讲,构造方法、onMeasure(),onDraw() 这几个方法都是必不可少的。,因此哦大家先打出这套模版html
public ClockView(Context context) {
super(context);
}
public ClockView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
}
复制代码
重写 onMeasure() 方法的本质在于配置控件大小,而配置控件大小的重点就在于配置 setMeasuredDimension(..., ...) 方法。关于具体的配置细节能够参照:点击查看 blog.csdn.net/qq_43377749… 这里觉得是自定义时钟控件,因此内容很简单,在三种模式下分别放回三种值便可:java
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(measureDimension(widthMeasureSpec), measureDimension(heightMeasureSpec));
}
private int measureDimension(int measureSpec) {
int defaultSize = 800;
int model = MeasureSpec.getMode(measureSpec);
int size = MeasureSpec.getSize(measureSpec);
switch (model) {
case MeasureSpec.EXACTLY:
return size;
case MeasureSpec.AT_MOST:
return Math.min(size, defaultSize);
case MeasureSpec.UNSPECIFIED:
return defaultSize;
default:
return defaultSize;
}
}
复制代码
由于是自定义控件,因此逼着在这里自定义了一个控件属性文件,位于 /res/values/attr.xml 具体内容以下:git
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="ClockView">
<attr name="clock_backgroundColor" format="color" />
<attr name="clock_lightColor" format="color" />
<attr name="clock_darkColor" format="color" />
<attr name="clock_textSize" format="dimension" />
</declare-styleable>
</resources>
复制代码
如今让咱们开始搭建时钟,因为是时钟的搭建,因此咱们基本能够分为一下三个步骤:github
得到当前系统时间canvas
绘制时针ide
绘制分针工具
绘制秒针优化
首先,要绘制时钟,必然要得到当前的时间,要不三根指针非重合在一块儿不可,因此让咱们先来研究下如何得到当前系统时间,这里咱们就须要使用到一个叫作 Calendar 的工具类,Calendar 是 Android 开发中须要获取时间时必不可少的一个工具类。spa
所须要的信息基本能够分为
milliSecond (毫秒,保证秒针滚动平滑不是一跳一跳)
second
minute
hour
private void getCurrentTime() {
Calendar calendar = Calendar.getInstance();
float milliSecond = calendar.get(Calendar.MILLISECOND);
float second = calendar.get(Calendar.SECOND) + milliSecond / 1000;// 精确到小数点后 保证圆滑
float minute = calendar.get(Calendar.MINUTE) + second / 60;
float hour = calendar.get(Calendar.HOUR) + minute / 60;
}
复制代码
可是这里有个问题,自定义 View 中,绘制时是根据某个倾斜角度进行绘制的,而非给系统一个浮点型的时间,他就会自动取绘制。因此这里咱们还须要知道 每一个时间(分秒时,占总时间的比重所表明的偏转角),在这里咱们这三个全局私有变量:
/* 时针角度 */
private float mHourDegree;
/* 分针角度 */
private float mMinuteDegree;
/* 秒针角度 */
private float mSecondDegree;
private void getCurrentTime(){
Calendar calendar = Calendar.getInstance();
float milliSecond = calendar.get(Calendar.MILLISECOND);
float second = calendar.get(Calendar.SECOND) + milliSecond / 1000;
float minute = calendar.get(Calendar.MINUTE) + second / 60;
float hour = calendar.get(Calendar.HOUR) + minute / 60;
mSecondDegree = second / 60 * 360;
mMinuteDegree = minute / 60 * 360;
mHourDegree = hour / 60 * 360;
}
复制代码
最后别忘了在 onDraw() 中调用
为了区别于时针分针单一的矩形,这里的秒针咱们用一个三角尖代替:
既然是绘制图像,自定义画笔就是必不可少的
而后,画笔是用于上色的,因此咱们还须要一个 Path 类对象将这个小三角的边界画出来
因为绘制是在成员方法中进行,因此咱们须要定一个 Canvas 对象,来保存 onDraw() 中因为绘制视图的 Canvas
除此以外,秒针是有长度的,因此咱们须要一个整型长度变量
最后,咱们还须要一个整型变量来存储颜色值,颜色值应该从咱们先前定义的 xml 文件的属性中获取。
因为时间是须要反复更新的,因此 onDraw() 方法也是要被反复调用的。这就使得 Paint 等变量不能再其中定义,而须要在构造方法中定义,不然不免有内存溢出的风险。
/* 亮色,用于分针、秒针、渐变终止色 */
private int mLightColor;
/* 秒针画笔 */
private Paint mSecondHandPaint;
public ClockView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ClockView, 0, 0);
mLightColor = ta.getColor(R.styleable.ClockView_clock_lightColor, Color.parseColor("#ffffff"));
ta.recycle();
mSecondHandPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mSecondHandPaint.setStyle(Paint.Style.FILL);
mSecondHandPaint.setColor(mLightColor);
}
复制代码
长度值和 Path 的定义和 Paint 同样,不适合在 onDraw() 中,建议你们在 onSizeChanged 中定义,这个方法的提供了测量长度的各个形参。
/* 加一个默认的padding值,为了防止用camera旋转时钟时形成四周超出view大小 */
private float mDefaultPadding;
private float mPaddingTop;
/* 时钟半径,不包括padding值 */
private float mRadius;
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mRadius = Math.min(w - getPaddingLeft() - getPaddingRight(),
h - getPaddingTop() - getPaddingBottom()) / 2;// 各个指针长度
mDefaultPadding = 0.12f * mRadius;
mPaddingTop = mDefaultPadding + h / 2 - mRadius + getPaddingTop();// 钟离上边界距离
}
复制代码
这里把绘制方法命名为:drawSecondNeedle() 首先咱们须要得到 Canvas 参数
/* 秒针路径 */
private Path mSecondHandPath = new Path();
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mCanvas = canvas;
getCurrentTime();
drawSecondNeedle();
invalidate();
}
复制代码
根据这个参数咱们开始绘制秒针:
首先绘制画笔的 Style 设为 FILL 填充
定义一个 Path 对象因为绘制
调用 Path 对象的 moveTo 方法设定绘制起点
调用 lineTo 方法,绘制线条
调用 Canvas 的 close 方法将终点与起点连线造成封闭图形
private void drawSecondNeedle() {
mCanvas.save();// ❑ save:用来保存Canvas的状态。save以后,能够调用Canvas的平移、放缩、旋转、错切、裁剪等操做。
mCanvas.rotate(mSecondDegree, getWidth() / 2, getHeight() / 2);// 设置指针位置
mSecondHandPath.reset();
float offset = mPaddingTop;
mSecondHandPath.moveTo(getWidth() / 2, offset + 0.26f * mRadius);// 这三行绘制三角尖
mSecondHandPath.lineTo(getWidth() / 2 - 0.05f * mRadius, offset + 0.34f * mRadius);
mSecondHandPath.lineTo(getWidth() / 2 + 0.05f * mRadius, offset + 0.34f * mRadius);
mSecondHandPath.close();
mSecondHandPaint.setColor(mLightColor);
mCanvas.drawPath(mSecondHandPath, mSecondHandPaint);
mCanvas.restore();// ❑ restore:用来恢复Canvas以前保存的状态。防止save后对Canvas执行的操做对后续的绘制有影响。
}
复制代码
要绘制分针首先得有如下准备
一个用于绘制分针的 Path 对象
一个用于绘制中心轴圆圈的 RectF 对象
一个用于画笔对象
/* 分针路径 */
private Path mMinuteHandPath = new Path();
/* 分针画笔 */
private Paint mMinuteHandPaint;
/* 小时圆圈的外接矩形 */
private RectF mCircleRectF = new RectF();
/** * 绘制分针 */
private void drawMinuteNeedle() {
mCanvas.save();
mCanvas.rotate(mMinuteDegree, getWidth() / 2, getHeight() / 2);
mMinuteHandPath.reset();
float offset = mPaddingTop ;
mMinuteHandPath.moveTo(getWidth() / 2 - 0.01f * mRadius, getHeight() / 2 - 0.03f * mRadius);
mMinuteHandPath.lineTo(getWidth() / 2 - 0.008f * mRadius, offset + 0.365f * mRadius);
mMinuteHandPath.quadTo(getWidth() / 2, offset + 0.345f * mRadius,
getWidth() / 2 + 0.008f * mRadius, offset + 0.365f * mRadius);
mMinuteHandPath.lineTo(getWidth() / 2 + 0.01f * mRadius, getHeight() / 2 - 0.03f * mRadius);
mMinuteHandPath.close();
mMinuteHandPaint.setStyle(Paint.Style.FILL);
mCanvas.drawPath(mMinuteHandPath, mMinuteHandPaint);
mCircleRectF.set(getWidth() / 2 - 0.03f * mRadius, getHeight() / 2 - 0.03f * mRadius,//绘制指针轴的小圆圈
getWidth() / 2 + 0.03f * mRadius, getHeight() / 2 + 0.03f * mRadius);
mMinuteHandPaint.setStyle(Paint.Style.STROKE);
mMinuteHandPaint.setStrokeWidth(0.02f * mRadius);
mCanvas.drawArc(mCircleRectF, 0, 360, false, mMinuteHandPaint);
mCanvas.restore();
}
复制代码
首先咱们根据以前计算得到的角度旋转画笔到当前要绘制的时间
而后咱们绘制分针,绘制方法很简单,首先咱们将画笔移到 View 中心篇左的地方
而后用 lineTo 绘制一条直线
接着用 quadTo 绘制一条曲线到右边对称点
再接着 用 lineTo 绘制一条直线到中心篇右
最后调用 close 方法闭合图形便可
至于绘制圆心轴的方法就不说了 就是最基本的绘制圆的方法,先设定 RectF 对象,在调用 fraeArc 方法绘制便可
绘制是真的过程与绘制分针如出一辙,因为轴心圆的 RectF 能够直接调用以前绘制分针用到的,因此甚至是更简单些
/* 时针路径 */
private Path mHourHandPath = new Path();
/* 时针画笔 */
private Paint mHourHandPaint;
/** * 绘制时针 */
private void drawHourHand() {
mCanvas.save();
mCanvas.rotate(mHourDegree, getWidth() / 2, getHeight() / 2);
mHourHandPath.reset();
float offset = mPaddingTop;
mHourHandPath.moveTo(getWidth() / 2 - 0.018f * mRadius, getHeight() / 2 - 0.03f * mRadius);
mHourHandPath.lineTo(getWidth() / 2 - 0.009f * mRadius, offset + 0.48f * mRadius);
mHourHandPath.quadTo(getWidth() / 2, offset + 0.46f * mRadius,
getWidth() / 2 + 0.009f * mRadius, offset + 0.48f * mRadius);
mHourHandPath.lineTo(getWidth() / 2 + 0.018f * mRadius, getHeight() / 2 - 0.03f * mRadius);
mHourHandPath.close();
mHourHandPaint.setStyle(Paint.Style.FILL);
mCanvas.drawPath(mHourHandPath, mHourHandPaint);
mCircleRectF.set(getWidth() / 2 - 0.03f * mRadius, getHeight() / 2 - 0.03f * mRadius,
getWidth() / 2 + 0.03f * mRadius, getHeight() / 2 + 0.03f * mRadius);
mHourHandPaint.setStyle(Paint.Style.STROKE);
mHourHandPaint.setStrokeWidth(0.01f * mRadius);
mCanvas.drawArc(mCircleRectF, 0, 360, false, mHourHandPaint);
mCanvas.restore();
}
}
复制代码
到此为止咱们的小时钟就定义完啦,若是你们阅读过程当中发现错误,欢迎评论区中指出呦~~