Android自定义控件实现圆形进度CircleProgressBar

近日有朋友问我有没有如下图效果的开源控件

相信大家无论是用IOS还是Android,都对这种效果不陌生,很多主流APP都会有这样或类似的效果,之前也打算研究一下这类控件的代码,苦于一直不知道应该怎么搜索这种效果(就是关键词)或者所搜的结果不是自己想要的,所以就一直搁置了下来。

正好朋友需要这种效果,所以就忙里偷闲写了一个类似的、更加常见和适用范围更多的控件,效果如下图所示:

自定义上图所示效果的控件时,其实就是用Canvas绘制不同效果,比如渐变圆弧背景、圆周白色分割线、中间文字等,这篇博客也根据绘制的顺序依次阐述。

1.自定义CircleProgressBar,继承View,并实现响应的构造函数

代码如下:

/**
 * Created by WangChunLei on 2016.1.16
 * E-mail:wcl_android@163.com
 */
public class GradientProgressBar extends View {
 public GradientProgressBar(Context context) {
 super(context);
 init();
 }

 public GradientProgressBar(Context context, AttributeSet attrs) {
 super(context, attrs);
 init();
 }

 public GradientProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {
 super(context, attrs, defStyleAttr);
 init();
 }
}

其中init方法是对相关画笔进行初始化的方法,init方法代码如下:

private void init() {
 backCirclePaint = new Paint();
 backCirclePaint.setStyle(Paint.Style.STROKE);
 backCirclePaint.setAntiAlias(true);
 backCirclePaint.setColor(Color.LTGRAY);
 backCirclePaint.setStrokeWidth(circleBorderWidth);
// backCirclePaint.setMaskFilter(new BlurMaskFilter(20, BlurMaskFilter.Blur.OUTER));

 gradientCirclePaint = new Paint();
 gradientCirclePaint.setStyle(Paint.Style.STROKE);
 gradientCirclePaint.setAntiAlias(true);
 gradientCirclePaint.setColor(Color.LTGRAY);
 gradientCirclePaint.setStrokeWidth(circleBorderWidth);

 linePaint = new Paint();
 linePaint.setColor(Color.WHITE);
 linePaint.setStrokeWidth(5);

 textPaint = new Paint();
 textPaint.setAntiAlias(true);
 textPaint.setTextSize(textSize);
 textPaint.setColor(Color.BLACK);
 }

2.测量控件的宽高-onMeasure

onMeasure是自定义控件的第一步,目的就是测量得到该控件应该占有的宽高尺寸。其中onMeasure方法的代码如下:

@Override
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
 int measureWidth = MeasureSpec.getSize(widthMeasureSpec);
 int measureHeight = MeasureSpec.getSize(heightMeasureSpec);
 setMeasuredDimension(Math.min(measureWidth, measureHeight), Math.min(measureWidth, measureHeight));
 }

贴上onMeasure的代码后,大家估计是很少见过测量过程这么简单的onMeasure,不要介意,有兴趣的同僚们可以细化一下这个测量过程,对不同的测量模式分别进行处理和测量,让控件适配效果更好更完善!

onMeasure方法中,分别获取期望的宽度和高度,并取其中较小的尺寸作为该控件的宽和高。

3.依次绘制不同的控件组成部分。

因为控件是直接继承自View,所以不需要再处理onLayout方法,这也是自定义View的难度远小于自定义ViewGroup的原因,但继承ViewGroup也并不一定要重写onMeasure。
要实现如图所示的效果,需要分以下步骤依次实现

(1)绘制灰色空心圆环
(2)绘制颜色渐变的圆环
(3)绘制圆环上分割的白色线条
(4)绘制百分比文字等。

绘制过程过,后绘制的内容如果与之前绘制的内容存在交集,则后绘制的内容会覆盖掉之前绘制的内容。

按照上述步骤依次介绍

在绘制过程中,会产生以下成员变量,下文中会用到:

/*圆弧线宽*/
 private float circleBorderWidth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 20, getResources().getDisplayMetrics());
 /*内边距*/
 private float circlePadding = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 20, getResources().getDisplayMetrics());
 /*字体大小*/
 private float textSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 50, getResources().getDisplayMetrics());
 /*绘制圆周的画笔*/
 private Paint backCirclePaint;
 /*绘制圆周白色分割线的画笔*/
 private Paint linePaint;
 /*绘制文字的画笔*/
 private Paint textPaint;
 /*百分比*/
 private int percent = 0;
 /*渐变圆周颜色数组*/
 private int[] gradientColorArray = new int[]{Color.GREEN, Color.parseColor("#fe751a"), Color.parseColor("#13be23"), Color.GREEN};
 private Paint gradientCirclePaint;

