Android自定義View實現(xiàn)漸變色儀表盤
前言:最近一直在學(xué)自定義View的相關(guān)知識,感覺這在Android中還是挺難的一塊,當然這也是每個程序員必經(jīng)之路,正好公司項目要求實現(xiàn)類似儀表盤的效果用于直觀的顯示公司數(shù)據(jù),于是就簡單的寫了個demo,記錄實現(xiàn)的過程。上篇《Android自定義View實現(xiàn)圓弧進度效果》簡單記錄了圓弧及文字的繪制,漸變色的儀表盤效果將更加升入的介紹canvas及paint的使用(如畫布旋轉(zhuǎn),paint的漸變色設(shè)置等)。
知識梳理
1.圓弧漸變色(SweepGradient)
2.圓弧上刻度繪制
3.指針指示當前數(shù)據(jù)位置(Bitmap)
4.數(shù)據(jù)文本跟隨弧度顯示(drawTextOnPath)
效果圖:

1.繼承自View
(1)重寫構(gòu)造方法,初始化Paint
public DashBoardView(Context context) {
this(context, null);
}
public DashBoardView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public DashBoardView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
初始化相關(guān)Paint
/**
* 初始化Paint
*/
private void init() {
//設(shè)置默認寬高值
defaultSize = dp2px(260);
//設(shè)置圖片線條的抗鋸齒
mPaintFlagsDrawFilter = new PaintFlagsDrawFilter
(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
//最外層圓環(huán)漸變畫筆設(shè)置
mOuterGradientPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
//設(shè)置圓環(huán)漸變色渲染
mOuterGradientPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_ATOP));
float position[] = {0.1f, 0.3f, 0.8f};
Shader mShader = new SweepGradient(width / 2, radius, mColors, position);
mOuterGradientPaint.setShader(mShader);
mOuterGradientPaint.setStrokeCap(Paint.Cap.ROUND);
mOuterGradientPaint.setStyle(Paint.Style.STROKE);
mOuterGradientPaint.setStrokeWidth(30);
//最外層圓環(huán)刻度畫筆設(shè)置
mCalibrationPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mCalibrationPaint.setColor(Color.WHITE);
mCalibrationPaint.setStyle(Paint.Style.STROKE);
//中間圓環(huán)畫筆設(shè)置
mMiddlePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mMiddlePaint.setStyle(Paint.Style.STROKE);
mMiddlePaint.setStrokeCap(Paint.Cap.ROUND);
mMiddlePaint.setStrokeWidth(5);
mMiddlePaint.setColor(GRAY_COLOR);
//內(nèi)層圓環(huán)畫筆設(shè)置
mInnerPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mInnerPaint.setStyle(Paint.Style.STROKE);
mInnerPaint.setStrokeCap(Paint.Cap.ROUND);
mInnerPaint.setStrokeWidth(4);
mInnerPaint.setColor(GRAY_COLOR);
PathEffect mPathEffect = new DashPathEffect(new float[]{5, 5, 5, 5}, 1);
mInnerPaint.setPathEffect(mPathEffect);
//外層圓環(huán)文本畫筆設(shè)置
mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mTextPaint.setColor(GRAY_COLOR);
mTextPaint.setTextSize(dp2px(12));
//中間文字畫筆設(shè)置
mCenterTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mCenterTextPaint.setTextAlign(Paint.Align.CENTER);
//中間圓環(huán)進度畫筆設(shè)置
mMiddleProgressPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mMiddleProgressPaint.setColor(GREEN_COLOR);
mMiddleProgressPaint.setStrokeCap(Paint.Cap.ROUND);
mMiddleProgressPaint.setStrokeWidth(5);
mMiddleProgressPaint.setStyle(Paint.Style.STROKE);
//指針圖片畫筆
mPointerBitmapPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPointerBitmapPaint.setColor(GREEN_COLOR);
//獲取指針圖片及寬高
mBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.pointer);
mBitmapHeight = mBitmap.getHeight();
mBitmapWidth = mBitmap.getWidth();
}
注:
A、最外層圓弧的漸變色使用的是SweepGradient類實現(xiàn)的,SweepGradient繼承自Shader;
B、注意漸變色的開始角度問題,如果跟圓弧起始角度不一致,記得使用矩陣轉(zhuǎn)換進行旋轉(zhuǎn),再讓paint去設(shè)置shader;
C、SweepGradient的第3個參數(shù)int[] colors必須包含兩個及以上顏色值,不然會報錯;
D、SweepGradient的第四個參數(shù)的數(shù)組大小必須和第三個參數(shù)的數(shù)組大小一樣,也可以填入null。
(2)重寫onMeasure,用于測量view寬高
onMeasure方法:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(remeasure(widthMeasureSpec, defaultSize),
remeasure(heightMeasureSpec, defaultSize));
}
remeasure方法:
/**
* 根據(jù)傳入的值進行重新測量
*/
public int remeasure(int measureSpec, int defaultSize) {
int result;
int specSize = MeasureSpec.getSize(measureSpec);
switch (MeasureSpec.getMode(measureSpec)) {
case MeasureSpec.UNSPECIFIED:
//未指定
result = defaultSize;
break;
case MeasureSpec.AT_MOST:
//設(shè)置warp_content時設(shè)置默認值
result = Math.min(specSize, defaultSize);
break;
case MeasureSpec.EXACTLY:
//設(shè)置math_parent 和設(shè)置了固定寬高值
result=specSize;
break;
default:
result = defaultSize;
}
return result;
}
(3)重寫onChange,用于獲取view寬高
在onChange方法中獲取當前View的寬高及獲取圓弧的半徑,初始化圓弧的RectF等
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
//確定View寬高
width = w;
height = h;
//圓環(huán)半徑
radius = width / 2;
//外層圓環(huán)
float oval1 = radius - mOuterGradientPaint.getStrokeWidth() * 0.5f;
mOuterRectF = new RectF(-oval1, -oval1, oval1, oval1);
//中間和內(nèi)層圓環(huán)
float oval2 = radius * 5 / 8;
float oval3 = radius * 3 / 4;
mInnerRectF = new RectF(-oval2 + dp2px(5), -oval2 + dp2px(5), oval2 - dp2px(5), oval2 - dp2px(5));
mMiddleRectF = new RectF(-oval3 + dp2px(10), -oval3 + dp2px(10), oval3 - dp2px(10), oval3 - dp2px(10));
//中間進度圓環(huán)
oval4 = radius * 6 / 8;
mMiddleProgressRectF = new RectF(-oval4+ dp2px(10), -oval4+ dp2px(10), oval4- dp2px(10), oval4- dp2px(10));
}
(4)重寫onDraw方法,用于繪制view
@SuppressLint("DrawAllocation")
@Override
protected void onDraw(Canvas canvas) {
//設(shè)置畫布繪圖無鋸齒
canvas.setDrawFilter(mPaintFlagsDrawFilter);
//繪制圓弧
drawArc(canvas);
//繪制圓弧上的刻度
drawCalibration(canvas);
//繪制跟隨圓弧path的文字
drawArcText(canvas);
//繪制圓弧中心文字
drawCenterText(canvas);
//繪制當前bitmap指針指示進度
drawBitmapProgress(canvas);
}
2.Canvas繪制view
mStartAngle=105f,mEndAngle=250f
(1)繪制圓弧
/**
* 分別繪制外層 中間 內(nèi)層圓環(huán)
*/
private void drawArc(Canvas canvas) {
canvas.save();
canvas.translate(width / 2, height / 2);
//畫布旋轉(zhuǎn)140°
canvas.rotate(140);
//最外層的漸變圓環(huán)
canvas.drawArc(mOuterRectF, -mStartAngle, -mEndAngle, false, mOuterGradientPaint);
//繪制內(nèi)層虛線圓弧
canvas.drawArc(mInnerRectF, -mStartAngle, -mEndAngle, false, mInnerPaint);
//繪制中間圓弧
canvas.drawArc(mMiddleRectF, -mStartAngle, -mEndAngle, false, mMiddlePaint);
canvas.restore();
}
(2)繪制漸變色圓弧上的大小刻度
/**
* 繪制外層漸變色圓弧上的大小刻度線
*/
private void drawCalibration(Canvas canvas) {
int dst = (int) (2 * radius - mOuterGradientPaint.getStrokeWidth());
for (int i = 0; i <= 40; i++) {
canvas.save();
canvas.rotate(-(-30 + 6 * i), radius, radius);
if (i % 10 == 0) {
mCalibrationPaint.setStrokeWidth(4);
//繪制大刻度
canvas.drawLine(dst, radius, 2 * radius, radius, mCalibrationPaint);
} else {
//小刻度
mCalibrationPaint.setStrokeWidth(1);
canvas.drawLine(dst, radius, 2 * radius, radius, mCalibrationPaint);
}
canvas.restore();
}
}
注:
A、圓弧的總弧度為240f,循環(huán)40次
B、小刻度每次旋轉(zhuǎn)6弧度,每繪制10次小刻度就會繪制一次大刻度,即大刻度每次旋轉(zhuǎn)60弧度
(3)繪制跟隨圓弧弧度描述文字
/**
* 繪制跟隨圓弧弧度的文本
*/
private void drawArcText(Canvas canvas) {
canvas.save();
//每次旋轉(zhuǎn)角度
int rotateAngle = 30;
//旋轉(zhuǎn)畫布
canvas.rotate(-118, radius - dp2px(26), radius-dp2px(103));
for (int i = 0; i < valueList.size(); i++) {
//計算起始角度
int startAngle = 30 * i - 108;
//設(shè)置數(shù)據(jù)跟著圓弧繪制
Path paths = new Path();
paths.addArc(mInnerRectF, startAngle, rotateAngle);
float textLen = mTextPaint.measureText(valueList.get(i));
canvas.drawTextOnPath(valueList.get(i), paths, -textLen / 2 + dp2px(20), -dp2px(22), mTextPaint);
//canvas.drawText(text[i], radius - 10, radius * 3 / 16+dp2px(10), mTextPaint);
}
canvas.restore();
}
注:
A、drawTextOnPath為文字隨path路徑顯示,drawTextOnPath的第3個參數(shù)hOffset為文字水平方向的偏移量,第4個參數(shù)vOffset為文字垂直方向的偏移量;
B、重點是畫布開始時的旋轉(zhuǎn)角度及不同文字的起始角度
(4)繪制圓弧中心的數(shù)據(jù)及描述信息
/**
* 繪制圓弧中間的文本內(nèi)容
*/
private void drawCenterText(Canvas canvas) {
//繪制當前數(shù)據(jù)值
mCenterTextPaint.setColor(GREEN_COLOR);
mCenterTextPaint.setTextSize(dp2px(25));
mCenterTextPaint.setStyle(Paint.Style.STROKE);
canvas.drawText(String.valueOf(mAnimatorValue), radius, radius, mCenterTextPaint);
//繪制當前數(shù)據(jù)描述
mCenterTextPaint.setTextSize(dp2px(20));
canvas.drawText(mCurrentDes, radius, radius + dp2px(25), mCenterTextPaint);
}
(5)繪制當前數(shù)值對應(yīng)的圓弧及指針圖片指示
/**
* 繪制當前進度和指示圖片
*/
private void drawBitmapProgress(Canvas canvas) {
//如果當前角度為0,則不繪制指示圖片
if (mCurrentAngle==0f){
return;
}
canvas.save();
canvas.translate(radius, radius);
canvas.rotate(270);
//繪制對應(yīng)的圓弧
canvas.drawArc(mMiddleProgressRectF, -mStartAngle-20, mCurrentAngle+5, false, mMiddleProgressPaint);
canvas.rotate(60 + mCurrentAngle);
//利用矩陣平移使圖片指針方向始終指向刻度
Matrix matrix = new Matrix();
matrix.preTranslate(-oval4 - mBitmapWidth * 3 / 8 + 10, -mBitmapHeight / 2);
canvas.drawBitmap(mBitmap, matrix, mPointerBitmapPaint);
canvas.restore();
}
注:為了使指針圖片的指針一直指向刻度盤上的刻度,這里使用了矩陣的平移。
3.添加動畫及數(shù)據(jù)
(1)動畫效果
/**
* 當前數(shù)據(jù)對應(yīng)弧度旋轉(zhuǎn)及當前數(shù)據(jù)自增動畫
*/
public void startRotateAnim() {
ValueAnimator mAngleAnim = ValueAnimator.ofFloat(mCurrentAngle, mTotalAngle);
mAngleAnim.setInterpolator(new AccelerateDecelerateInterpolator());
mAngleAnim.setDuration(2500);
mAngleAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
mCurrentAngle = (float) valueAnimator.getAnimatedValue();
postInvalidate();
}
});
mAngleAnim.start();
ValueAnimator mNumAnim = ValueAnimator.ofInt(mAnimatorValue, mCurrentValue);
mNumAnim.setDuration(2500);
mNumAnim.setInterpolator(new LinearInterpolator());
mNumAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
mAnimatorValue = (int) valueAnimator.getAnimatedValue();
postInvalidate();
}
});
mNumAnim.start();
}
(2)設(shè)置數(shù)據(jù)及描述信息
/**
* 設(shè)置數(shù)據(jù)
*/
public void setValues(int values, List<String> valueList) {
this.valueList=valueList;
if (values <= 0) {
mCurrentValue = values;
mTotalAngle = 0f;
mCurrentDes = "";
} else if (values <= 14000) {
mCurrentValue = values;
mTotalAngle = values / 14000f * 60-2;
Log.e("rcw","mTotalAngle="+mTotalAngle);
mCurrentDes = "基礎(chǔ)目標";
} else if (values>14000&&values <= 17000) {
mCurrentValue = values;
mCurrentDes = "測試目標";
mTotalAngle = values / 17000f * 120-2;
} else if (values>17000&&values <= 21000) {
mCurrentValue = values;
mTotalAngle = values / 21000f * 180-2;
mCurrentDes = "保底目標";
} else {
mCurrentValue=values;
float ratio=values / 21000f;
if (ratio<20){
mTotalAngle = ratio+180;
}else {
mTotalAngle = (float) (ratio*0.2+200);
}
mCurrentDes = "沖刺目標";
}
startRotateAnim();
}
總結(jié):自定義View實現(xiàn)儀表盤效果用到了canvas的旋轉(zhuǎn)及矩陣平移;drawTextOnpath使的文字跟隨path繪制;SweepGradient實現(xiàn)圓弧的漸變色效果。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Android中Fragment的加載方式與數(shù)據(jù)通信詳解
本文主要介紹了Android中Fragment的加載方式與數(shù)據(jù)通信的相關(guān)知識。具有很好的參考價值。下面跟著小編一起來看下吧2017-03-03
Android App中讀取XML與JSON格式數(shù)據(jù)的基本方法示例
這篇文章主要介紹了Android App中讀取XML與JSON格式數(shù)據(jù)的基本方法示例,Android中自帶的JSONObject非常好用,需要的朋友可以參考下2016-03-03
詳解Android開啟OTG功能/USB?Host?API功能
這篇文章主要介紹了Android開啟OTG功能/USB?Host?API功能,本文給大家介紹的非常詳細,對大家的學(xué)習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2022-07-07
Android TextWatcher三個回調(diào)以及監(jiān)聽EditText的輸入案例詳解
這篇文章主要介紹了Android TextWatcher三個回調(diào)以及監(jiān)聽EditText的輸入案例詳解,本篇文章通過簡要的案例,講解了該項技術(shù)的了解與使用,以下就是詳細內(nèi)容,需要的朋友可以參考下2021-08-08

