目录
前言
之前项目有个需求是在桌面上增加一个桌面时钟,当时是参考别人的博客,在其基础上添加了属性动画然后实现的。因此今天特地重新写了一遍这个自定义view 加深印象并分享出来
效果图
用手机拍的视频,然后转为GIF的,因为是试用的,所以就是这个渣渣效果,还有水印,求推荐一个好用的格式转换工具
项目实现
为了编写方便,这里没有用到自定义属性,有需要的话,可以自行添加自定义属性,同时在代码中注释很详细,文章这里只简单介绍一下
用到的属性
private Paint hPaint ,mPaint ,sPaint ,circlePaint ,cPointPaint,paintDegree,txtPaint,titlePaint;
private final int maxWidth = 400;
int width ,height;
int hSound = 0;
int mSound = 0;
int sSound = 0;
private int hcount = 0;//当前小时数
private int mcount = 0;//当前分钟数
private int scount = 0;//当前秒钟数
private final int hScale = 30;//每小时之间30度
private static final int mScale = 6;//每分钟之间是6度
RectF hRect , mRect ,sRect ;//三个
boolean first = true;//执行动画之后再转动指针
重写三个构造方法
public ClockView(Context context) {
super(context);
init();
}
public ClockView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public ClockView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
初始化各个指针的画笔,指针的矩形
这里各种new操作不要放在Ondraw方法中,避免重复实现
private void init(){
//外圆盘画笔
circlePaint = new Paint();
circlePaint.setAntiAlias(true);
circlePaint.setDither(true);
circlePaint.setStyle(Paint.Style.STROKE);
circlePaint.setColor(Color.BLACK);
circlePaint.setStrokeWidth(1);
//圆心画笔
cPointPaint = new Paint();
cPointPaint.setAntiAlias(true);
cPointPaint.setDither(true);
cPointPaint.setStyle(Paint.Style.FILL_AND_STROKE);
cPointPaint.setColor(0xff4CAF50);//绿色
cPointPaint.setStrokeWidth(2);
//时针画笔
hPaint = new Paint();
hPaint.setAntiAlias(true);
hPaint.setDither(true);
hPaint.setStyle(Paint.Style.FILL_AND_STROKE);
hPaint.setColor(0xff349CE2);//蓝色
hPaint.setStrokeWidth(7);
//时针矩形
hRect = new RectF(-16,0,89,0);
//分针画笔
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setDither(true);
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
mPaint.setColor(0xffDB2540);//红色
mPaint.setStrokeWidth(6);
//分针矩形
mRect = new RectF(-25,0,140,0);
//秒针画笔
sPaint = new Paint();
sPaint.setAntiAlias(true);
sPaint.setDither(true);
sPaint.setStyle(Paint.Style.FILL_AND_STROKE);
sPaint.setColor(0xff4CAF50);//绿色
sPaint.setStrokeWidth(5);
//秒钟矩形
sRect = new RectF(-30,0,180,0);
//刻度画笔
paintDegree = new Paint();
paintDegree.setColor(0xff000000);
paintDegree.setStyle(Paint.Style.STROKE);
paintDegree.setAntiAlias(true);
//文字画笔
txtPaint = new Paint();
txtPaint.setColor(0xff000000);
txtPaint.setTextSize(20);
txtPaint.setAntiAlias(true);
//标题画笔
titlePaint = new Paint();
titlePaint.setColor(0xff000000);
titlePaint.setTextSize(45);
titlePaint.setAntiAlias(true);
}
重写 onMeasure方法
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = MeasureSpec.getSize(widthMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int height = MeasureSpec.getSize(heightMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
if (widthMode == MeasureSpec.AT_MOST && heightMode ==MeasureSpec.AT_MOST){
setMeasuredDimension(maxWidth,maxWidth);
}else if (widthMode == MeasureSpec.AT_MOST ){
setMeasuredDimension(maxWidth,width);
}else if (heightMode ==MeasureSpec.AT_MOST ){
setMeasuredDimension(height,maxWidth);
}
}
获取系统的时间
private void getDatas() {
SimpleDateFormat format = new SimpleDateFormat("HH,mm,ss");
String time = format.format(new Date());
try {
String s[] = time.split(",");
hcount = Integer.parseInt(s[0]);
mcount = Integer.parseInt(s[1]);
scount = Integer.parseInt(s[2]);
} catch (Exception ex) {
ex.printStackTrace();
}
}
重写ondraw方法
首先获取并计算宽高,设置各指针长度以及外圆的半径
super.onDraw(canvas);
width = getMeasuredWidth()-getPaddingStart()-getPaddingEnd();
height = getMeasuredHeight()-getPaddingBottom()-getPaddingTop();
width = height = Math.min(width,height);
//圆心点
int cpoints = width/2;
hSound = width/8;//时针长度
mSound = width/6;//分针长度
sSound = width/4;//秒针长度
int radius =cpoints*3/4;//外圆半径
借用一下别人的图
这里判断是否是第一次显示时钟,等于true为第一次,然后绘制标题,位置的计算根据上图
if(!first){
getDatas();
}
String titleText = "三原色时钟";
//X坐标等于圆心的X坐标减去文字的一半的长度,Y坐标等于圆心的Y坐标减圆的半径再减20
canvas.drawText(titleText,cpoints - titlePaint.measureText(titleText) / 2 ,cpoints-radius-20,titlePaint);
然后绘制刻度,圆盘
//画出12个小时的刻度线及文字
for (int i = 0; i < 12; i++) {
String txtTime = Integer.toString(i);
//3,6,9,12比其他的略粗略长
if(i%3==0) {
if (i==0){
txtTime = "12";
}
paintDegree.setStrokeWidth(5);
canvas.drawLine(width / 2, height / 2 - radius, width / 2, height / 2 - radius + 20, paintDegree);
}else{
paintDegree.setStrokeWidth(4);
canvas.drawLine(width / 2, height / 2 - radius, width / 2, height / 2 - radius + 15, paintDegree);
}
canvas.drawText(txtTime,cpoints - txtPaint.measureText(txtTime) / 2,height / 2 - radius + 40,txtPaint);
canvas.rotate(hScale, width / 2, height / 2);
}
//画出60个分钟的刻度线
for (int x = 0; x < 60; x++) {
paintDegree.setStrokeWidth(3);
if (x % 5 != 0) {//当x % 5 == 0时即是时钟刻度,因此不需要绘制,避免重复绘制
canvas.drawLine(width / 2, height / 2 - radius, width / 2, height / 2 - radius + 8, paintDegree);
}
canvas.rotate(mScale, width / 2, height / 2);
}
//画外层圆
canvas.drawCircle(cpoints,cpoints,radius,circlePaint);
//画内层圆
canvas.drawCircle(cpoints,cpoints,radius/6,circlePaint);
//平移至中心点
canvas.translate(cpoints,cpoints);
//保存画布
canvas.save();
最后是绘制三个指针
//int hRotate = 270 + hScale * hcount;
int offset = 30 * mcount / 60;
offset -= offset % mScale;//时针相对分针数,有一个偏移量
int hRotate = 270 + hScale * hcount + offset;
canvas.rotate(hRotate);
// canvas.drawLine(0, -10, 0, hSound, hPaint);//画时针
canvas.drawRoundRect(hRect,15,15,hPaint);//画时针
canvas.restore();
canvas.save();
int mRotate = 270 + mScale * mcount ;
canvas.rotate(mRotate);
canvas.drawRoundRect(mRect,25,25,mPaint);//画分针
//canvas.drawLine(0, -15, 0, mSound, mPaint);//画分针
canvas.restore();
canvas.save();
//一圈360度,总共60秒,因此时间每多一秒,度数加6
int sRotate = 270 + mScale * scount ;
canvas.rotate(sRotate);
//canvas.drawLine(0, -25, 0, sSound, sPaint);//画秒针
canvas.drawRoundRect(sRect,15,15,sPaint);//画秒针
绘制指针上的圆心点
canvas.drawCircle(0,0,6,cPointPaint);//画圆心
第一次出现时不转动指针
if(!first){
postInvalidateDelayed(1000);
}
最后给时钟添加上属性动画
在View中添加一个公共方法给外部调用
public void startAnim(){
getDatas();
final ValueAnimator animatorh = ValueAnimator.ofInt(0,hcount>12?hcount-12:hcount);//大于十二点时减去12 避免转两圈
final ValueAnimator animatorm=ValueAnimator.ofInt(0,mcount);
final ValueAnimator animators=ValueAnimator.ofInt(0,scount);
//设置动画时长
animatorh.setDuration(1500);
animatorm.setDuration(1500);
animators.setDuration(1500);
animatorh.setInterpolator(new LinearInterpolator());
animatorh.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
hcount = (int)animation.getAnimatedValue();
}
});
animatorh.start();
animatorm.setInterpolator(new LinearInterpolator());
animatorm.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mcount = (int)animation.getAnimatedValue();
}
});
animatorm.start();
animators.setInterpolator(new LinearInterpolator());
animators.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
scount = (int)animation.getAnimatedValue();
postInvalidate();//添加之后动画才会执行,不然看不到效果
}
});
animators.start();
//添加动画完成时的监听,在动画完成之后开始指针的转动
animators.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
first = false ;
postInvalidate();
}
});
}
有时不需要执行动画的话,可以再添加一个公共方法直接设置first 为false
public void SetFirstInit(Boolean ttt) {
first =ttt;
}
//然后在布局添加自定义view即可
在Activity中使用ClockView并根据需求调用对应的方法
到这里文章就结束了,觉得还算可以的,欢迎点赞啊