自定义View起步:Canvas之绘制基本形状

一、Canvas简介
      Canvas在Android中被称之为画布,可以帮助我们绘制各种各样的图形。是Android平台绘制2D图形的基础。但是想要绘制出来一个完美的控件也是困难的,需要对各种基础的方法非常的熟练加以运用。
二、Canvas的基本API
全部的API请参考官网的文档:点击打开链接
三、Canvas的一些基本操作
 3.1绘制颜色
 

3.2初始化画笔

3.3绘制一个点或者一组点

3.4绘制直线
  直线的绘制是由起点和终点两点的连线决定的,同样我们也可以绘制一组点

3.5绘制矩形

确定一个矩形最少需要四个数据,就是对角线的两个点的坐标值,这里一般采用左上角和右下角的两个点的坐标。

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

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

答案当然是存在区别的,两者最大的区别就是精度不同,Rect是int(整形)的,而RectF是float(单精度浮点型)的。除了精度不同,两种提供的方法也稍微存在差别,在这里我们暂时无需关注,想了解更多参见官方文档 RectRectF

3.6绘制圆角矩形

下面简单解析一下圆角矩形的几个必要的参数的意思。

很明显可以看出,第二种方法前四个参数和第一种方法的RectF作用是一样的,都是为了确定一个矩形,最后一个参数Paint是画笔,无需多说,与矩形相比,圆角矩形多出来了两个参数rx 和 ry,这两个参数是干什么的呢?

稍微分析一下,既然是圆角矩形,他的角肯定是圆弧(圆形的一部分),我们一般用什么确定一个圆形呢?

答案是圆心 和 半径,其中圆心用于确定位置,而半径用于确定大小

由于矩形位置已经确定,所以其边角位置也是确定的,那么确定位置的参数就可以省略,只需要用半径就能描述一个圆弧了。

但是,半径只需要一个参数,但这里怎么会有两个呢?

好吧,让你发现了,这里圆角矩形的角实际上不是一个正圆的圆弧,而是椭圆的圆弧,这里的两个参数实际上是椭圆的两个半径,他们看起来个如下图:

红线标注的 rx 与 ry 就是两个半径,也就是相比绘制矩形多出来的那两个参数。

我们了解到原理后,就可以为所欲为了,通过计算可知我们上次绘制的矩形宽度为700,高度为300,当你让 rx大于350(宽度的一半), ry大于150(高度的一半) 时奇迹就出现了, 你会发现圆角矩形变成了一个椭圆, 他们画出来是这样的 ( 为了方便确认我更改了画笔颜色, 同时绘制出了矩形和圆角矩形 ):

实际上在rx为宽度的一半,ry为高度的一半时,刚好是一个椭圆,通过上面我们分析的原理推算一下就能得到,而当rx大于宽度的一半,ry大于高度的一半时,实际上是无法计算出圆弧的,所以drawRoundRect对大于该数值的参数进行了限制(修正),凡是大于一半的参数均按照一半来处理。

3.7绘制椭圆

相对于绘制圆角矩形,绘制椭圆就简单的多了,因为他只需要一个矩形矩形作为参数:

绘制椭圆实际上就是绘制一个矩形的内切图形,如果你传递进来的是一个长宽相等的矩形(即正方形),那么绘制出来的实际上就是一个圆。原理如下,就不多说了:

3.8绘制圆形

绘制圆形有四个参数,前两个是圆心坐标,第三个是半径,最后一个是画笔。

3.9绘制圆弧

绘制圆弧就比较神奇一点了,为了理解这个比较神奇的东西,我们先看一下它需要的几个参数:关键是后边的StartAngle是开始扫描的角度,Sweep angle是从开始位置计算起,扫描多少度。userCenter是否启用中心位置,如果不启用,扇形的面积是扫描之后起点和结束点的连线,如果是true将会包括中心位置,是一个真正的扇形。

我们来看一下实例代码:


在看一下正圆的情况下是什么样子的:

四、简单介绍一下画笔

如果我想绘制一个圆,只要边不要里面的颜色怎么办?很简单,绘制的基本形状Canvas确定,但绘制出来的颜色,具体效果则由Paint确定。设置画笔的填充模式就可以实现我们想要的效果。如果你注意到了的话,在一开始我们设置画笔样式的时候是这样的:

实例代码如下


五、下面我们来做一个百分比的饼状图来实际操作一下

简要介绍画布的操作:

制作一个饼状图如下:

简单分析一下我们所需要的数据和思路

其实根据我们上面的知识已经能自己制作一个饼状图了。不过制作东西最重要的不是制作结果,而是制作思路。 相信我贴上代码大家一看就立刻明白了,非常简单的东西。不过嘛,咱们还是想了解一下制作思路:

先分析饼状图的构成,非常明显,饼状图就是一个又一个的扇形构成的,每个扇形都有不同的颜色,对应的有名字,数据和百分比。

经以上信息可以得出饼状图的最基本数据应包括:名字 数据值 百分比 对应的角度 颜色

用户关心的数据 : 名字 数据值 百分比
需要程序计算的数据: 百分比 对应的角度
其中颜色这一项可以用户指定也可以用程序指定(我们这里采用程序指定)。

封装数据:

package net.fitrun.mysvg;

/**
 * Created by 晁东洋 on 2017/3/28.
 * 绘制饼状图的数据
 */

public class PieData {
    // 用户关心数据
    private String name;        // 名字
    private float value;        // 数值
    private float percentage;   // 百分比

