前几天闲暇之余,写了一个WatchView。
http://www.jianshu.com/p/db6d25c809d3
不过真正要用起来其实灵活性并不好,所以我又改了一下,使其属性可以定制化。
修改了:
- 在12点、3点、6点、9点刻度时,短线不会多画一条,导致这四条长线显得粗。
- 在秒针尾端加了一截,实际的表秒针也是有一截的。:) 实现方法很简单,就是在秒针绘制的反方向再画一条短线,相同的速度。
- 自定义属性,实现可以定制化。
还需要完善的地方:
- 1~12点时间文字除了12和6其他事歪的,想要把他掰正,我有强迫症:)
- 不同分辨率下实现文字线条灵活配置,希望能一个xml解决多个机型下的显示。
- 缺少一点立体感,比如背景可以加个过渡绘制,显得比较立体又感觉。
- 我对比了我mac上面的时间,view的时间相对于mac的时间慢了几秒3秒左右。没有确认具体原因。
在xml中添加属性,即可轻松的使用。
首先看下我定义了哪些属性:
自定义属性定制
attr.xml
<!--WatchView Style start-->
<declare-styleable name="WatchView">
<attr name="viewSize" format="dimension"></attr>
<attr name="borderColor" format="color"></attr>
<attr name="backgroundColor" format="color"></attr>
<attr name="textSize" format="dimension"></attr>
<attr name="secondlineColor" format="color"></attr>
<attr name="hourlineColor" format="color"></attr>
<attr name="textColor" format="color"></attr>
<attr name="minutelineColor" format="color"></attr>
<attr name="hourlinePaintStrockwidth" format="dimension"></attr>
<attr name="minutelinePaintStrockwidth" format="dimension"></attr>
<attr name="secondlinePaintStrockwidth" format="dimension"></attr>
<attr name="borderLinePaintStrockWidth" format="dimension"></attr>
</declare-styleable>
<!--WatchView Style end-->
相信通过名称你也看的出来是哪些属性:
size,边框颜色,时分秒线条颜色,文字颜色大小,线条粗细等。
使用
接下来在xml中引入我们的View,并使用自定义属性:
<com.example.xxiang1x.teststudio.WatchView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="35dip"
android:layout_marginTop="120dip"
app:backgroundColor="#EBEBF3"
app:borderColor="#808080"
app:borderLinePaintStrockWidth="2dp"
app:hourlineColor="#000000"
app:hourlinePaintStrockwidth="5dp"
app:minutelineColor="#000000"
app:minutelinePaintStrockwidth="5dp"
app:secondlineColor="#DC143C"
app:secondlinePaintStrockwidth="3dp"
app:textSize="22sp"
app:viewSize="350dp" />
java代码实现
java代码中有足够多的注释相信能看懂。代码不至于特别糟糕吧:)
package com.example.xxiang1x.teststudio;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.util.AttributeSet;
import android.view.View;
import java.util.Calendar;
import java.util.GregorianCalendar;
/**
* 手表表盘
* ReadMe:
* 实现:表盘中,各个元素比如时分秒的线颜色,粗细,背景颜色,边框颜色,字体颜色,大小等都是可以在xml中自定义实现各个需求
* 目前还未完善的部分是还未在各个分辨率下查看效果,因为每个机型不一样分辨率也不一样,导致显示的效果也不一样。如何以
* 更小的改动来适应变化的因素,需要另外完善一下。另外,时间的文字还可以改成全部是正的,而不是除了12和6点其他都是歪的。
*
*/
public class WatchView extends View {
//create some variables
/**
* 分针画笔
*/
private Paint minPaint;
/**
* 秒针画笔
*/
private Paint secondPaint;
/**
* 时针画笔
*/
private Paint hourPaint;
/**
* 表盘外轮廓画笔
*/
private Paint circlePaint;
/**
* 表盘文字的画笔
*/
private Paint textPaint;
/**
* 表盘内短线画笔
*/
private Paint linePaint;
/**
* 背景颜色画笔
*/
private Paint backgroundPaint;
private static final double ROUND = 2d * Math.PI;
private static final double QUARTER = 1d / 4d;
private Handler mHandler = new Handler(Looper.getMainLooper());
private Calendar calendar = new GregorianCalendar();
public WatchView(Context context) {
super(context);
initAttributes(context, null);
initPaint();
}
public WatchView(Context context, AttributeSet attrs) {
super(context, attrs);
initAttributes(context, attrs);
initPaint();
}
/////////////////////////////Attributes from attr.xml
/**
* 整个WatchView的大小,因为WatchView是一个圆,所以我们在处理的时候默认就是以正方形为基础来看待。width==height
*/
private int mSize = 220;
/**
* 外边框的颜色
*/
private int borderColor = Color.GRAY;
/**
* 背景颜色
*/
private int backgroundColor = Color.WHITE;
/**
* 秒针颜色
*/
private int secondLineColor = Color.RED;
/**
* 分针颜色
*/
private int minuteLineColor = Color.BLUE;
/**
* 时针颜色
*/
private int hourLineColor = Color.GRAY;
/**
* 时间文字颜色
*/
private int textColor = Color.GRAY;
/**
* 时间文字大小
*/
private int textSize = 22;
//////////////////////////Others:Paint: strockWidth;
/**
* 时针划线粗细
*/
private int hourLinePaintStrockWidth = 5;
/**
* 秒针划线粗细
*/
private int secondLinePaintStrockWidth = 3;
/**
* 分针线粗细
*/
private int minuteLinePaintStrockWidth = 5;
/**
* 边框线粗细
*/
private int borderLinePaintStrockWidth = 5;
/**
* text线条粗细
*/
private int textPaintStrockWidth = 5;
/**
* initialize the attributes.
*
* @param ctx
* @param attrs
*/
private void initAttributes(Context ctx, AttributeSet attrs) {
TypedArray typeArray = null;
try {
typeArray = ctx.obtainStyledAttributes(attrs, R.styleable.WatchView);
mSize = typeArray.getDimensionPixelOffset(R.styleable.WatchView_viewSize, mSize);
borderColor = typeArray.getColor(R.styleable.WatchView_borderColor, borderColor);
backgroundColor = typeArray.getColor(R.styleable.WatchView_backgroundColor, backgroundColor);
secondLineColor = typeArray.getColor(R.styleable.WatchView_secondlineColor, secondLineColor);
minuteLineColor = typeArray.getColor(R.styleable.WatchView_minutelineColor, minuteLineColor);
hourLineColor = typeArray.getColor(R.styleable.WatchView_hourlineColor, hourLineColor);
textColor = typeArray.getColor(R.styleable.WatchView_textColor, textColor);
textSize = typeArray.getDimensionPixelSize(R.styleable.WatchView_textSize, 22);
hourLinePaintStrockWidth = typeArray.getDimensionPixelSize(R.styleable.WatchView_hourlinePaintStrockwidth, 5);
minuteLinePaintStrockWidth = typeArray.getDimensionPixelSize(R.styleable.WatchView_minutelinePaintStrockwidth, 5);
secondLinePaintStrockWidth = typeArray.getDimensionPixelSize(R.styleable.WatchView_secondlinePaintStrockwidth, 3);
borderLinePaintStrockWidth = typeArray.getDimensionPixelSize(R.styleable.WatchView_borderLinePaintStrockWidth, 3);
} finally {
typeArray.recycle();
}
}
/**
* 创建所有的paint
*/
private void initPaint() {
minPaint = getLinePaint(minuteLineColor, minuteLinePaintStrockWidth, Paint.Style.FILL_AND_STROKE);
secondPaint = getLinePaint(secondLineColor, secondLinePaintStrockWidth, Paint.Style.FILL_AND_STROKE);
hourPaint = getLinePaint(hourLineColor, hourLinePaintStrockWidth, Paint.Style.FILL_AND_STROKE);
circlePaint = getLinePaint(borderColor, borderLinePaintStrockWidth, Paint.Style.STROKE);
textPaint = getTextPaint(textColor, textSize, textPaintStrockWidth, Paint.Style.FILL);
linePaint = getLinePaint(textColor, textPaintStrockWidth, Paint.Style.FILL);
backgroundPaint = getLinePaint(backgroundColor, mSize / 2, Paint.Style.FILL);
}
/**
* get the Line Paint
*
* @param color
* @param strockWidth
* @param style
* @return
*/
private Paint getLinePaint(int color, int strockWidth, Paint.Style style) {
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FAKE_BOLD_TEXT_FLAG);
paint.setAntiAlias(true);
paint.setAlpha(1);
paint.setColor(color);
paint.setStyle(style);
paint.setStrokeWidth(strockWidth);
paint.setStrokeCap(Paint.Cap.ROUND);
paint.setTextAlign(Paint.Align.CENTER);
paint.setStrokeCap(Paint.Cap.ROUND);
paint.setDither(true);//设置图像抖动处理
paint.setStrokeJoin(Paint.Join.ROUND);//画笔线等连接处的轮廓样式
paint.setSubpixelText(true);
return paint;
}
/**
* get a paint by ur request.
*
* @param color
* @param textSize
* @param strockWidth
* @param style
* @return
*/
private Paint getTextPaint(int color, int textSize, int strockWidth, Paint.Style style) {
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FAKE_BOLD_TEXT_FLAG);
paint.setAntiAlias(true);
paint.setTextSize(textSize);
paint.setAlpha(1);
paint.setColor(color);
paint.setStyle(style);
paint.setStrokeWidth(strockWidth);
paint.setStrokeCap(Paint.Cap.ROUND);
paint.setTextAlign(Paint.Align.CENTER);
paint.setStrokeCap(Paint.Cap.ROUND);
paint.setDither(true);//设置图像抖动处理
paint.setStrokeJoin(Paint.Join.ROUND);//画笔线等连接处的轮廓样式
paint.setSubpixelText(true);
return paint;
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public WatchView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//获取圆心坐标
int x, y;
x = y = mSize / 2;
//半径
int radius = Math.min(x, y) - borderLinePaintStrockWidth;
// 为了让秒针连续转动,所以秒针的角度肯定不是从「秒」这个整数里来
// 刚好秒针走一圈是 1 分钟,那么,就用分乘以「一圈(2π)」就是秒针要走的弧度数了
float millis = calendar.get(Calendar.MILLISECOND) / 1000f;
float second = (calendar.get(Calendar.SECOND) + millis) / 60f;
float minute = (calendar.get(Calendar.MINUTE) + second) / 60f;
float hour = (calendar.get(Calendar.HOUR) + minute) / 12f;
//画背景颜色
canvas.drawCircle(x, y, radius, backgroundPaint);
//draw outer circle
circlePaint.setColor(borderColor);
circlePaint.setStyle(Paint.Style.STROKE);
canvas.drawCircle(x, y, radius, circlePaint);
//draw the hour line .
drawHourNumbers(canvas, textPaint, mSize, mSize);
//draw the minutes line .
drawMinuteLine(canvas, textPaint, mSize, mSize);
//画时针,分针,秒针
drawHand(canvas, hourPaint, x, y, radius * 0.6f, hour);
drawHand(canvas, minPaint, x, y, radius * 0.8f, minute);
drawHand(canvas, secondPaint, x, y, radius * 0.9f, second);
drawSecondExtraHand(canvas, secondPaint, x, y, radius * 0.1f, second);
//draw center point
circlePaint.setColor(secondLineColor);
circlePaint.setStyle(Paint.Style.FILL);
canvas.drawCircle(x, y, secondLinePaintStrockWidth, circlePaint);
}
/**
* 画小时的线,画这个线都是以边框宽度以及整个WatchView的大小来计算,这样灵活性比较好。不会因为改动了
* WatchView的大小而导致要重新调整各个计算。
*
* @param canvas
* @param paint
* @param width
* @param height
*/
private void drawHourNumbers(Canvas canvas, Paint paint, int width, int height) {
//12个小时,12条线。
for (int i = 1; i < 13; i++) {
canvas.save(); //save current state of canvas.
canvas.rotate(360 / 12 * i, width / 2, height / 2);
//绘制表盘
canvas.drawLine(width / 2, borderLinePaintStrockWidth, width / 2, height / 15, paint);
//绘制文字
canvas.drawText(String.valueOf(i), width / 2, height / 15 * 2, paint);
//恢复开始位置
canvas.restore();
}
}
/**
* 画分钟的线
*
* @param canvas
* @param paint
* @param width
* @param height
*/
private void drawMinuteLine(Canvas canvas, Paint paint, int width, int height) {
//一共分出来有60条线
for (int i = 1; i < 61; i++) {
float degree = 360 / 12 / 5 * i;
//这里要避开下面几个角度,否则会发生重叠绘制线条。你可以把判断去掉发现12点,3点,6点,9点的线条哦要粗得多。
if (degree % 90 != 0) {//90 ,180,270,360
canvas.save(); //save current state of canvas.
canvas.rotate(degree, width / 2, height / 2);
//绘制表盘
canvas.drawLine(width / 2, borderLinePaintStrockWidth, width / 2, height/15/2, paint);
//恢复开始位置
canvas.restore();
}
}
}
/**
* @param canvas
* @param paint
* @param x
* @param y
* @param length
* @param round
*/
private void drawHand(Canvas canvas, Paint paint, float x, float y, float length, float round) {
// 三角函数的坐标轴是以 3 点方向为 0 的,所以记得要减去四分之一个圆周哦
double radians = (round - QUARTER) * ROUND;
canvas.drawLine(
x,
y,
x + (float) Math.cos(radians) * length,
y + (float) Math.sin(radians) * length,
paint);
}
/**
* draw the opposite direction's line .
*
* @param canvas
* @param paint
* @param x
* @param y
* @param length
* @param round
*/
private void drawSecondExtraHand(Canvas canvas, Paint paint, float x, float y, float length, float round) {
// 三角函数的坐标轴是以 3 点方向为 0 的,所以记得要减去四分之一个圆周哦
double radians = (round - QUARTER * 3) * ROUND;
canvas.drawLine(
x,
y,
x + (float) Math.cos(radians) * length,
y + (float) Math.sin(radians) * length,
paint);
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
mHandler.post(r);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
mHandler.removeCallbacksAndMessages(null);//remove all of the messages.
}
Runnable r = new Runnable() {
@Override
public void run() {
calendar.setTimeInMillis(System.currentTimeMillis());
invalidate();
mHandler.postDelayed(this, 1000 / 60);
}
};
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int newSpec = MeasureSpec.makeMeasureSpec(mSize, MeasureSpec.getMode(Math.min(widthMeasureSpec, heightMeasureSpec)));
super.onMeasure(newSpec, newSpec);
}
//////////////////////////////
/**
* dp转化为px unit
*
* @param dpValue
* @return
*/
private int dp2px(int dpValue) {
if (dpValue == 0) {
return 0;
}
final float density = getResources().getDisplayMetrics().density;
return (int) ((dpValue * density) + 0.5f);
}
/**
* 根据手机的分辨率从 px(像素) 的单位 转成为 dp
*/
public int px2dp(float pxValue) {
final float scale = getResources().getDisplayMetrics().density;
return (int) (pxValue / scale + 0.5f);
}
}
运行效果
没有找到一个比较好用的录制工具,这个gif录制出来效果不好。看起来秒针走一回停一下似得。擦....
结尾
- 自定义View其实蛮重要,很多知识点还需要学习,深入研究。希望后期会有比较好的成果展示分享出来。