Android使用Canvas绘制圆形进度条效果

前言

Android自定义控件经常会用到Canvas绘制2D图形,在优化自己自定义控件技能之前,必须熟练掌握Canvas绘图机制。本文从以下三个方面对Canvas绘图机制进行讲解:

画布Canvas
画笔Paint
示例圆形进度条

画布Canvas

首先,来看一下Android官网对Canvas类的定义:

The Canvas class holds the “draw” calls。To draw something, you need 4 basic components: A Bitmap to hold the pixels, a Canvas to host the draw calls(writing into the bitmap), a drawing primitive(eg, Rect, Path, text, Bitmap), and a paint(to describe the colors and styles for the drawing).

简单来说,Android进行2D绘图必须要有Canvas类的支持,它位于“android.graphics.Canvas”包下。我们可以把Canvas理解成系统分配给我们的一块用于绘图的内存(ps:真正的内存是其包含的Bitmap)。

Canvas提供了两个构造函数:

Canvas() : 创建一个空的Canvas对象。
Canvas(Bitmap bitmap) : 创建一个以bitmap位图为背景的Canvas。
通常,我们会采用第二种包含Bitmap参数的构造函数方式或者直接使用onDraw方法中系统提供的Canvas。

既然Canvas主要用于绘图,那么它提供了很多相应的draw方法,方便我们在Canvas对象上绘图,介绍几个常用的draw方法:

void drawRect(RectF rect, Paint paint) : 绘制区域,参数为RectF的区域。
void drawOval(RectF oval, Paint paint) : 绘制矩形的内切椭圆。
void drawCircle(float cx, float cy, float radius, Paint paint) : 绘制圆形。cx和cy是圆心坐标,radius是半径长度。
void drawArc(RectF oval, float startAngle, float sweepAngle. boolean useCenter, Paint paint) : 绘制圆弧形,也是以矩形的内切椭圆为标准。其中,startAngle为起始角度,sweepAngle为弧度大小,useCenter为true,则是绘制一个扇行,为false,则只是一段圆弧。(ps:startAngle为0时,是圆形钟表3点钟方向)。
void drawPath(Path path, Paint paint) : 根据给定的path,绘制连线。
void drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint) : 贴图,参数bitmap是要进行绘制的bitmap对象,参数src是指bitmap的源区域(一般为null),dst是bitmap的目标区域,paint是画笔,可为null。
void drawLine(float startX, float startY, float stopX, float stopY, Paint paint) : 根据给定的起点和结束点之间绘制连线。
void drawPoint(float x, float y, Paint paint) : 根据给定的坐标,绘制点。
void drawText(String text, float x, float y, Paint paint) : 根据给定的坐标,绘制文字。其中,x是文本起始的x轴坐标,y是文本纵向结束的y轴坐标。

画笔Paint

从上面列举的几个Canvas.drawXXX()方法可以看到,其中都有一个类型为Paint的参数,可以把它理解成为”画笔”,通过这个画笔,在Canvas这张画布上作画。它位于“android.graphics.Paint”包下,主要用于设置绘图风格,包括画笔颜色。

Paint中提供了大量设置绘画风格的方法,这里仅列出一些常用的功能:

setARGB(int a, int r, int g, int b) : 设置ARGB颜色。
setColor(int color) : 设置颜色。
setAlpha(int a) : 设置透明度。
setAntiAlias(boolean aa) : 设置是否抗锯齿。
setShader(Shader shader) : 设置Paint的填充效果。
setStrokeWidth(float width) : 设置Paint的笔触宽度。
setStyle(Paint.Style style) : 设置Paint的填充风格。
setTextSize(float textSize) : 设置绘制文本时的文字大小。

自定义圆形进度条

这里以一个自定义的圆形进度条为例,我们首先看一下效果图:

通过效果图,我们首先抽象出自定义属性,如下:

圆环内部填充色。
圆环进度条的背景色。
圆环进度条的颜色。
圆环半径。
圆环进度条的宽度。
进度条起始的角度。
中间文字的颜色。
中间文字的大小。
中间文字是否需要显示的标志位。

在Android中,可以在项目的res/values/目录下,建立一个resources源文件,通过declare-styleable来声明一个特定的属性集合。

示例属性集合如下所示(res/values/attrs_round_progress_bar.xml):