    // 非用户关心数据
    private int color = 0;      // 颜色
    private float angle = 0;    // 角度

    public PieData(String name, float value) {
        this.name = name;
        this.value = value;
    }

    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 float getAngle() {
        return angle;
    }

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

自定义View:

package net.fitrun.mysvg;

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

import java.util.ArrayList;

/**
 * Created by 晁东洋 on 2017/3/24.
 */

public class CustomView extends View {
    private Paint mPaint;

    // 颜色表(注意: 此处定义颜色使用的是ARGB,带Alpha通道的)
    private int[] mColors = {0xFFCCFF00, 0xFF6495ED, 0xFFE32636, 0xFF800000, 0xFF808000, 0xFFFF8C69, 0xFF808080,
            0xFFE6B800, 0xFF7CFC00};
    // 饼状图初始绘制角度
    private float mStartAngle = 0;

    //数据
    private ArrayList<PieData> mDate;
    //宽高
    private int mWidth,mHeight;



    public CustomView(Context context) {
        super(context);
        //初始化画笔
        intiPaint();
    }

    private void intiPaint() {
        mPaint = new Paint();
        mPaint.setStyle(Paint.Style.FILL); //设置画笔模式为填充
        //mPaint.setStrokeWidth(10f); //设置画笔的宽度为10px
        mPaint.setAntiAlias(true);
    }

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

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

    public CustomView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //取出宽度的确切数值
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        //取出宽度的测量模式
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        //取出高度的确切数值
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        //取出高度的测量模式
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);

    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mWidth = w;
        mHeight = h;
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
    }

    @Override
    public void layout(int l, int t, int r, int b) {
        super.layout(l, t, r, b);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (null == mDate)
            return;;
        float currentStartAngle = mStartAngle; //当前的起始角度,为0度
        canvas.translate(mWidth/2,mHeight/2); //将画布的中心圆点移动到中心位置
        float r = (float)(Math.min(mWidth,mHeight)/2*0.8); //绘图的半径,取宽和高较小的值
        RectF mRectf = new RectF(-r,-r,r,r); //绘图的区域,其实是一个正方形

        //开始根据数据绘制扇形
        for (int i =0; i<mDate.size();i++){
            PieData pie = mDate.get(i);
            mPaint.setColor(pie.getColor());
            canvas.drawArc(mRectf,currentStartAngle,pie.getAngle(),true,mPaint);
            currentStartAngle += pie.getAngle();
        }


    }

    //设置起始角度
    public void setStartAngle(int mStartAngle){
        this.mStartAngle = mStartAngle;
        invalidate(); //刷新界面
    }

    //设置数据
    public void setData(ArrayList<PieData> mData){
        this.mDate = mData;
        initData(mData);
        invalidate();
    }
    //计算数据
    private void initData(ArrayList<PieData> mData) {
        if (null == mData || mData.size() ==0){
            return;
        }else {
            float sumValue =0; //记录数据的总和
            for (int i=0; i<mData.size();i++){
                PieData pie = mData.get(i);
                sumValue += pie.getValue();
                int j = i % mColors.length;
                pie.setColor(mColors[j]); //循环的为每一个设置颜色

            }
            float sumAngle =0; //总的角度
            for (int i=0; i<mData.size();i++){
                PieData pie = mData.get(i);
                float percentage = pie.getValue()/sumValue; //百分比
                float angle = percentage * 360; //对应占的角度是多少

                //记录百分比
                pie.setPercentage(percentage);
                pie.setAngle(angle);

                sumAngle += angle;
                Log.e("总的角度",pie.getAngle()+"");

            }
        }
    }

    //监听键盘的点击事件
    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        return super.onKeyDown(keyCode, event);
    }

    //监听键盘的弹起事件
    @Override
    public boolean onKeyUp(int keyCode, KeyEvent event) {
        return super.onKeyUp(keyCode, event);
    }


    // 监听轨迹球的移动事件
    @Override
    public boolean onTrackballEvent(MotionEvent event) {
        return super.onTrackballEvent(event);
    }
    //触摸的事件监听
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        return super.onTouchEvent(event);
    }

    //视图焦点变化的监听
    @Override
    protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
        super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
    }
    //包含这一视图的窗口焦点的监听
    @Override
    public void onWindowFocusChanged(boolean hasWindowFocus) {
        super.onWindowFocusChanged(hasWindowFocus);
    }

    //视图被附加add时被调用
    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
    }

    //视图和窗口分离事调用
    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
    }
    //调用视图的窗口可见性变化时调用
    @Override
    protected void onWindowVisibilityChanged(int visibility) {
        super.onWindowVisibilityChanged(visibility);
    }
}


Activity中引用我们的控件

 @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        final PorterDuffXfermodeView taiJi = new PorterDuffXfermodeView(this);
        ArrayList<PieData> mlist = new ArrayList<>();
        for (int i =0; i<5;i++){
            float mF = 20f;
            mF+= mF;
            PieData pieData = new PieData("生产",mF);
            mlist.add(pieData);
        }

        CustomView customView = new CustomView(this);
        customView.setData(mlist);
        customView.setStartAngle(180);

        setContentView(customView);


最后是我们的效果图

最后如果大家喜欢我的学习笔记,可以扫描左边的二维码关注我的微信公众号,有更多的干货,更及时的分享给大家。

参考GcsSloopgithub详细内容




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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值