3.1绘制灰色空心圆环

代码如下:

//1.绘制灰色背景圆环
 canvas.drawArc(
  new RectF(circlePadding * 2, circlePadding * 2,
   getMeasuredWidth() - circlePadding * 2, getMeasuredHeight() - circlePadding * 2), -90, 360, false, backCirclePaint);

其中,-90为绘制圆弧的起始角度,360是圆弧绘制的角度,即sweepAngle.

3.2绘制颜色渐变的圆环

//2.绘制颜色渐变圆环
 LinearGradient linearGradient = new LinearGradient(circlePadding, circlePadding,
  getMeasuredWidth() - circlePadding,
  getMeasuredHeight() - circlePadding,
  gradientColorArray, null, Shader.TileMode.MIRROR);
 gradientCirclePaint.setShader(linearGradient);
 gradientCirclePaint.setShadowLayer(10, 10, 10, Color.RED);
 canvas.drawArc(
  new RectF(circlePadding * 2, circlePadding * 2,
   getMeasuredWidth() - circlePadding * 2, getMeasuredHeight() - circlePadding * 2), -90, (float) (percent / 100.0) * 360, false, gradientCirclePaint);

其中,linearGradient是Paint的shadow,是为了圆弧的颜色渐变效果的而需要设置的,日常开发中应用频率不高,但的确是可以实现非常理想的颜色渐变效果。

3.3绘制圆环上分割的白色线条

绘制圆弧上的白色线条时,需要进行一些简单的运算,比如线条的起始坐标startX,startY和线条的终止坐标stopX,stopY等,利用简单的三角函数还是很容易去计算出来的。
效果中,将圆弧使用白色线条平分成100分,每一个的阶级为1,可以满足int类型的百分比与效果图比例的一致。

//半径
float radius = (getMeasuredWidth() - circlePadding * 3) / 2;
 //X轴中点坐标
 int centerX = getMeasuredWidth() / 2;

 //3.绘制100份线段,切分空心圆弧
 for (float i = 0; i < 360; i += 3.6) {
  double rad = i * Math.PI / 180;
  float startX = (float) (centerX + (radius - circleBorderWidth) * Math.sin(rad));
  float startY = (float) (centerX + (radius - circleBorderWidth) * Math.cos(rad));

  float stopX = (float) (centerX + radius * Math.sin(rad) + 1);
  float stopY = (float) (centerX + radius * Math.cos(rad) + 1);

  canvas.drawLine(startX, startY, stopX, stopY, linePaint);
 }

3.4绘制百分比文字等

最后绘制百分比文字。
绘制文字时,为了保持文字的中心点和圆弧的原点一致,需要先测量得到要显示文字的宽度和高度,然后再进行一些简单的运算,原理不再赘述,相信大家数学一定都比我好。

//4.绘制文字
float textWidth = textPaint.measureText(percent + "%");
int textHeight = (int) (Math.ceil(textPaint.getFontMetrics().descent - textPaint.getFontMetrics().ascent) + 2);
 canvas.drawText(percent + "%", centerX - textWidth / 2, centerX + textHeight / 4, textPaint);

最后,暴漏一个公共的方法供改变显示的百分比,代码如下:

/**
 * 设置百分比
 *
 * @param percent
 */
 public void setPercent(int percent) {
 if (percent < 0) {
  percent = 0;
 } else if (percent > 100) {
  percent = 100;
 }

 this.percent = percent;
 invalidate();
 }

至此,所有绘制过程简述完毕,130行代码就能实现很炫酷的效果有木有?

最后,贴上项目完整代码,供懒得看实现过程的同僚们使用,O(∩_∩)O哈哈~

package com.example.myview;

import android.content.Context;
import android.graphics.*;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;

/**
 * Created by WangChunLei on 2016.1.16
 * e-mail:wcl_android@163.com
 */
