我们今天来自定义一个闹钟。效果如下:
第一步:自定义属性
在文件app/src/main/res/values/attrs.xml中加入自定义属性:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="ClockView">
<attr name="circle_color" format="color"/>
<attr name="circle_scale_color" format="color"/>
<attr name="sec_point_color" format="color"/>
<attr name="min_point_color" format="color"/>
<attr name="hour_point_color" format="color"/>
<attr name="dot_color" format="color"/>
</declare-styleable>
...
</resources>
第二步:重写onMeasure 、onLayout、onDraw方法
重写onMeasure主要是为了解决,当wrap_content时,视图会占满整个父布局,所以要设置一个默认值。
重写onLayout是因为遵守良好的习惯。因为onMeasure可能会重复调用多次,那么视图的宽高就可能在最后一次才能确定下来。当然这种情况更可能出现在继承ViewGroup中的自定义视图中。我们本例是继承View,所以一般不会出现上述情况。onLayout中一定能拿到视图的宽高而且是确定下来的。
onDraw方法一般都需要重写。
package com.wong.clock;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.Calendar;
public class ClockView extends View {
/*画笔*/
private Paint mPaintCircle = new Paint();
private Paint mPaintDot = new Paint();
private Paint mPaintScale = new Paint();
private Paint mPaintHour = new Paint();
private Paint mPaintMin = new Paint();
private Paint mPaintSec = new Paint();
private int width = 0;
private int height = 0;
private int circleColor = Color.WHITE;
private int scaleColor = Color.GRAY;
private int dotColor = Color.BLACK;
private int hourPointColor = Color.BLACK;
private int minPointColor = Color.BLACK;
private int secPointColor = Color.RED;
private float hourPointWidth = 12f;
private float minPointWidth = 8f;
private float secPointWidth = 2f;
private float dotWidth = 0;
public void setCircleColor(int circleColor){
this.circleColor = circleColor;
}
public void setDotColor(int dotColor) {
this.dotColor = dotColor;
}
public void setHourPointColor(int hourPointColor) {
this.hourPointColor = hourPointColor;
}
public void setMinPointColor(int minPointColor) {
this.minPointColor = minPointColor;
}
public void setSecPointColor(int secPointColor) {
this.secPointColor = secPointColor;
}
public void setHourPointWidth(float hourPointWidth) {
this.hourPointWidth = hourPointWidth;
}
public void setMinPointWidth(float minPointWidth) {
this.minPointWidth = minPointWidth;
}
public void setSecPointWidth(float secPointWidth) {
this.secPointWidth = secPointWidth;
}
public ClockView(Context context) {
super(context);
init(context,null);
}
public ClockView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(context,attrs);
}
public ClockView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context,attrs);
}
public ClockView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init(context,attrs);
}
private void init(@NonNull Context context,AttributeSet attrs){
/*初始化xml属性的值*/
if(attrs != null) {
TypedArray typedArray = context.obtainStyledAttributes(attrs,R.styleable.ClockView);
circleColor = typedArray.getColor(R.styleable.ClockView_circle_color,circleColor);
scaleColor = typedArray.getColor(R.styleable.ClockView_circle_scale_color,scaleColor);
dotColor = typedArray.getColor(R.styleable.ClockView_dot_color,dotColor);
hourPointColor = typedArray.getColor(R.styleable.ClockView_hour_point_color,hourPointColor);
minPointColor = typedArray.getColor(R.styleable.ClockView_min_point_color,minPointColor);
secPointColor = typedArray.getColor(R.styleable.ClockView_sec_point_color,secPointColor);
hourPointWidth = typedArray.getDimension(R.styleable.ClockView_hour_point_width,hourPointWidth);
minPointWidth = typedArray.getDimension(R.styleable.ClockView_min_point_width,minPointWidth);
secPointWidth = typedArray.getDimension(R.styleable.ClockView_sec_point_width,secPointWidth);
dotWidth = typedArray.getDimension(R.styleable.ClockView_dot_width,dotWidth);
typedArray.recycle();
}
/*表盘*/
mPaintCircle.setStyle(Paint.Style.FILL);
mPaintCircle.setAntiAlias(true);
mPaintCircle.setColor(circleColor);
/*刻度*/
mPaintScale.setStyle(Paint.Style.FILL);
mPaintScale.setAntiAlias(true);
mPaintScale.setColor(scaleColor);
/*圆心的圆*/
mPaintDot.setStyle(Paint.Style.FILL);
mPaintDot.setAntiAlias(true);
mPaintDot.setColor(dotColor);
/*hour*/
mPaintHour.setAntiAlias(true);
mPaintHour.setStrokeWidth(hourPointWidth);
mPaintHour.setStyle(Paint.Style.FILL);
mPaintHour.setColor(hourPointColor);
/*min*/
mPaintMin.setAntiAlias(true);
mPaintMin.setStrokeWidth(minPointWidth);
mPaintMin.setStyle(Paint.Style.FILL);
mPaintMin.setColor(minPointColor);
/*sec*/
mPaintSec.setAntiAlias(true);
mPaintSec.setStrokeWidth(secPointWidth);
mPaintSec.setStyle(Paint.Style.FILL);
mPaintSec.setColor(secPointColor);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int widthW = widthSize - getPaddingLeft()-getPaddingRight();
int heightH = heightSize - getPaddingTop() - getPaddingBottom();
switch (widthMode){
case MeasureSpec.EXACTLY:
case MeasureSpec.AT_MOST:
if(getLayoutParams().width == ViewGroup.LayoutParams.MATCH_PARENT) {
width = widthW;
}else if(getLayoutParams().width == ViewGroup.LayoutParams.WRAP_CONTENT){
width = (int)getResources().getDimension(R.dimen.clock_view_width);
}else{
width = widthW;
}
break;
case MeasureSpec.UNSPECIFIED:
width = widthW;
break;
}
switch (heightMode){
case MeasureSpec.EXACTLY:
case MeasureSpec.AT_MOST:
if(getLayoutParams().width == ViewGroup.LayoutParams.MATCH_PARENT) {
height = heightH;
}else if(getLayoutParams().width == ViewGroup.LayoutParams.WRAP_CONTENT){
height = (int)getResources().getDimension(R.dimen.clock_view_height);
}else{
height = heightH;
}
break;
case MeasureSpec.UNSPECIFIED:
height = heightH;
break;
}
setMeasuredDimension(width,height);
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
width = getMeasuredWidth();
height = getMeasuredHeight();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
/*设置画布的颜色*/
canvas.drawColor(Color.WHITE);
int radius = Math.min(width,height)/2;
/*画刻盘*/
canvas.drawCircle(width/2,height/2,radius,mPaintCircle);
/*绘制12个点,代表12小时*/
float scaleRadius = radius / 24;
float dotY = (float) height/2-radius+5*scaleRadius/4;
for(int i = 0; i < 12; i++){
canvas.drawCircle(width/2, dotY, scaleRadius, mPaintScale);
canvas.rotate(30f,width/2,height/2);
}
/*绘制时针、分针、秒针*/
Calendar calendar = Calendar.getInstance();
float hour = calendar.get(Calendar.HOUR);
float min = calendar.get(Calendar.MINUTE);
float sec = calendar.get(Calendar.SECOND);
/*时针转过的角度*/
float angleHour = hour * (float) (360 / 12) + min/60*30;
/*分针转过的角度*/
float angleMin = min * 6;
/*秒针转过的角度*/
float angleSec = sec * 6;
/*绘制时针*/
float hourY = (float) height/2-radius+(float) radius / 2;
canvas.save();
canvas.rotate(angleHour,width/2,height/2);
canvas.drawLine(width/2,height/2,width/2,hourY,mPaintHour);
canvas.restore();
/*绘制分针*/
float minY = (float) height/2-radius+(float) radius / 3;
canvas.save();
canvas.rotate(angleMin,width/2,height/2);
canvas.drawLine(width/2,height/2,width/2,minY,mPaintMin);
canvas.restore();
/*绘制分针*/
float secY = (float) height/2-radius+(float) radius / 5;
canvas.save();
canvas.rotate(angleSec,width/2,height/2);
canvas.drawLine(width/2,height/2,width/2,secY,mPaintSec);
canvas.restore();
/*在圆心画个圆盖住三条指针的连接处*/
float dotRadius = Math.max(dotWidth,(float) 0.05*radius);
canvas.drawCircle(width/2,height/2,dotRadius,mPaintDot);
/*每隔1秒就刷新*/
postDelayed(new Runnable() {
@Override
public void run() {
invalidate();
}
},1000);
}
}
代码的具体含义,请看代码中的注释。
第三步:在布局文件中使用闹钟视图
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.wong.clock.ClockView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
android:background="@color/colorPrimaryDark"
app:circle_color="@color/colorPrimaryDark"
app:circle_scale_color="@android:color/white"
app:dot_color="@android:color/white"
app:hour_point_color="@android:color/white"
app:min_point_color="@android:color/white"
app:sec_point_color="@android:color/holo_red_dark"
app:sec_point_width="3dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
效果如前所示。
谢谢阅读!
来源:CSDN
作者:WongKyunban
链接:https://blog.csdn.net/weixin_40763897/article/details/104007956