自定義View按類型來劃分的話,自定義View的實現方式大概可以分爲三種:
- 組合控件
- 繼承控件
- 自繪控件
上一篇我們講過了組合控件,接下來我們來講講自繪控件。
每一個視圖的繪製過程都必須經歷三個最主要的階段,即onMeasure()、onLayout()和onDraw()。
onMeasure():測量的意思,顧名思義就是用於測量視圖的大小的。**measure()**方法接收兩個參數widthMeasureSpec和heightMeasureSpec。
widthMeasureSpec:用於確定視圖的寬度的規格和大小。
heightMeasureSpec:用於確定視圖的寬度和高度的規格和大小。
MeasureSpec的值由specSize和specMode共同組成的,其中specSize記錄的是大小,specMode記錄的是規格。
specMode一共有三種類型 EXACTLY、AT_MOST、UNSPECIFIED
EXACTLY:即子視圖的大小是由specSize的值來決定的,系統默認會按照這個規則來設置子視圖的大小。
AT_MOST:即子視圖最大隻能是specSize中指定的大小,並且不會超過specSize。系統默認會按照這個規則來設置子視圖的大小。
UNSPECIFIED:即開發人員可以將視圖按照自己的意願設置成任意的大小,沒有任何限制。
onLayout():這個方法是用於給視圖進行佈局的,也就是確定視圖的位置。
layout()方法接收四個參數,分別代表着左、上、右、下的座標,當然這個座標是相對於當前視圖的父視圖而言的。
public void layout(int l, int t, int r, int b) {}
onDraw():measure和layout的之後,就進入到draw的過程。
下面我們就來自定義一個自己的View
public class CircularProgressBar extends View {
/**
* 動畫時長
*/
private int mDuration;
/**
* 接口
*/
private OnFinishListener mListener;
/**
* 動畫
*/
private ValueAnimator mAnimator;
/**
* 逆向進度條最大值
*/
private int mCurrentProgress;
/**
* 進度條最大值
*/
private int maxValue = 100;
/**
* 當前進度值
*/
private int mCurrentValue;
/**
* 每次掃過的角度,用來設置進度條圓弧所對應的圓心角,alphaAngle=(currentValue/maxValue)*360
*/
private float mAlphaAngle;
/**
* 底部圓弧的顏色,默認 ResUtil.getColor(R.color.colorAccent)
*/
private int mDefaultColor;
/**
* 進度條圓弧塊的顏色, 默認 ResUtil.getColor(R.color.colorWhith)
*/
private int mRunColor;
/**
* 中間文字顏色(默認藍色)
*/
private int cTextColor = Color.BLUE;
/**
* 中間文字的字體大小(默認30dp)
*/
private int cTextSize;
/**
* 圓環的寬度
*/
private int mCircleWidth;
/**
* 畫圓弧的畫筆
*/
private Paint mCirclePaint;
/**
* 畫文字的畫筆
*/
private Paint mTextPaint;
/**
* 進度條方向
*/
private boolean mDirectionof;
public void setListener(OnFinishListener mListener) {
this.mListener = mListener;
}
public CircularProgressBar(Context context) {
this(context, null);
}
public CircularProgressBar(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public CircularProgressBar(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs, defStyleAttr);
}
//初始化
@SuppressLint("ResourceAsColor")
private void init(Context context, AttributeSet attrs, int defStyleAttr) {
TypedArray ta = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CirculaProgressBar,
defStyleAttr, 0);
// 默認底色白色透明度百分之20
mDefaultColor = ta.getColor(R.styleable.CirculaProgressBar_circular_defaultColor, R.color.colorGray);
// 默認進度條顏色爲白色
mRunColor = ta.getColor(R.styleable.CirculaProgressBar_circular_runColor, R.color.colorWhith);
// 默認中間文字顏色爲藍色
cTextColor = ta.getColor(R.styleable.CirculaProgressBar_circular_cTextColor, Color.BLUE);
// 默認中間文字字體大小爲30dp
cTextSize = ta.getDimensionPixelSize(R.styleable.CirculaProgressBar_circular_cTextSize, DpDonversionPxUtils.px2dip(30));
// 默認圓弧寬度爲6dp
mCircleWidth = ta.getDimensionPixelSize(R.styleable.CirculaProgressBar_circular_circleWidth, DpDonversionPxUtils.px2dip(6));
// 默認順時針方向
mDirectionof = ta.getBoolean(R.styleable.CirculaProgressBar_circular_directionof, false);
ta.recycle();
//畫圓畫筆
mCirclePaint = new Paint();
// 抗鋸齒
mCirclePaint.setAntiAlias(true);
// 防抖動
mCirclePaint.setDither(true);
//畫筆寬度
mCirclePaint.setStrokeWidth(mCircleWidth);
//畫文字畫筆
mTextPaint = new Paint();
// 抗鋸齒
mTextPaint.setAntiAlias(true);
// 防抖動
mTextPaint.setDither(true);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
//獲取屏幕寬
int widthPixels = this.getResources().getDisplayMetrics().widthPixels;
//獲取屏幕高
int heightPixels = this.getResources().getDisplayMetrics().heightPixels;
int rwidth = MeasureSpec.getSize(widthMeasureSpec);
int rhedight = MeasureSpec.getSize(heightMeasureSpec);
int minWidth = Math.min(widthPixels, rwidth);
int minHedight = Math.min(heightPixels, rhedight);
setMeasuredDimension(Math.min(minWidth, minHedight), Math.min(minWidth, minHedight));
}
@Override
protected void onDraw(Canvas canvas) {
int center = this.getWidth() / 2;
int radius = center - mCircleWidth / 2;
// 繪製進度圓弧
drawCircle(canvas, center, radius);
drawText(canvas, center);
}
/**
* 繪製進度圓弧
*
* @param canvas 畫布對象
* @param center 圓心的x和y座標
* @param radius 圓的半徑
*/
private void drawCircle(Canvas canvas, int center, int radius) {
if (mCirclePaint == null) {
return;
}
// 清除上一次的shader
mCirclePaint.setShader(null);
// 設置底部圓環的顏色,這裏使用第一種顏色
mCirclePaint.setColor(mDefaultColor);
// 設置繪製的圓爲空心
mCirclePaint.setStyle(Paint.Style.STROKE);
// 畫底部的空心圓
canvas.drawCircle(center, center, radius, mCirclePaint);
// 圓的外接正方形
RectF oval = new RectF(center - radius, center - radius, center + radius, center + radius);
mCirclePaint.setShadowLayer(10, 10, 10, Color.BLUE);
// 設置圓弧的顏色
mCirclePaint.setColor(mRunColor);
// 把每段圓弧改成圓角的
mCirclePaint.setStrokeCap(Paint.Cap.ROUND);
// 計算每次畫圓弧時掃過的角度,這裏計算要注意分母要轉爲float類型,否則alphaAngle永遠爲0
if (mDirectionof) {
canvas.drawArc(oval, -90, mCurrentProgress - 360, false, mCirclePaint);
} else {
mAlphaAngle = mCurrentValue * 360.0f / maxValue * 1.0f;
canvas.drawArc(oval, -90, mAlphaAngle, false, mCirclePaint);
}
}
/**
* 繪製文字
*
* @param canvas 畫布對象
* @param center 圓心的x和y座標
*/
private void drawText(Canvas canvas, int center) {
// 計算進度
int result = ((maxValue - mCurrentValue) * (mDuration / 1000) / maxValue);
String percent;
if (maxValue == mCurrentValue) {
percent = "完成";
// 設置要繪製的文字大小
mTextPaint.setTextSize(cTextSize);
} else {
percent = result + "";
// 設置要繪製的文字大小
mTextPaint.setTextSize(cTextSize + cTextSize / 3);
}
// 設置文字居中,文字的x座標要注意
mTextPaint.setTextAlign(Paint.Align.CENTER);
// 設置文字顏色
mTextPaint.setColor(cTextColor);
// 注意此處一定要重新設置寬度爲0,否則繪製的文字會重疊
mTextPaint.setStrokeWidth(0);
// 文字邊框
Rect bounds = new Rect();
// 獲得繪製文字的邊界矩形
mTextPaint.getTextBounds(percent, 0, percent.length(), bounds);
// 獲取繪製Text時的四條線
Paint.FontMetricsInt fontMetrics = mTextPaint.getFontMetricsInt();
// 計算文字的基線,方法見http://blog.csdn.net/harvic880925/article/details/50423762
int baseline = center + (fontMetrics.bottom - fontMetrics.top) / 2 - fontMetrics.bottom;
// 繪製表示進度的文字
canvas.drawText(percent, center, baseline, mTextPaint);
}
/**
* 設置圓環的寬度
*
* @param width
*/
public void setCircleWidth(int width) {
this.mCircleWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, width, getResources()
.getDisplayMetrics());
mCirclePaint.setStrokeWidth(mCircleWidth);
//一般只是希望在View發生改變時對UI進行重繪。invalidate()方法系統會自動調用 View的onDraw()方法。
invalidate();
}
/**
* 設置圓環的底色,默認爲亮灰色LTGRAY
*
* @param color
*/
public void setDefaultColor(int color) {
this.mDefaultColor = color;
mCirclePaint.setColor(mDefaultColor);
//一般只是希望在View發生改變時對UI進行重繪。invalidate()方法系統會自動調用 View的onDraw()方法。
invalidate();
}
/**
* 設置進度條的顏色,默認爲白色<br>
*
* @param color
*/
public void setRunColor(int color) {
this.mRunColor = color;
mCirclePaint.setColor(mRunColor);
//一般只是希望在View發生改變時對UI進行重繪。invalidate()方法系統會自動調用 View的onDraw()方法。
invalidate();
}
/**
* 設置進度條運行方向
*
* @param directionof
*/
public void setRunDirectionof(boolean directionof) {
this.mDirectionof = directionof;
//一般只是希望在View發生改變時對UI進行重繪。invalidate()方法系統會自動調用 View的onDraw()方法。
invalidate();
}
/**
* 按進度顯示百分比,可選擇是否啓用數字動畫
*
* @param duration 動畫時長
*/
public void setDuration(int duration, OnFinishListener listener) {
this.mListener = listener;
this.mDuration = duration + 1000;
if (mAnimator != null) {
mAnimator.cancel();
} else {
mAnimator = ValueAnimator.ofInt(0, maxValue);
mAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mCurrentValue = (int) animation.getAnimatedValue();
Log.e(" onAnimationUpdate ", " onAnimationUpdate = " + mCurrentValue);
mCurrentProgress = (int) (360 * (mCurrentValue / 100f));
Log.e(" onAnimationUpdate ", " onAnimationUpdate mCurrentProgress = " + mCurrentProgress);
//一般只是希望在View發生改變時對UI進行重繪。invalidate()方法系統會自動調用 View的onDraw()方法。
invalidate();
if (maxValue == mCurrentValue && CircularProgressBar.this.mListener != null) {
CircularProgressBar.this.mListener.onFinish();
}
}
});
mAnimator.setInterpolator(new LinearInterpolator());
}
mAnimator.setDuration(duration);
mAnimator.start();
}
/**
* 停止動畫
*/
public void stopAnimator() {
if (mAnimator != null) {
mAnimator.cancel();
}
mCurrentValue = 0;
mCurrentProgress = 0;
invalidate();
}
}
效果圖