Android使用Canvas绘图

目录

  • 目录
  • 前言
  • 画布Canvas
  • 画笔Paint
  • 自定义圆形进度条


前言

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提供了两个构造函数:

  1. Canvas() : 创建一个空的Canvas对象。
  2. 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绘图_第1张图片

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

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

在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的布局文件中,可以这样调用圆形进度条:


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

你可能感兴趣的:(Android应用开发)