Android自定义View实现渐变色仪表盘

前言:最近一直在学自定义View的相关知识,感觉这在Android中还是挺难的一块,当然这也是每个程序员必经之路,正好公司项目要求实现类似仪表盘的效果用于直观的显示公司数据,于是就简单的写了个demo,记录实现的过程。上篇《Android自定义View实现圆弧进度效果》简单记录了圆弧及文字的绘制,渐变色的仪表盘效果将更加升入的介绍canvas及paint的使用(如画布旋转,paint的渐变色设置等)。

知识梳理

1.圆弧渐变色(SweepGradient)

2.圆弧上刻度绘制

3.指针指示当前数据位置(Bitmap)

4.数据文本跟随弧度显示(drawTextOnPath)

效果图:

1.继承自View

(1)重写构造方法,初始化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();
}

初始化相关Paint

/**
 * 初始化Paint
 */
private void init() {
 //设置默认宽高值
 defaultSize = dp2px(260);

 //设置图片线条的抗锯齿
 mPaintFlagsDrawFilter = new PaintFlagsDrawFilter
   (0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);

 //最外层圆环渐变画笔设置
 mOuterGradientPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
 //设置圆环渐变色渲染
 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);

 //最外层圆环刻度画笔设置
 mCalibrationPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
 mCalibrationPaint.setColor(Color.WHITE);
 mCalibrationPaint.setStyle(Paint.Style.STROKE);

 //中间圆环画笔设置
 mMiddlePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
 mMiddlePaint.setStyle(Paint.Style.STROKE);
 mMiddlePaint.setStrokeCap(Paint.Cap.ROUND);
 mMiddlePaint.setStrokeWidth(5);
 mMiddlePaint.setColor(GRAY_COLOR);

 //内层圆环画笔设置
 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);

 //外层圆环文本画笔设置
 mTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
 mTextPaint.setColor(GRAY_COLOR);
 mTextPaint.setTextSize(dp2px(12));

 //中间文字画笔设置
 mCenterTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
 mCenterTextPaint.setTextAlign(Paint.Align.CENTER);

 //中间圆环进度画笔设置
 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类实现的,SweepGradient继承自Shader;

B、注意渐变色的开始角度问题,如果跟圆弧起始角度不一致,记得使用矩阵转换进行旋转,再让paint去设置shader;

C、SweepGradient的第3个参数int[] colors必须包含两个及以上颜色值,不然会报错;

D、SweepGradient的第四个参数的数组大小必须和第三个参数的数组大小一样,也可以填入null。

(2)重写onMeasure,用于测量view宽高

onMeasure方法:

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

remeasure方法:

/**
 * 根据传入的值进行重新测量
 */
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:
   //设置warp_content时设置默认值
   result = Math.min(specSize, defaultSize);
   break;
  case MeasureSpec.EXACTLY:
   //设置math_parent 和设置了固定宽高值
   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;

 //圆环半径
 radius = width / 2;

 //外层圆环
 float oval1 = radius - mOuterGradientPaint.getStrokeWidth() * 0.5f;
 mOuterRectF = new RectF(-oval1, -oval1, oval1, oval1);

 //中间和内层圆环
 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));

 //中间进度圆环
 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) {
 //设置画布绘图无锯齿
 canvas.setDrawFilter(mPaintFlagsDrawFilter);
 //绘制圆弧
 drawArc(canvas);
 //绘制圆弧上的刻度
 drawCalibration(canvas);
 //绘制跟随圆弧path的文字
 drawArcText(canvas);
 //绘制圆弧中心文字
 drawCenterText(canvas);
 //绘制当前bitmap指针指示进度
 drawBitmapProgress(canvas);
}

2.Canvas绘制view

mStartAngle=105f,mEndAngle=250f

(1)绘制圆弧

/**
 * 分别绘制外层 中间 内层圆环
 */