public class GradientProgressBar extends View {
 /*圆弧线宽*/
 private float circleBorderWidth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 20, getResources().getDisplayMetrics());
 /*内边距*/
 private float circlePadding = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 20, getResources().getDisplayMetrics());
 /*字体大小*/
 private float textSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 50, getResources().getDisplayMetrics());
 /*绘制圆周的画笔*/
 private Paint backCirclePaint;
 /*绘制圆周白色分割线的画笔*/
 private Paint linePaint;
 /*绘制文字的画笔*/
 private Paint textPaint;
 /*百分比*/
 private int percent = 0;
 /*渐变圆周颜色数组*/
 private int[] gradientColorArray = new int[]{Color.GREEN, Color.parseColor("#fe751a"), Color.parseColor("#13be23"), Color.GREEN};
 private Paint gradientCirclePaint;

 public GradientProgressBar(Context context) {
 super(context);
 init();
 }

 public GradientProgressBar(Context context, AttributeSet attrs) {
 super(context, attrs);
 init();
 }

 public GradientProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {
 super(context, attrs, defStyleAttr);
 init();
 }

 private void init() {
 backCirclePaint = new Paint();
 backCirclePaint.setStyle(Paint.Style.STROKE);
 backCirclePaint.setAntiAlias(true);
 backCirclePaint.setColor(Color.LTGRAY);
 backCirclePaint.setStrokeWidth(circleBorderWidth);
// backCirclePaint.setMaskFilter(new BlurMaskFilter(20, BlurMaskFilter.Blur.OUTER));

 gradientCirclePaint = new Paint();
 gradientCirclePaint.setStyle(Paint.Style.STROKE);
 gradientCirclePaint.setAntiAlias(true);
 gradientCirclePaint.setColor(Color.LTGRAY);
 gradientCirclePaint.setStrokeWidth(circleBorderWidth);

 linePaint = new Paint();
 linePaint.setColor(Color.WHITE);
 linePaint.setStrokeWidth(5);

 textPaint = new Paint();
 textPaint.setAntiAlias(true);
 textPaint.setTextSize(textSize);
 textPaint.setColor(Color.BLACK);
 }

 @Override
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
 int measureWidth = MeasureSpec.getSize(widthMeasureSpec);
 int measureHeight = MeasureSpec.getSize(heightMeasureSpec);
 setMeasuredDimension(Math.min(measureWidth, measureHeight), Math.min(measureWidth, measureHeight));
 }

 @Override
 protected void onDraw(Canvas canvas) {
 super.onDraw(canvas);
 //1.绘制灰色背景圆环
 canvas.drawArc(
  new RectF(circlePadding * 2, circlePadding * 2,
   getMeasuredWidth() - circlePadding * 2, getMeasuredHeight() - circlePadding * 2), -90, 360, false, backCirclePaint);
 //2.绘制颜色渐变圆环
 LinearGradient linearGradient = new LinearGradient(circlePadding, circlePadding,
  getMeasuredWidth() - circlePadding,
  getMeasuredHeight() - circlePadding,
  gradientColorArray, null, Shader.TileMode.MIRROR);
 gradientCirclePaint.setShader(linearGradient);
 gradientCirclePaint.setShadowLayer(10, 10, 10, Color.RED);
 canvas.drawArc(
  new RectF(circlePadding * 2, circlePadding * 2,
   getMeasuredWidth() - circlePadding * 2, getMeasuredHeight() - circlePadding * 2), -90, (float) (percent / 100.0) * 360, false, gradientCirclePaint);

 //半径
 float radius = (getMeasuredWidth() - circlePadding * 3) / 2;
 //X轴中点坐标
 int centerX = getMeasuredWidth() / 2;

 //3.绘制100份线段,切分空心圆弧
 for (float i = 0; i < 360; i += 3.6) {
  double rad = i * Math.PI / 180;
  float startX = (float) (centerX + (radius - circleBorderWidth) * Math.sin(rad));
  float startY = (float) (centerX + (radius - circleBorderWidth) * Math.cos(rad));

  float stopX = (float) (centerX + radius * Math.sin(rad) + 1);
  float stopY = (float) (centerX + radius * Math.cos(rad) + 1);

  canvas.drawLine(startX, startY, stopX, stopY, linePaint);
 }

 //4.绘制文字
 float textWidth = textPaint.measureText(percent + "%");
 int textHeight = (int) (Math.ceil(textPaint.getFontMetrics().descent - textPaint.getFontMetrics().ascent) + 2);
 canvas.drawText(percent + "%", centerX - textWidth / 2, centerX + textHeight / 4, textPaint);
 }

 /**
 * 设置百分比
 *
 * @param percent
 */
 public void setPercent(int percent) {
 if (percent < 0) {
  percent = 0;
 } else if (percent > 100) {
  percent = 100;
 }

 this.percent = percent;
 invalidate();
 }
}

最后,贴上自定义控件代码(自定义控件、Activity,布局文件)下载地址: Android圆形进度CircleProgressBar

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

(0)

