Android進階路之自定義view實操第一篇

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章