private void drawArc(Canvas canvas) {

 canvas.save();
 canvas.translate(width / 2, height / 2);
 //画布旋转140°
 canvas.rotate(140);
 //最外层的渐变圆环
 canvas.drawArc(mOuterRectF, -mStartAngle, -mEndAngle, false, mOuterGradientPaint);

 //绘制内层虚线圆弧
 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,循环40次

B、小刻度每次旋转6弧度,每绘制10次小刻度就会绘制一次大刻度,即大刻度每次旋转60弧度

(3)绘制跟随圆弧弧度描述文字

/**
 * 绘制跟随圆弧弧度的文本
 */
private void drawArcText(Canvas canvas) {
 canvas.save();
 //每次旋转角度
 int rotateAngle = 30;
 //旋转画布
 canvas.rotate(-118, radius - dp2px(26), radius-dp2px(103));
 for (int i = 0; i < valueList.size(); i++) {
  //计算起始角度
  int startAngle = 30 * i - 108;
  //设置数据跟着圆弧绘制
  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个参数hOffset为文字水平方向的偏移量,第4个参数vOffset为文字垂直方向的偏移量;

B、重点是画布开始时的旋转角度及不同文字的起始角度

(4)绘制圆弧中心的数据及描述信息

/**
 * 绘制圆弧中间的文本内容
 */
private void drawCenterText(Canvas canvas) {

 //绘制当前数据值
 mCenterTextPaint.setColor(GREEN_COLOR);
 mCenterTextPaint.setTextSize(dp2px(25));
 mCenterTextPaint.setStyle(Paint.Style.STROKE);
 canvas.drawText(String.valueOf(mAnimatorValue), radius, radius, mCenterTextPaint);

 //绘制当前数据描述
 mCenterTextPaint.setTextSize(dp2px(20));
 canvas.drawText(mCurrentDes, radius, radius + dp2px(25), mCenterTextPaint);

}

(5)绘制当前数值对应的圆弧及指针图片指示

/**
 * 绘制当前进度和指示图片
 */
private void drawBitmapProgress(Canvas canvas) {
 //如果当前角度为0,则不绘制指示图片
 if (mCurrentAngle==0f){
  return;
 }
 canvas.save();
 canvas.translate(radius, radius);
 canvas.rotate(270);
 //绘制对应的圆弧
 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.添加动画及数据

(1)动画效果

/**
 * 当前数据对应弧度旋转及当前数据自增动画
 */
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)设置数据及描述信息

/**
 * 设置数据
 */
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 = "基础目标";
 } 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();
}

总结:自定义View实现仪表盘效果用到了canvas的旋转及矩阵平移;drawTextOnpath使的文字跟随path绘制;SweepGradient实现圆弧的渐变色效果。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Android自定义View仿支付宝芝麻信用分仪表盘

    先看下iOS的芝麻信用分截图 这是我做的效果,还是有点差距的 支付宝9.9版本芝麻信用分的实现 首先初始化各种画笔,默认的size,padding,小圆点. (因为实在找不到原版芝麻信用的带点模糊效果的小圆点,所以只好用这个代替) //View的默认大小 defaultSize = dp2px(250); //默认Padding大小 arcDistance = dp2px(14); //外层圆环画笔 mMiddleArcPaint = new Paint(Paint.ANTI_ALIAS_FLA

  • Android自定义view之仿支付宝芝麻信用仪表盘示例

    自定义view练习 仿支付宝芝麻信用的仪表盘 对比图: 首先是自定义一些属性,可自己再添加,挺基础的,上代码 <?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="RoundIndicatorView"> <!--最大数值--> <attr name="maxNum" form

  • Android自定义View制作仪表盘界面

    前言 最近我跟自定义View杠上了,甚至说有点上瘾到走火入魔了.身为菜鸟的我自然要查阅大量的资料,学习大神们的代码,这不,前两天正好在郭神在微信公众号里推送一片自定义控件的文章--一步步实现精美的钟表界面.正适合我这种菜鸟来学习,闲着没事,我就差不多依葫芦画瓢也写了一个自定义表盘View,现在纯粹最为笔记记录下来.先展示下效果图: 下面进入正题 自定义表盘属性 老规矩,先在attrs文件里添加表盘自定义属性 <declare-styleable name="WatchView"&

  • Android自定义View实现渐变色仪表盘

    前言:最近一直在学自定义View的相关知识,感觉这在Android中还是挺难的一块,当然这也是每个程序员必经之路,正好公司项目要求实现类似仪表盘的效果用于直观的显示公司数据,于是就简单的写了个demo,记录实现的过程.上篇<Android自定义View实现圆弧进度效果>简单记录了圆弧及文字的绘制,渐变色的仪表盘效果将更加升入的介绍canvas及paint的使用(如画布旋转,paint的渐变色设置等). 知识梳理 1.圆弧渐变色(SweepGradient) 2.圆弧上刻度绘制 3.指针指示当前

  • Android自定义View之渐变色折线图的实现

    目录 前言 如何实现 总结 前言 在之前的项目中,有做过一个需求,需要实现一个颜色渐变的折线图.当时项目中使用的图表库是MPAndroidChart,但是该库没有提供合适的方法来实现想要的效果,因此只能通过自定义view来实现. 通过这篇文章记录一下,便于之后需要实现类似的效果时查找使用. 如何实现 通过创建LinearGradient来实现颜色渐变,并将之设置到画笔Paint的着色器Shader,绘制想要的路径即可实现该效果. 实现代码如下: class GradientLineChart :

  • Android自定义View实现渐变色进度条

    在网上看到一个进度条效果图,非常美观,如下: 进行效果分解: 1.渐变色,看起来颜色变化并不复杂,使用LinearGradient应该可以实现. 2.圆头,无非是画两个圆,外圆使用渐变色的颜色,内圆固定为白色. 3.灰底,还没有走到的进度部分为灰色. 4.进度值,使用文本来显示: 5.弧形的头部,考虑使用直线进行连接,或者使用曲线,例如贝塞尔曲线: 我首先初步实现了进度条的模样,发现样子有了,却不太美观. 反思了一下,我只是个写代码的,对于哪种比例比较美观,是没有清晰的认识的,所以,还是参考原图

  • Android自定义View 实现闹钟唤起播放闹钟铃声功能

    先上图看一下闹钟唤期页面的效果 实现的功能: 1:转动的图片根据天气情况更换 2:转动时间可以设置,转动结束,闹铃声音就结束 3:光圈颜色渐变效果 直接上代码啦: package com.yuekong.sirius.extension.customview; import android.animation.Animator; import android.animation.ValueAnimator; import android.content.Context; import andro

  • Android自定义View实现颜色选取器

    Android 自定义View 颜色选取器,可以实现水平.竖直选择颜色类似 SeekBar 的方式通过滑动选择颜色. 效果图 xml 属性 1.indicatorColor 指示点颜色 2.indicatorEnable 是否使用指示点 3.orientation 方向 horizontal 水平 vertical 竖直 使用 复制 \library\src-\ColorPickerView.java 和 \library\src\main\res\values\attrs.xml 文件到你的项

  • Android自定义View绘制彩色圆弧

    本文实例为大家分享了Android自定义View绘制彩色圆弧的具体代码,供大家参考,具体内容如下 效果如下: 自定义View代码如下: package com.example.yan; import android.content.Context; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; import android.graphics.RectF; i

  • Android自定义View实现圆形进度条

    本文实例为大家分享了Android自定义View实现圆形进度条的具体代码,供大家参考,具体内容如下 效果如下: 主要代码 CircularProgressView.java public class CircularProgressView extends View { private Paint mBackPaint, mProgPaint; // 绘制画笔 private RectF mRectF; // 绘制区域 private int[] mColorArray; // 圆环渐变色 pr

  • Android自定义view之围棋动画效果的实现

    前言 废话不多说直接开始 老规矩,文章最后有源码 完成效果图 棋子加渐变色 棋子不加渐变色 一.测量 1.获取宽高 @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); mWidth = w; mHeight = h; useWidth = mWidth; if (mWidth > mHeight) { useWidth =

  • Android自定义view实现阻尼效果的加载动画

    效果: 需要知识: 1. 二次贝塞尔曲线 2. 动画知识 3. 基础自定义view知识 先来解释下什么叫阻尼运动 阻尼振动是指,由于振动系统受到摩擦和介质阻力或其他能耗而使振幅随时间逐渐衰减的振动,又称减幅振动.衰减振动.[1] 不论是弹簧振子还是单摆由于外界的摩擦和介质阻力总是存在,在振动过程中要不断克服外界阻力做功,消耗能量,振幅就会逐渐减小,经过一段时间,振动就会完全停下来.这种振幅随时间减小的振动称为阻尼振动.因为振幅与振动的能量有关,阻尼振动也就是能量不断减少的振动.阻尼振动是非简谐运

  • Android 自定义View 密码框实例代码

    暴露您view中所有影响可见外观的属性或者行为. •通过XML添加和设置样式 •通过元素的属性来控制其外观和行为,支持和重要事件交流的事件监听器 详细步骤见:Android 自定义View步骤 效果图展示: 支持的样式 可以通过XML定义影响外边和行为的属性如下 边框圆角值,边框颜色,分割线颜色,边框宽度,密码长度,密码大小,密码颜色 <declare-styleable name="PasswordInputView"> <attr name="borde

随机推荐