相关推荐

  • 详解Android使用OKHttp3实现下载(断点续传、显示进度)

    OKHttp3是如今非常流行的Android网络请求框架,那么如何利用Android实现断点续传呢,今天写了个Demo尝试了一下,感觉还是有点意思 准备阶段 我们会用到OKHttp3来做网络请求,使用RxJava来实现线程的切换,并且开启Java8来启用Lambda表达式,毕竟RxJava实现线程切换非常方便,而且数据流的形式也非常舒服,同时Lambda和RxJava配合食用味道更佳 打开我们的app Module下的build.gradle,代码如下 apply plugin: 'com.an

  • Android编程之ProgressBar圆形进度条颜色设置方法

    本文实例讲述了Android ProgressBar圆形进度条颜色设置方法.分享给大家供大家参考,具体如下: 你是不是还在为设置进度条的颜色而烦恼呢--别着急,且看如下如何解决. ProgressBar分圆形进度条和水平进度条 我这里就分享下如何设置圆形进度条的颜色吧,希望对大家会有帮助. 源码如下: 布局文件代码: <ProgressBar android:id="@+id/progressbar" android:layout_width="wrap_content

  • Android 七种进度条的样式

    当一个应用在后台执行时,前台界面就不会有什么信息,这时用户根本不知道程序是否在执行.执行进度如何.应用程序是否遇到错误终止等,这时需要使用进度条来提示用户后台程序执行的进度.Android系统提供了两大类进度条样式,长形进度条(progress-BarStyleHorizontal) 和圆形进度条(progressBarStyleLarge).进度条用处很多,比如,应用程序装载资源和网络连接时,可以提示用户稍等,这一类进度条只是代表应用程序中某一部分的执行情况,而整个应用程序执行情况呢,则可以通

  • android自定义进度条渐变色View的实例代码

    最近在公司,项目不是很忙了,偶尔看见一个兄台在CSDN求助,帮忙要一个自定义的渐变色进度条,我当时看了一下进度条,感觉挺漂亮的,就尝试的去自定义view实现了一个,废话不说,先上图吧! 这个自定义的view,完全脱离了android自带的ProgressView,并且没使用一张图片,这样就能更好的降低程序代码上的耦合性! 下面我贴出代码  ,大概讲解一下实现思路吧! 复制代码 代码如下: package com.spring.progressview; import android.conten

  • android ListView和ProgressBar(进度条控件)的使用方法

    ListView控件的使用:ListView控件里面装的是一行一行的数据,一行中可能有多列,选中一行,则该行的几列都被选中,同时可以触发一个事件,这种控件在平时还是用得很多的.使用ListView时主要是要设置一个适配器,适配器主要是用来放置一些数据.使用起来稍微有些复杂,这里用的是android自带的SimpleAdapter,形式如下:android.widget.SimpleAdapter.SimpleAdapter(Context context, List<? extends Map<

  • Android中自定义进度条详解

    Android原生控件只有横向进度条一种,而且没法变换样式,比如原生rom的样子 很丑是吧,当伟大的产品设计要求更换前背景,甚至纵向,甚至圆弧状的,咋办,比如: ok,我们开始吧: 一)变换前背景 先来看看progressbar的属性: 复制代码 代码如下: <ProgressBar             android:id="@+id/progressBar"             style="?android:attr/progressBarStyleHor

  • Android中实现Webview顶部带进度条的方法

    写这篇文章,做份备忘,简单滴展示一个带进度条的Webview示例,进度条位于Webview上面. 示例图如下: 主Activity代码: 复制代码 代码如下: package com.droidyue.demo.webviewprogressbar; import android.app.Activity; import android.os.Bundle; import android.view.Menu; import android.view.View; import android.vi

  • 实例详解Android自定义ProgressDialog进度条对话框的实现

    Android SDK已经提供有进度条组件ProgressDialog组件,但用的时候我们会发现可能风格与我们应用的整体风格不太搭配,而且ProgressDialog的可定制行也不太强,这时就需要我们自定义实现一个ProgressDialog. 通过看源码我们发现,ProgressDialog继承自Alertdialog,有一个ProgressBar和两个TextView组成的,通过对ProgressDialog的源码进行改进就可以实现一个自定义的ProgressDialog. 1.效果: 首先

  • Android自定义ProgressDialog进度等待框

    Android本身已经提供了ProgressDialog进度等待框,使用该Dialog,我们可以为用户提供更好的体验:在网络请求时,弹出此框等待网络数据. 不过,既然是为了提高用户体验,我们肯定希望该Dialog能更加炫酷,让用户看着更舒服.那如何做呢,当然是我们自己定义一个ProgressDialog了. 可以先看下,接下来将实现的Dialog效果图: 步骤1:要定义布局文件,该布局文件即是Dialog的布局了 <?xml version="1.0" encoding=&quo

  • Android文件下载进度条的实现代码

    main.xml: 复制代码 代码如下: <?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:orientation="vertical"    android:layout_width="fill_paren

随机推荐