Android 自定义view探索——Canvas

Canvas详解

简单来说,Canvas就是Android的2D画布,可以直接操作“画笔”进行view绘制。但由于过于基础,自由性很高,因此想要实现某些效果会比较复杂。也正是由于自由度高,因此可操作性极强,上限也很高。

常用操作

绘制颜色 drawColor, drawRGB, drawARGB 使用单一颜色填充整个画布
绘制基本形状 drawPoint, drawPoints, drawLine, drawLines, drawRect, drawRoundRect, drawOval, drawCircle, drawArc 依次为 点、线、矩形、圆角矩形、椭圆、圆、圆弧
绘制图片 drawBitmap, drawPicture 绘制位图和图片
绘制文本 drawText, drawPosText, drawTextOnPath 依次为 绘制文字、绘制文字时指定每个文字位置、根据路径绘制文字
绘制路径 drawPath 绘制路径,绘制贝塞尔曲线时也需要用到该函数
顶点操作 drawVertices, drawBitmapMesh 通过对顶点操作可以使图像形变,drawVertices直接对画布作用、 drawBitmapMesh只对绘制的Bitmap作用
画布剪裁 clipPath, clipRect 设置画布的显示区域
画布快照 save, restore, saveLayerXxx, restoreToCount, getSaveCount 依次为 保存当前状态、 回滚到上一次保存的状态、 保存图层状态、 回滚到指定状态、 获取保存次数
画布变换 translate, scale, rotate, skew 依次为 位移、缩放、 旋转、错切
Matrix(矩阵) getMatrix, setMatrix, concat 实际上画布的位移,缩放等操作的都是图像矩阵Matrix, 只不过Matrix比较难以理解和使用,故封装了一些常用的方法。

开始重点

绘制颜色

一般都是用颜色填充整个画布

canvas.drawColor(Color.BLUE); //绘制蓝色

创建画笔

想要在Canvas上“作画”,就需要一根“画笔”

// 1.创建一个画笔
private Paint mPaint = new Paint();

// 2.初始化画笔
private void initPaint() {
    mPaint.setColor(Color.BLACK);       //设置画笔颜色
    mPaint.setStyle(Paint.Style.FILL);  //设置画笔模式为填充
    mPaint.setStrokeWidth(10f);         //设置画笔宽度为10px
}

// 3.在构造函数中初始化
public MyView(Context context, AttributeSet attrs) {
   super(context, attrs);
   initPaint();
}

创建完画笔,就可以开始绘制了

绘制点

可以绘制一个点,也可以绘制一组点

canvas.drawPoint(200, 200, mPaint);     
//在坐标(200,200)位置绘制一个点
canvas.drawPoints(new float[]{          
//绘制一组点,坐标位置由float数组指定
      500,500,
      500,600,
      500,700
},mPaint);

绘制直线

绘制直线时需要两个点,分别是起点和终点,用坐标来体现。
同样,在绘制直线时可以绘制一条,也可以是一组。

canvas.drawLine(300,300,500,600,mPaint);    
// 在坐标(300,300)(500,600)之间绘制一条直线
canvas.drawLines(new float[]{               
// 绘制一组线 每四数字(两个点的坐标)确定一条线
    100,200,200,200,
    100,300,200,300
},mPaint);

绘制矩形

绘制矩形需要四个点,也分别是四个顶点,每个顶点XY两个坐标值。但矩形一定需要指定四个点么?
其实,仅仅左上和右下两个点就足够了,也就说仅仅需要两对、四个坐标值

关于绘制矩形,Canvas提供了三种重载方法,第一种就是提供四个数值(矩形左上角和右下角两个点的坐标)来确定一个矩形进行绘制。 其余两种是先将矩形封装为Rect或RectF(实际上仍然是用两个坐标点来确定的矩形),然后传递给Canvas绘制

// 第一种
canvas.drawRect(100,100,800,400,mPaint);

// 第二种
Rect rect = new Rect(100,100,800,400);
canvas.drawRect(rect,mPaint);

// 第三种
RectF rectF = new RectF(100,100,800,400);
canvas.drawRect(rectF,mPaint);

三种所绘制的结果是一样的

为什么会有Rect和RectF两种?两者有什么区别吗?

答案当然是存在区别的,两者最大的区别就是精度不同,Rect是int(整形)的,而RectF是float(单精度浮点型)的。除了精度不同,两种提供的方法也稍微存在差别,这里暂不讨论。

绘制圆角矩形

圆角矩形在如今的UI开发中出现的非常多,但绘制方法和矩形差别不大,同样需要两组数据。

// 第一种
RectF rectF = new RectF(100,100,800,400);
canvas.drawRoundRect(rectF,30,30,mPaint);

// 第二种
canvas.drawRoundRect(100,100,800,400,30,30,mPaint);

第二种方法在API 21之后才有,所以暂时重点掌握第一种。

和矩形相比,圆角矩形多出了rx和ry两个参数,即示例中的30,30,这两个参数是用来确定圆角弧度的。

这里写图片描述

绘制椭圆

绘制椭圆实际上就是在一个矩形内部塞上一个椭圆,很简单。

// 第一种
RectF rectF = new RectF(100,100,800,400);
canvas.drawOval(rectF,mPaint);

// 第二种
canvas.drawOval(100,100,800,400,mPaint);

第一种比较常用。

如果传进去的矩形参数是一个正方形,那绘制出的就是一个正圆

绘制圆

虽然可以通过椭圆来绘制,不过正圆还有是对应的方法。

canvas.drawCircle(500,500,400,mPaint);  
// 绘制一个圆心坐标在(500,500),半径为400 的圆。