<resources>
  <declare-styleable name="RoundProgressBar">
    <attr name="startAngle" format="integer"></attr>
    <attr name="radius" format="dimension"></attr>
    <attr name="ringWidth" format="dimension"></attr>
    <attr name="centerColor" format="color"></attr>
    <attr name="ringColor" format="color"></attr>
    <attr name="progressColor" format="color"></attr>
    <attr name="textSize" format="dimension"></attr>
    <attr name="textColor" format="color"></attr>
    <attr name="isTextDisplay" format="boolean"></attr>
  </declare-styleable>
</resources>

自定义的属性可以在自定义View的构造函数中,通过TypedArray数组获取,我们来自定义一个圆形的View来实现上图的效果(RoundProgressBar.java):

package love.com.progressbar.view;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.Typeface;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;

import love.com.progressbar.R;

public class RoundProgressBar extends View {
  private static final int START_ANGLE = -90;
  private static final String CENTER_COLOR = "#eeff06";
  private static final String RING_COLOR = "#FF7281E1";
  private static final String PROGRESS_COLOR = "#FFDA0F0F";
  private static final String TEXT_COLOR = "#FF000000";
  private static final int TEXT_SIZE = 30;
  private static final int CIRCLE_RADIUS = 20;
  private static final int RING_WIDTH = 5;

  /**
   * 圆弧的起始角度,参考canvas.drawArc方法
   */
  private int startAngle;

  /**
   * 圆形内半径
   */
  private int radius;

  /**
   * 进度条的宽度
   */
  private int ringWidth;

  /**
   * 默认进度
   */
  private int mProgress = 0;

  /**
   * 圆形内部填充色
   */
  private int centerColor;

  /**
   * 进度条背景色
   */
  private int ringColor;

  /**
   * 进度条的颜色
   */
  private int progressColor;

  /**
   * 文字大小
   */
  private int textSize;

  /**
   * 文字颜色
   */
  private int textColor;

  /**
   * 文字是否需要显示
   */
  private boolean isTextDisplay;

  private String textContent;

  private Paint mPaint;

  public RoundProgressBar(Context context) {
    this(context, null);
  }

  public RoundProgressBar(Context context, AttributeSet attrs) {
    this(context, attrs, 0);
  }

  public RoundProgressBar(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);

    // 获取自定义属性
    TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RoundProgressBar);
    for (int i = 0; i < a.length(); i ++) {
      int attr = a.getIndex(i);
      switch (attr) {
        case R.styleable.RoundProgressBar_startAngle:
          startAngle = a.getInteger(attr, START_ANGLE);
          break;
        case R.styleable.RoundProgressBar_centerColor:
          centerColor = a.getColor(attr, Color.parseColor(CENTER_COLOR));
          break;
        case R.styleable.RoundProgressBar_progressColor:
          progressColor = a.getColor(attr, Color.parseColor(PROGRESS_COLOR));
          break;
        case R.styleable.RoundProgressBar_ringColor:
          ringColor = a.getColor(attr, Color.parseColor(RING_COLOR));
          break;
        case R.styleable.RoundProgressBar_textColor:
          textColor = a.getColor(attr, Color.parseColor(TEXT_COLOR));
          break;
        case R.styleable.RoundProgressBar_textSize:
          textSize = (int) a.getDimension(attr, TypedValue.applyDimension(
              TypedValue.COMPLEX_UNIT_SP, TEXT_SIZE,
              getResources().getDisplayMetrics()));
          break;
        case R.styleable.RoundProgressBar_isTextDisplay:
          isTextDisplay = a.getBoolean(attr, true);
          break;
        case R.styleable.RoundProgressBar_radius:
          radius = (int) a.getDimension(attr, TypedValue.applyDimension(
              TypedValue.COMPLEX_UNIT_DIP, CIRCLE_RADIUS,
              getResources().getDisplayMetrics()
          ));
          break;
        case R.styleable.RoundProgressBar_ringWidth:
           ringWidth = (int) a.getDimension(attr, TypedValue.applyDimension(
              TypedValue.COMPLEX_UNIT_DIP, RING_WIDTH,
              getResources().getDisplayMetrics()
          ));
          break;
        default:
          break;
      }
    }
    a.recycle();

    // 初始化画笔设置
    setPaint();
  }

  private void setPaint() {
    mPaint = new Paint();
    mPaint.setAntiAlias(true);
  }

  @Override
  protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);

    // 获取圆心坐标
    int cx = getWidth() / 2;
    int cy = cx;

    /**
     * 画圆心颜色
     */
    if (centerColor != 0) {
      drawCenterCircle(canvas, cx, cy);
    }

    /**
     * 画外层大圆
     */
    drawOuterCircle(canvas, cx, cy);

    /**
     * 画进度圆弧
     */
    drawProgress(canvas, cx, cy);

    /**
     * 画出进度百分比
     */
    drawProgressText(canvas, cx, cy);
  }

  private void drawProgressText(Canvas canvas, int cx, int cy) {
    if (!isTextDisplay) {
      return;
    }
    mPaint.setColor(textColor);
    mPaint.setTextSize(textSize);
    mPaint.setTypeface(Typeface.DEFAULT_BOLD);
    mPaint.setStrokeWidth(0);
    textContent = getProgress() + "%";
    float textWidth = mPaint.measureText(textContent);
    canvas.drawText(textContent, cx - textWidth / 2, cy + textSize / 2, mPaint);
  }

  private void drawProgress(Canvas canvas, int cx, int cy) {
    mPaint.setColor(progressColor);
    mPaint.setStrokeWidth(ringWidth);
    mPaint.setStyle(Paint.Style.STROKE);
    RectF mRectF = new RectF(cx - radius, cy - radius, cx + radius, cy + radius);
    float sweepAngle = (float) (mProgress * 360.0 / 100);
    canvas.drawArc(mRectF, startAngle, sweepAngle, false, mPaint);
  }

  private void drawOuterCircle(Canvas canvas, int cx, int cy) {
    mPaint.setStyle(Paint.Style.STROKE);
    mPaint.setColor(ringColor);
    mPaint.setStrokeWidth(ringWidth);
    canvas.drawCircle(cx, cy, radius, mPaint);
  }

  private void drawCenterCircle(Canvas canvas, int cx, int cy) {
    mPaint.setColor(centerColor);
    mPaint.setStyle(Paint.Style.FILL);
    canvas.drawCircle(cx, cy, radius, mPaint);
  }

  public synchronized int getProgress() {
    return mProgress;
  }

  public synchronized void setProgress(int progress) {
    if (progress < 0) {
      progress = 0;
    } else if (progress > 100) {
      progress = 100;
    }
    mProgress = progress;
    // 进度改变时,需要通过invalidate方法进行重绘
    postInvalidate();
  }
}

