热门标签 | HotTags
当前位置:  开发笔记 > Android > 正文

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

这篇文章主要为大家详细介绍了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 

注:

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 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实现圆弧的渐变色效果。

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


推荐阅读
  • TechStride 网站
    TechStride 成立于2014年初,致力于互联网前沿技术、产品创意及创业内容的聚合、搜索、学习与展示。我们旨在为互联网从业者提供更高效的新技术搜索、学习、分享和产品推广平台。 ... [详细]
  • 并发编程:深入理解设计原理与优化
    本文探讨了并发编程中的关键设计原则,特别是Java内存模型(JMM)的happens-before规则及其对多线程编程的影响。文章详细介绍了DCL双重检查锁定模式的问题及解决方案,并总结了不同处理器和内存模型之间的关系,旨在为程序员提供更深入的理解和最佳实践。 ... [详细]
  • MATLAB实现n条线段交点计算
    本文介绍了一种通过逐对比较线段来求解交点的简单算法。此外,还提到了一种基于排序的方法,但该方法较为复杂,尚未完全理解。文中详细描述了如何根据线段端点求交点,并判断交点是否在线段上。 ... [详细]
  • 作为一名程序员,从大学步入职场后,常常感受到一种难以言喻的空虚感。这种感觉或许源于对生活的不满、职业发展的瓶颈,或是日常琐事带来的压力。本文将深入探讨这种复杂的情感,并尝试寻找解决之道。 ... [详细]
  • 本文深入探讨了C++对象模型中的一些细节问题,特别是虚拟继承和析构函数的处理。通过具体代码示例和详细分析,揭示了书中某些观点的不足之处,并提供了更合理的解释。 ... [详细]
  • 随着网络安全威胁的不断演变,电子邮件系统成为攻击者频繁利用的目标。本文详细探讨了电子邮件系统中的常见漏洞及其潜在风险,并提供了专业的防护建议。 ... [详细]
  • 微软Exchange服务器遭遇2022年版“千年虫”漏洞
    微软Exchange服务器在新年伊始遭遇了一个类似于‘千年虫’的日期处理漏洞,导致邮件传输受阻。该问题主要影响配置了FIP-FS恶意软件引擎的Exchange 2016和2019版本。 ... [详细]
  • 探讨如何真正掌握Java EE,包括所需技能、工具和实践经验。资深软件教学总监李刚分享了对毕业生简历中常见问题的看法,并提供了详尽的标准。 ... [详细]
  • 深入理解Spring:Aware接口、异步编程与计划任务
    本文将带你深入了解Spring框架中的 Aware 接口、异步编程以及计划任务。通过具体示例和详细解释,帮助你掌握这些核心功能的实现原理和应用场景。 ... [详细]
  • 网易严选Java开发面试:MySQL索引深度解析
    本文详细记录了网易严选Java开发岗位的面试经验,特别针对MySQL索引相关的技术问题进行了深入探讨。通过本文,读者可以了解面试官常问的索引问题及其背后的原理。 ... [详细]
  • 前端开发:从底层到顶端的行业现象解析
    在编程领域,鄙视链现象屡见不鲜,从C语言到Java、.NET等,每个技术栈都有其独特地位。然而,前端开发者尽管常处于鄙视链底端,却在市场需求中备受青睐。本文深入探讨这一现象,并分析前端工程师如何在竞争激烈的市场中脱颖而出。 ... [详细]
  • 本文介绍了多个关于JavaScript的书籍资源、实用工具和编程实例,涵盖从入门到进阶的各个阶段,帮助读者全面提升JavaScript编程能力。 ... [详细]
  • PHP插件机制的实现方案解析
    本文深入探讨了PHP中插件机制的设计与实现,旨在分享一种可行的实现方式,并邀请读者共同讨论和优化。该方案不仅涵盖了插件机制的基本概念,还详细描述了如何在实际项目中应用。 ... [详细]
  • 分享一个简化版的Silverlight链接图项目:Link Map Simplified
    本文介绍了一个使用Silverlight开发的可视化工具,主要用于展示和操作复杂的实体关系图(Graph)。该工具在犯罪调查系统中得到了广泛应用,帮助用户直观地获取和理解相关信息。 ... [详细]
  • 本文探讨了如何在日常工作中通过优化效率和深入研究核心技术,将技术和知识转化为实际收益。文章结合个人经验,分享了提高工作效率、掌握高价值技能以及选择合适工作环境的方法,帮助读者更好地实现技术变现。 ... [详细]
author-avatar
加QQ873759613
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有