關於本文:本文原先在個人 CSDN 博客發佈(由圖片水印能發現),整理以往博客過程當中,發現當時總結的很仔細,因此將其遷移到這裏,但願對你們在自定義 View 方面,能有所幫助 💗php
Android 自定義 View 應用很是普遍,最近逛 github 是偶然發現一個 Demo 感受寫的很好,我結合着這個項目的內容,給你們講講如何繪製時鐘錶盤,也算是加深下本身對自定義 View 的理解,涉及內容比較多,你們慢慢吸取。html
開始以前,先讓你們看看最後的效果java
讓咱們先搭建這個 Viewandroid
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="ClockView">
<attr name="clock_backgroundColor" format="color" />
<attr name="clock_lightColor" format="color" />
<attr name="clock_darkColor" format="color" />
<attr name="clock_textSize" format="dimension" />
</declare-styleable>
</resources>
複製代碼
小時圓環組成分爲外圍的圓弧和四個小時數字,因此咱們須要的東西很明確了。c++
重寫構造方法:git
/* 暗色,圓弧、刻度線、時針、漸變起始色 */
private int mDarkColor;
/* 小時文本字體大小 */
private float mTextSize;
private Paint mTextPaint;
private Paint mCirclePaint;
public ClockView(Context context) {
super(context);
}
public ClockView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ClockView, 0, 0);
mDarkColor = ta.getColor(R.styleable.ClockView_clock_darkColor, Color.parseColor("#80ffffff"));
mTextSize = ta.getDimension(R.styleable.ClockView_clock_textSize, DensityUtils.sp2px(context, 14));
ta.recycle();
// ANTI_ALIAS_FLAG 平滑繪製 不帶磕磕絆絆
mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mTextPaint.setStyle(Paint.Style.FILL);
mTextPaint.setColor(mDarkColor);
// 居中繪製文字
mTextPaint.setTextAlign(Paint.Align.CENTER);
mTextPaint.setTextSize(mTextSize);
mCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mCirclePaint.setColor(mDarkColor);
// 官方:使用此樣式繪製的幾何和文本將被描邊,尊重繪畫上與筆劃相關的字段。
// 說白了就是,不要吧這塊扇形都上色,只是把最外層的邊描下
mCirclePaint.setStyle(Paint.Style.STROKE);
mCirclePaint.setStrokeWidth(mCircleStrokeWidth);// 描邊寬度
}
複製代碼
別忘了重寫 onMeasure 方法,測量控件大小 關於具體的測量方法,請參考自定義 View 的文章,無非就是對 MeasureSpec 的三種 mode 類型進行分類處理罷了。github
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getMeasureResult(widthMeasureSpec), getMeasureResult(heightMeasureSpec));
}
private int getMeasureResult(int measureSpec){
int defaultSize = 800;
int size = MeasureSpec.getSize(measureSpec);
int mode = MeasureSpec.getMode(measureSpec);
switch (mode){
case MeasureSpec.UNSPECIFIED:
return defaultSize;
case MeasureSpec.AT_MOST:
return Math.max(defaultSize, size);
case MeasureSpec.EXACTLY:
return size;
default:
return defaultSize;
}
}
複製代碼
咱們知道,對於繪製圓與橢圓這類圖形,常常須要先用 RectF 設置一個邊界矩形再進行繪製。若是是繪製文本則是 Rect 。編程
因此繪製外圍圓環,首先要定義一個 RectF 變量用於繪製圓環,在定義一個 Rect 變量,用於繪製文字。canvas
注 mCanvas 繪圖類是 onDraw 中的參數,咱們在 onDraw 中將它保存起來ide
// 測量文字大小
private Rect mTextRect = new Rect();
private RectF mCircleRectF = new RectF();
/* 小時圓圈線條寬度 */
private float mCircleStrokeWidth = 4;
/** * 畫最外圈的時間 十二、三、六、9 文本和4段弧線 */
private void drawOutSideArc() {
String[] timeList = new String[]{"12", "3", "6", "9"};
//計算數字的高度
mTextPaint.getTextBounds(timeList[0], 0, timeList[0].length(), mTextRect);// 計算後放回一個矩形存在 mTextRect (涉及c++原生方法,會用就行不要深究)
mCircleRectF.set(mTextRect.width() / 2 + mCircleStrokeWidth / 2,// 畫一個外界小矩形,在矩形裏畫圓
mTextRect.height() / 2 + mCircleStrokeWidth / 2,
getWidth() - mTextRect.width() / 2 - mCircleStrokeWidth / 2,
getHeight() - mTextRect.height() / 2 - mCircleStrokeWidth / 2);
mCanvas.drawText(timeList[0], getWidth() / 2, mCircleRectF.top + mTextRect.height() / 2, mTextPaint);// 定點寫字,經過 RectF 取得邊界值,因爲是頂點在右上方寫字,因此要向下平移
mCanvas.drawText(timeList[1], mCircleRectF.right, getHeight() / 2 + mTextRect.height() / 2, mTextPaint);
mCanvas.drawText(timeList[2], getWidth() / 2, mCircleRectF.bottom + mTextRect.height() / 2, mTextPaint);
mCanvas.drawText(timeList[3], mCircleRectF.left, getHeight() / 2 + mTextRect.height() / 2, mTextPaint);
//畫鏈接數字的4段弧線
for (int i = 0; i < 4; i++) {
// 畫四個弧線 sweepAngle 弧線角度(扇形角度)
mCanvas.drawArc(mCircleRectF, 5 + 90 * i, 80, false, mCirclePaint);
}
}
複製代碼
接着,咱們重寫 onDraw() 方法,並在 onDraw() 方法中,調用上面這個方法繪製圓環
private Canvas mCanvas;
@Override
protected void onDraw(Canvas canvas) {
mCanvas = canvas;
drawOutSideArc();
}
複製代碼
包正繪圖是圓形的前提是:
private float mRadius;
/* 加一個默認的padding值,爲了防止用camera旋轉時鐘時形成四周超出view大小 */
private float mDefaultPadding;
private float mPaddingLeft;
private float mPaddingTop;
private float mPaddingRight;
private float mPaddingBottom;// 以上4值 均在 onSizechanged()中測量
@Override
protected void onSizeChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
mRadius = Math.min(l - getPaddingLeft() - getPaddingRight(),
t - getPaddingTop() - getPaddingBottom()) / 2;// 各個指針長度
mDefaultPadding = 0.12f * mRadius;
mPaddingLeft = mDefaultPadding + l / 2 - mRadius + getPaddingLeft();// 鍾離左邊界距離
mPaddingRight = mDefaultPadding + l / 2 - mRadius + getPaddingRight();// 鍾離右邊界距離
mPaddingTop = mDefaultPadding + t / 2 - mRadius + getPaddingTop();// 鍾離上邊界距離
mPaddingBottom = mDefaultPadding + t / 2 - mRadius + getPaddingBottom();// 鍾離下邊界距離
}
複製代碼
對於圓的半徑 mRadius ,咱們就取控件長和寬中,短的那個的一半爲它的值,除此以外還有一種狀況,若是控件設置了 padding 那麼,若是知識取長寬中短的,那麼不管 padding 的值怎麼設置,控件的半徑始終都是保持長寬中短的那邊的一半不變,這樣取值使得 padding 失去了做用,也就顯得不那麼人性化了,因此真正的半徑應該是長寬中短的那邊,再減去兩個 padding 的值,以下:
mRadius = Math.min(w - getPaddingLeft() - getPaddingRight(), h - getPaddingTop() - getPaddingBottom()) / 2;
那麼這個 mDefaultPadding 又是什麼做用呢?不如咱們將其山區看看效果:
試想一下若是咱們,沒有這個默認值,那麼用戶在沒有設置 padding 時,畫出的圓弧必然和 View 的邊界相切,圓弧相切到嗨沒啥,關鍵是圓弧上顯示時間的文字也得給截去了一半,但有了這個 mDefaultPadding 就不要懼怕這個問題。開始繪製先前,咱們先要準備下一些工具,
/* 刻度線長度 */
private float mScaleLength;
/* 刻度線畫筆 */
private Paint mScaleLinePaint;
/* 背景色 */
private int mBackgroundColor;
public ClockView(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"));
mDarkColor = ta.getColor(R.styleable.ClockView_clock_darkColor, Color.parseColor("#80ffffff"));
mTextSize = ta.getDimension(R.styleable.ClockView_clock_textSize, DensityUtils.sp2px(context, 14));
ta.recycle();
.
.
.
mScaleLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mScaleLinePaint.setStyle(Paint.Style.STROKE);
mScaleLinePaint.setColor(mBackgroundColor);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
.
.
.
mScaleLength = 0.12f * mRadius;// 根據比例肯定刻度線長度
mScaleLinePaint.setStrokeWidth(0.012f * mRadius);// 刻度圈的寬度
}
複製代碼
繪製國晨反而很簡單,對於咱們來講 一小時 60min 一分鐘 60s,最好的狀況莫過於分爲 360 份,可是這樣一來,因爲手機屏幕比較小會直接致使先太密集,密集到了變成圓地步:
因此這裏,咱們將 360 度,劃分爲 200份 ,
/** * 畫一圈梯度渲染的亮暗色漸變圓弧,重繪時不斷旋轉,上面蓋一圈背景色的刻度線 */
private void drawScaleLine() {
mCanvas.save();
// 畫背景色刻度線
for (int i = 0; i < 100; 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();
}
複製代碼
項目 Demo 地址: github.com/FishInWater…
若是有錯歡迎在評論區指出,很是感謝~
祝你們編程愉快!