前两个参数是圆心坐标,后面的是半径,最后是画笔。

绘制圆弧

圆弧要相对复杂一些。
圆弧和椭圆类似,确定位置时需要一个矩形,也就是说需要两组坐标来确定位置。

RectF rectF = new RectF(100,100,800,400);
// 绘制背景矩形
mPaint.setColor(Color.GRAY);
canvas.drawRect(rectF,mPaint);

// 绘制圆弧
mPaint.setColor(Color.BLUE);
canvas.drawArc(rectF,0,90,false,mPaint);

重点看最后的drawArc
第一个参数为传入用于确定位置的的矩形。
第二个参数0代表其实参数为0。
第三个参数90代表弧形扫过的角度。
第四个参数,true表示连接圆心形成扇形,false表示不连接圆心,只连接起点和终点。

当绘制的矩形为正方形时,所绘制的圆弧就是正圆的一部分

Paint详解

Paint是我们的“画笔”,所以相关属性比较重要,直接关系到绘制的结果。
我们一开始设置画笔时是这样的:

mPaint.setStyle(Paint.Style.FILL);  //设置画笔模式为填充

这里的画笔模式设置为了填充,实际上有三种。

STROKE                //仅描边
FILL                  //仅填充
FILL_AND_STROKE       //描边加填充

配合setStrokeWidth()来设置画笔宽度,基本可以确定一个画笔的属性了。

Demo练习

先列几个画布要用到的相关操作
save 保存当前画布状态
restore 回滚到上一次保存的状态
translate 相对于当前位置位移
rotate 旋转

我们来做个饼状统计图玩玩
首先,要有不同数据的名称,然后要有数据值,最后要有对应的扇形角度。
整体思路上,用户指定名称、数据值、百分比,后台计算对应扇形角度并进行绘制。

先来封装对象

package com.zbd.viewtest;


public class PieData {
    private String name;//名称
    private float value;//数据
    private float percentage;//百分比

    //后台部分
    private int color = 0;//颜色,暂时替用户决定
    private int angle = 0;//角度,需要计算

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public float getValue() {
        return value;
    }

    public void setValue(float value) {
        this.value = value;
    }

    public float getPercentage() {
        return percentage;
    }

    public void setPercentage(float percentage) {
        this.percentage = percentage;
    }

    public int getColor() {
        return color;
    }

    public void setColor(int color) {
        this.color = color;
    }

    public int getAngle() {
        return angle;
    }

    public void setAngle(int angle) {
        this.angle = angle;
    }
}

自定义view

package com.zbd.viewtest;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;

import java.util.ArrayList;
import java.util.List;


public class MyView extends View {

    private int[] mColors = {0xFFCCFF00, 0xFF6495ED, 0xFFE32636, 0xFF800000, 0xFF808000, 0xFFFF8C69, 0xFF808080,
            0xFFE6B800, 0xFF7CFC00};
    //设置颜色

    private float mAngle  = 0;
    //初始角度为0

    private ArrayList<PieData> datas;
    //数据存放

    private int mWidth,mHeight;
    //宽高

    private Paint mPaint = new Paint();
    //画笔


    public MyView(Context context, AttributeSet attrs){
        super(context,attrs);
        initpaint();
    }

    public MyView(Context context) {
        super(context);
    }


    public void initpaint(){
        mPaint.setColor(Color.RED);
        mPaint.setStyle(Paint.Style.FILL);
//        mPaint.setStrokeWidth(10f);
        mPaint.setAntiAlias(true);
        //抗锯齿
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mWidth = w;
        mHeight = h;
        //获得view控件的大小,之所以写在这里,是因为要得到控件各种变化后的最终大小
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (datas == null){
            return;//如果数据有问题,直接返回
        }

        float currentStartAngle = mAngle;
        //当前起始角度
        canvas.translate(mHeight/2,mWidth/2);
        //将画布坐标原点移动到中心
        float r = (float)(Math.min(mWidth,mHeight)/2*0.8);
        //饼状图半径



        RectF rectF = new RectF(-r,-r,r,r);//饼状图绘制区域

        for (int i = 0;i < datas.size();i++){
            PieData pie = datas.get(i);
            mPaint.setColor(pie.getColor());
            canvas.drawArc(rectF,currentStartAngle,pie.getAngle(),true,mPaint);
            currentStartAngle += pie.getAngle();
        }
    }
    //设置起始角度
    public void setStartAngle(int mStartAngle){
        mAngle = mStartAngle;
        invalidate();//刷新
    }
    // 设置数据
    public void setData(ArrayList<PieData> mData) {
        datas = mData;
        initData(datas);
        invalidate();//刷新
    }

    private void initData(ArrayList<PieData> datas) {
        if (null == datas || datas.size() == 0){
            return;  // 数据有问题 直接返回
        }
        float sumValue = 0;
        for (int i = 0; i < datas.size(); i++) {
            PieData pie = datas.get(i);

            sumValue += pie.getValue(); //计算数值和

            int j = i % mColors.length; //设置颜色
            pie.setColor(mColors[j]);
        }

        float sumAngle = 0;
        for (int i = 0; i < datas.size(); i++) {
            PieData pie = datas.get(i);

            float percentage = pie.getValue() / sumValue;   
            // 百分比
            float angle = percentage * 360;                 
            // 对应的角度

            pie.setPercentage(percentage);                  
            // 记录百分比
            pie.setAngle(angle);                            
            // 记录角度大小
            sumAngle += angle;

            Log.i("angle", "" + pie.getAngle());
        }

    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值