前言
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