在MainActivity.java的布局文件中,可以这样调用圆形进度条:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:round="http://schemas.android.com/apk/res-auto"
  android:layout_width="match_parent"
  android:layout_height="match_parent">

  <love.com.progressbar.view.RoundProgressBar
    android:id="@+id/id_round_progressbar"
    android:layout_width="400dp"
    android:layout_height="400dp"
    round:radius="100dp"
    round:ringWidth="20dp"
    round:startAngle="-90"
    round:centerColor="#eeff06"
    round:ringColor="#e16556e6"
    round:progressColor="#d20c0c"
    round:textColor="#000000"
    round:textSize="20sp"
    round:isTextDisplay="true"/>
</RelativeLayout>

其中,xmlns:round=”http://schemas.android.com/apk/res-auto是Android Studio中增加的导入自定义View属性的命名空间写法。

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

(0)

相关推荐

  • Android自定义View系列之Path绘制仿支付宝支付成功动画

    前言 使用支付宝付款时,我们可以看到成功或者失败都会有个动画提示,如果我们需要做这样的效果的话,当然,你可以让设计师给你做个GIF,但是我们知道图像比较耗内存的,我们自己可以用代码实现还是代码实现好点吧. 效果 实现方法 首先我们需要了解PathMeasure这个类,这个类我们可以理解为用来管理Path.我们主要看几个方法. PathMeasure(): 构造方法 ,实例化一个对象 PathMeasure(Path path,boolean isClosed):传入Path对象和是否闭合,pat

  • Android使用自定义View绘制渐隐渐现动画

    实现了一个有趣的小东西:使用自定义View绘图,一边画线,画出的线条渐渐变淡,直到消失.效果如下图所示: 用属性动画或者渐变填充(Shader)可以做到一笔一笔的变化,但要想一笔渐变(手指不抬起边画边渐隐),没在Android中找到现成的API可用.所以,自己做了一个. 基本的想法是这样的: 在View的onTouchEvent中记录触摸点,生成一条一条的线LineElement,放在一个List中.给每个LineElement配置一个Paint实例. 在onDraw中绘制线段. 变换LineE

  • Android自定义View实现shape图形绘制

    概述 之前曾写过一篇文章介绍了Android中drawable使用Shape资源,通过定义drawable中的shape资源能够绘制简单的图形效果,如矩形,椭圆形,线形和圆环等.后来我在项目中正好遇到这样一个需求,要在特定的位置上显示一条垂直的虚线.正当我胸有成竹的把上面的资源文件放入进去的时候,我才发现它并不能符合我的要求.使用shape画出的垂直虚线,其实就是将一条水平的线,旋转90度.但这样做的弊端就是,该View有效区域为旋转90度后与原来位置相重合的区域,还不能随意的改动,这样的效果根

  • Android编程开发之在Canvas中利用Path绘制基本图形(圆形,矩形,椭圆,三角形等)

    本文实例讲述了Android编程开发之在Canvas中利用Path绘制基本图形的方法.分享给大家供大家参考,具体如下: 在Android中绘制基本的集合图形,本程序就是自定义一个View组件,程序重写该View组件的onDraw(Canvase)方法,然后在该Canvas上绘制大量的基本的集合图形. 直接上代码: 1.自定义的View组件代码: package com.infy.configuration; import android.content.Context; import andro

  • Android自定义View之继承TextView绘制背景

    本文实例为大家分享了TextView绘制背景的方法,供大家参考,具体内容如下 效果: 实现流程: 1.初始化:对画笔进行设置 mPaintIn = new Paint(); mPaintIn.setAntiAlias(true); mPaintIn.setDither(true); mPaintIn.setStyle(Paint.Style.FILL); mPaintIn.setColor(getResources().getColor(R.color.colorPrimary)); mPain

  • Android开发使用自定义View将圆角矩形绘制在Canvas上的方法

    本文实例讲述了Android开发使用自定义View将圆角矩形绘制在Canvas上的方法.分享给大家供大家参考,具体如下: 前几天,公司一个项目中,头像图片需要添加圆角,这样UI效果会更好看,于是写了一个小的demo进行圆角的定义,该处主要是使用BitmapShader进行了渲染(如果要将一张图片裁剪成椭圆或圆形显示在屏幕上,也可以使用BitmapShader来完成). BitmapShader类完成渲染图片的基本步骤如下: 1.创建BitmapShader类的对象 /** * Call this

  • Android自定义view绘制圆环占比动画

    一.实现效果图 二.核心代码 1.自定义MyProgressView.java package com.czhappy.effectdemo.view; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas;

  • Android自定义View绘制随机生成图片验证码

    本篇文章讲的是Android自定义View之随机生成图片验证码,开发中我们会经常需要随机生成图片验证码,但是这个是其次,主要还是想总结一些自定义View的开发过程以及一些需要注意的地方. 按照惯例先看看效果图: 一.先总结下自定义View的步骤: 1.自定义View的属性 2.在View的构造方法中获得我们自定义的属性 3.重写onMesure 4.重写onDraw 其中onMesure方法不一定要重写,但大部分情况下还是需要重写的 二.View 的几个构造函数 1.public CustomV

  • Android自定义View实现绘制虚线的方法详解

    前言 说实话当第一次看到这个需求的时候,第一反应就是Canvas只有drawLine方法,并没有drawDashLine方法啊!这咋整啊,难道要我自己做个遍历不断的drawLine?不到1秒,我就放弃这个想法了,因为太恶心了.方法肯定是有的,只不过我不知道而已. 绘制方法 最简单的方法是利用ShapeDrawable,比如说你想用虚线要隔开两个控件,就可以在这两个控件中加个View,然后给它个虚线背景. 嗯,理论上就是这样子的,实现上也很简单. <!-- drawable 文件 --> <

  • 自定义滑动按钮为例图文剖析Android自定义View绘制

    自定义View一直是横在Android开发者面前的一道坎. 一.View和ViewGroup的关系 从View和ViewGroup的关系来看,ViewGroup继承View. View的子类,多是功能型的控件,提供绘制的样式,比如imageView,TextView等,而ViewGroup的子类,多用于管理控件的大小,位置,如LinearLayout,RelativeLayout等,从下图可以看出 从实际应用中看,他们又是组合关系,我们在布局中,常常是一个ViewGroup嵌套多个ViewGro

  • Android自定义View绘制的方法及过程(二)

    上一篇<Android 自定义View(一) Paint.Rect.Canvas介绍>讲了最基础的如何自定义一个View,以及View用到的一些工具类.下面讲下View绘制的方法及过程 public class MyView extends View { private String TAG = "--------MyView"; private int width, height; public MyView(Context context, AttributeSet a

随机推荐