前言
Android自定义view在实际开发中运用十分广泛,也有许多牛人写出很多牛掰的自定义view,无奈只能望其项背啊!
今天,虽然标题是自定义view实操,其实就是把别人写好的轮子拿过来改造改造,所以,“实操”是有水分滴。
效果图
在开始吐墨水之前,先给大家看一下效果图:
实现步骤
- 先定义一个叫MyClockView的类,继承自View
public class MyClockView extends View{}
- 然后定义需要用到的属性
1、全局画布
private Canvas mCanvas;
2、时分秒的Paint
private Paint mTextPaint;
3、文本颜色、字体大小设置
/* 亮色,用于秒针、渐变终止色 */
private int mLightColor;
/* 暗色,刻度线、渐变起始色 */
private int mDarkColor;
/* 背景色 */
private int mBackgroundColor;
/* 时分秒文本字体大小 */
private float mTextSize;
/* am pm 文本字体大小 */
private float mSize;
/* 时钟半径,不包括padding值 */
private float mRadius;
/* 刻度线长度 */
private float mScaleLength;
/* 梯度扫描渐变 */
private SweepGradient mSweepGradient;
/* 渐变矩阵,作用在SweepGradient */
private Matrix mGradientMatrix;
4、时分秒显示
/* 时 */
private String h;
/* 分 */
private String m;
/* 秒 */
private String s;
/* 区分是上午还是下午 */
private String am_pm;
5、三角形秒针属性
/* 秒针角度 */
private float mSecondDegree;
/* 秒针画笔 */
private Paint mSecondHandPaint;
/* 秒针路径 */
private Path mSecondHandPath = new Path();
6、圆弧刻度
/* 刻度圆弧画笔 */
private Paint mScaleArcPaint;
/* 刻度圆弧的外接矩形 */
private RectF mScaleArcRectF = new RectF();
/* 刻度线画笔 */
private Paint mScaleLinePaint;
- 重写构造
这里只用到两个构造,在其中一个构造方法中处理一些初始化操作。
public MyClockView(Context context) {
super(context);
}
public MyClockView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ClockView, 0, 0);
//背景色
mBackgroundColor = ta.getColor(R.styleable.ClockView_clock_backgroundColor, Color.parseColor("#237EAD"));
mLightColor = ta.getColor(R.styleable.ClockView_clock_lightColor, Color.parseColor("#ffffff"));
mDarkColor = ta.getColor(R.styleable.ClockView_clock_darkColor, Color.parseColor("#80ffffff"));
mTextSize = ta.getDimension(R.styleable.ClockView_clock_textSize, DensityUtils.sp2px(context, 48));
mSize = ta.getDimension(R.styleable.ClockView_clock_textSize, DensityUtils.sp2px(context, 28));
ta.recycle();
//时分秒绘制
mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mTextPaint.setStyle(Paint.Style.FILL);
mTextPaint.setColor(mLightColor);
//居中绘制文字
mTextPaint.setTextAlign(Paint.Align.CENTER);
mTextPaint.setTextSize(mTextSize);
mAmpmPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mAmpmPaint.setStyle(Paint.Style.FILL);
mAmpmPaint.setColor(mLightColor);
mAmpmPaint.setTextAlign(Paint.Align.CENTER);
mAmpmPaint.setTextSize(mSize);
//圆弧刻度
mScaleLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mScaleLinePaint.setStyle(Paint.Style.STROKE);
mScaleLinePaint.setColor(mBackgroundColor);
mScaleArcPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mScaleArcPaint.setStyle(Paint.Style.STROKE);
mGradientMatrix = new Matrix();
//三角形秒针
mSecondHandPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mSecondHandPaint.setStyle(Paint.Style.FILL);
mSecondHandPaint.setColor(mLightColor);
}
- 重写onMeasure方法,测量控件大小
@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;
}
}
- 绘制时分秒
/**
* 绘制中间的文字
*/
private void drawCenterTxt() {
mTextPaint.getTextBounds(h + ":" + m + ":" + s, 0, 0, mTextRect);
// mCanvas.drawText(am_pm, getWidth() / 2, getHeight() / 2 - mDefaultPadding, mAmpmPaint);
mCanvas.drawText(h + ":" + m + ":" + s, getWidth() / 2, getHeight() / 2 + mDefaultPadding, mTextPaint);// 绘制时间
}
实时获取系统时间
/**
* 获取当前时间
*/
private void getCurrentTime() {
Calendar calendar = Calendar.getInstance();
float milliSecond = calendar.get(Calendar.MILLISECOND);
float second = calendar.get(Calendar.SECOND) + milliSecond / 1000;// 精确到小数点后 保证圆滑
s = String.valueOf(calendar.get(Calendar.SECOND) + calendar.get(Calendar.MILLISECOND) / 1000);
if (Integer.parseInt(s) < 10) {
s = "0" + s;
}
m = String.valueOf(calendar.get(Calendar.MINUTE) + Integer.parseInt(s) / 60);
if (Integer.parseInt(m) < 10)
m = "0" + m;
h = String.valueOf(calendar.get(Calendar.HOUR_OF_DAY) + Integer.parseInt(m) / 60);
if (Integer.parseInt(h) < 10)
h = "0" + h;
if (calendar.get(Calendar.AM_PM) == Calendar.AM) {
am_pm = "上午";
} else {
am_pm = "下午";
}
mSecondDegree = second / 60 * 360;
}
然后在onDraw方法中调用
@Override
protected void onDraw(Canvas canvas) {
mCanvas = canvas;
getCurrentTime();
drawCenterTxt();
}
- 绘制圆弧刻度
在绘制圆弧刻度时,需要保证绘制的是圆形,有几个前提:
1、RectF切割的是正方形
2、切割出正方形后,就需要知道正方形的四个边距离控件边界的距离
3、因此,就需要计算上下左右的padding
private float mDefaultPadding;
private float mPaddingLeft;
private float mPaddingTop;
private float mPaddingRight;
private float mPaddingBottom;
这里需要重写onSizeChanged方法来计算这5个变量
@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;
mPaddingLeft = mDefaultPadding + w / 2 - mRadius + getPaddingLeft();// 左边距
mPaddingRight = mDefaultPadding + w / 2 - mRadius + getPaddingRight();// 右边距
mPaddingTop = mDefaultPadding + h / 2 - mRadius + getPaddingTop();// 上边距
mPaddingBottom = mDefaultPadding + h / 2 - mRadius + getPaddingBottom();// 下边距
}
对于圆形半径mRadius的计算公式如下:
mRadius = Math.min(w - getPaddingLeft() - getPaddingRight(),
h - getPaddingTop() - getPaddingBottom()) / 2;
解释:原本半径的取值是取控件宽和高中值最小的那个的一半作为半径值,也就是加入宽240,高480,那么半径的值就是120,但是,如果控件设置了padding,而半径的取值始终是宽高中小的那个一半,那么padding就会失去作用,因此,就需要在宽和高各自减去两个padding的值。
注意:在onSizeChanged的计算中,有一个mDefaultPadding变量,此变量的作用是,如果没有设置padding时,避免圆形刻度与控件边界相切。
以上设置处理完成之后就可以开始绘制圆形刻度了。
刻度盘
/**
* 画一圈梯度渲染的亮暗色渐变圆弧,重绘时不断旋转,上面盖一圈背景色的刻度线
*/
private void drawScaleLine() {
mCanvas.save();
mCanvas.translate(mCanvasTranslateX, mCanvasTranslateY);
mScaleArcRectF.set(mPaddingLeft + 1.5f * mScaleLength + mTextRect.height() / 2,
mPaddingTop + 1.5f * mScaleLength + mTextRect.height() / 2,
getWidth() - mPaddingRight - mTextRect.height() / 2 - 1.5f * mScaleLength,
getHeight() - mPaddingBottom - mTextRect.height() / 2 - 1.5f * mScaleLength);
// matrix默认会在三点钟方向开始颜色的渐变,为了吻合钟表十二点钟顺时针旋转的方向,把秒针旋转的角度减去90度
mGradientMatrix.setRotate(mSecondDegree - 90, getWidth() / 2, getHeight() / 2);
mSweepGradient.setLocalMatrix(mGradientMatrix);
mScaleArcPaint.setShader(mSweepGradient);
mCanvas.drawArc(mScaleArcRectF, 0, 360, false, mScaleArcPaint);
// 画背景色刻度线
for (int i = 0; i < 200; i++) {
mCanvas.drawLine(getWidth() / 2, mPaddingTop + mScaleLength + mTextRect.height() / 2,
getWidth() / 2, mPaddingTop + 2 * mScaleLength + mTextRect.height() / 2, mScaleLinePaint);
mCanvas.rotate(1.8f, getWidth() / 2, getHeight() / 2);
}
mCanvas.restore();
}
秒针
/**
* 秒针
*/
private void drawSecondNeedle() {
mCanvas.save();// ❑ save:用来保存Canvas的状态。save之后,可以调用Canvas的平移、放缩、旋转、错切、裁剪等操作。
mCanvas.rotate(mSecondDegree, getWidth() / 2, getHeight() / 2);// 设置指针位置
mSecondHandPath.reset();
float offset = mPaddingTop + mTextRect.height() / 2;
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执行的操作对后续的绘制有影响。
}
自此,自定义时钟控件就完成了。
此文控件改造自文章:《自定义 Android 钟表盘,这一篇就够了》
文章地址:https://www.cnblogs.com/yuanhao-1999/p/11065219.html