我們今天來自定義一個鬧鐘。效果如下:
具體代碼請參考:Demo
第一步:自定義屬性
在檔案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>