關於本文:本文原先在個人 CSDN 博客發佈(由圖片水印能發現),整理以往博客過程當中,發現當時總結的很仔細,因此將其遷移到這裏,但願對你們在自定義 View 方面,能有所幫助 💗html
Android 自定義 View 應用很是普遍,最近逛 github 是偶然發現一個 Demo 感受寫的很好,我結合着這個項目的內容,給你們講講如何繪製時鐘錶盤,也算是加深下本身對自定義 View 的理解,涉及內容比較多,你們慢慢吸取。java
開始以前,先讓你們看看最後的效果android
讓咱們先搭建這個 Viewc++
<?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>
小時圓環組成分爲外圍的圓弧和四個小時數字,因此咱們須要的東西很明確了。git
重寫構造方法:github
/* 暗色,圓弧、刻度線、時針、漸變起始色 */ 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 類型進行分類處理罷了。編程
@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 。canvas
因此繪製外圍圓環,首先要定義一個 RectF 變量用於繪製圓環,在定義一個 Rect 變量,用於繪製文字。ide
注 mCanvas 繪圖類是 onDraw 中的參數,咱們在 onDraw 中將它保存起來工具
// 測量文字大小 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(); }
咱們看到 圓環和時間是出來了,可是這麼是個橢圓呢,在仔細檢查下咱們的代碼,在繪製過程當中,控制咱們圓環的 mCircleRectF 對象,是以整個控件大小爲邊界的,因此緣由就很明瞭了,那麼咱們只要將 mCircleRectF 對象設置成一個正方形就行。
------------------------
包正繪圖是圓形的前提是:
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 地址:
https://github.com/FishInWater-1999/android_view_user_defined_first.git
若是有錯歡迎在評論區指出,很是感謝~
祝你們編程愉快!