欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

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

程序员文章站 2024-03-05 12:54:24
前言 android自定义控件经常会用到canvas绘制2d图形,在优化自己自定义控件技能之前,必须熟练掌握canvas绘图机制。本文从以下三个方面对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使用Canvas绘制圆形进度条效果

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

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

在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属性的命名空间写法。

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