溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Android自定義View實(shí)現(xiàn)漸變色儀表盤

發(fā)布時(shí)間:2020-10-10 02:02:31 來源:腳本之家 閱讀:343 作者:ruancw 欄目:移動(dòng)開發(fā)

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

知識(shí)梳理

1.圓弧漸變色(SweepGradient)

2.圓弧上刻度繪制

3.指針指示當(dāng)前數(shù)據(jù)位置(Bitmap)

4.數(shù)據(jù)文本跟隨弧度顯示(drawTextOnPath)

效果圖:

Android自定義View實(shí)現(xiàn)漸變色儀表盤

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è)置默認(rèn)寬高值
 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)進(jì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類實(shí)現(xiàn)的,SweepGradient繼承自Shader;

B、注意漸變色的開始角度問題,如果跟圓弧起始角度不一致,記得使用矩陣轉(zhuǎn)換進(jìn)行旋轉(zhuǎn),再讓paint去設(shè)置shader;

C、SweepGradient的第3個(gè)參數(shù)int[] colors必須包含兩個(gè)及以上顏色值,不然會(huì)報(bào)錯(cuò);

D、SweepGradient的第四個(gè)參數(shù)的數(shù)組大小必須和第三個(gè)參數(shù)的數(shù)組大小一樣,也可以填入null。

(2)重寫onMeasure,用于測(cè)量view寬高

onMeasure方法:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
 setMeasuredDimension(remeasure(widthMeasureSpec, defaultSize),
   remeasure(heightMeasureSpec, defaultSize));
}

remeasure方法:

/**
 * 根據(jù)傳入的值進(jìn)行重新測(cè)量
 */
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í)設(shè)置默認(rèn)值
   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方法中獲取當(dāng)前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));

 //中間進(jìn)度圓環(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);
 //繪制當(dāng)前bitmap指針指示進(jìn)度
 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次小刻度就會(huì)繪制一次大刻度,即大刻度每次旋轉(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++) {
  //計(jì)算起始角度
  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個(gè)參數(shù)hOffset為文字水平方向的偏移量,第4個(gè)參數(shù)vOffset為文字垂直方向的偏移量;

B、重點(diǎn)是畫布開始時(shí)的旋轉(zhuǎn)角度及不同文字的起始角度

(4)繪制圓弧中心的數(shù)據(jù)及描述信息

/**
 * 繪制圓弧中間的文本內(nèi)容
 */
private void drawCenterText(Canvas canvas) {

 //繪制當(dāng)前數(shù)據(jù)值
 mCenterTextPaint.setColor(GREEN_COLOR);
 mCenterTextPaint.setTextSize(dp2px(25));
 mCenterTextPaint.setStyle(Paint.Style.STROKE);
 canvas.drawText(String.valueOf(mAnimatorValue), radius, radius, mCenterTextPaint);

 //繪制當(dāng)前數(shù)據(jù)描述
 mCenterTextPaint.setTextSize(dp2px(20));
 canvas.drawText(mCurrentDes, radius, radius + dp2px(25), mCenterTextPaint);

}

(5)繪制當(dāng)前數(shù)值對(duì)應(yīng)的圓弧及指針圖片指示

/**
 * 繪制當(dāng)前進(jìn)度和指示圖片
 */
private void drawBitmapProgress(Canvas canvas) {
 //如果當(dāng)前角度為0,則不繪制指示圖片
 if (mCurrentAngle==0f){
  return;
 }
 canvas.save();
 canvas.translate(radius, radius);
 canvas.rotate(270);
 //繪制對(duì)應(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.添加動(dòng)畫及數(shù)據(jù)

(1)動(dòng)畫效果

/**
 * 當(dāng)前數(shù)據(jù)對(duì)應(yīng)弧度旋轉(zhuǎn)及當(dāng)前數(shù)據(jù)自增動(dòng)畫
 */
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ǔ)目標(biāo)";
 } else if (values>14000&&values <= 17000) {
  mCurrentValue = values;
  mCurrentDes = "測(cè)試目標(biāo)";
  mTotalAngle = values / 17000f * 120-2;
 } else if (values>17000&&values <= 21000) {
  mCurrentValue = values;
  mTotalAngle = values / 21000f * 180-2;
  mCurrentDes = "保底目標(biāo)";
 } else {
  mCurrentValue=values;
  float ratio=values / 21000f;
  if (ratio<20){
   mTotalAngle = ratio+180;
  }else {
   mTotalAngle = (float) (ratio*0.2+200);
  }
  mCurrentDes = "沖刺目標(biāo)";
 }

 startRotateAnim();
}

總結(jié):自定義View實(shí)現(xiàn)儀表盤效果用到了canvas的旋轉(zhuǎn)及矩陣平移;drawTextOnpath使的文字跟隨path繪制;SweepGradient實(shí)現(xiàn)圓弧的漸變色效果。

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持億速云。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI