自定义View精炼详解第(三)课:onDraw()方法解析和小白级案例实现

一、前期基础知识储备:

在笔者的 View绘制流程—自定义View相关》的文章中,讲解到,Android中最常见的自定义步骤:

①自定义View属性;

②在View的构造方法中获得自定义的属性;

③重写onMeasure(); --> 并不是必须的,大部分的时候还需要覆写

④重写onDraw();

以上四步刚好对应了笔者的《自定义View精炼详解第(一)课:基础理论部分和简单小实现》中的描述:最常见也最有效的自定义方式就是继承现有的控件和布局实现。我们在前面的课程中已经讲解了自定义View的自定义属性部分的讲解,接下来本节中笔者将带领大家一起学习onDraw()方法的相关内容,并在最后实现一个示例。

PS:本节内容较多)

二、Canvas类的运用

(1)Canvas类的定义和作用Android群英传》中对于Canvas的描述为:当测量好了一个View之后,我们就可以简单地重写onDraw()方法,并在Canvas对象上来绘制所需要的图形,首先我们来了解一下利用西永2D绘图API所必须的使用到的Canvas对象。要想在Android的界面中绘制相应的图像,就必须在Canvas上进行绘制。Canvas就像是一个画板,使用Paint作为画笔就可以在上面作画了。

接着我们在看官方文档

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 (e.g. Rect,Path, text, Bitmap), and a paint (to describe the colors and styles for the drawing).

由官方文档,我们知道,Canvas是onDraw()方法中的唯一参数,在绘图时需要明确四个核心的东西(basic components):

用什么工具画? 

这个小问题很简单,我们需要用一支画笔(Paint)来绘图。

当然,我们可以选择不同颜色的笔,不同大小的笔。

把图画在哪里呢? 

我们把图画在了Bitmap上,它保存了所绘图像的各个像素(pixel)。

也就是说Bitmap承载和呈现了画的各种图形。

画的内容? 

根据自己的需求画圆,画直线,画路径。

怎么画?

调用canvas执行绘图操作。

比如,canvas.drawCircle(),canvas.drawLine(),canvas.drawPath()将我们需要的图像画出来。

(2)利用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 SloopView(Context context, AttributeSet attrs) {
   super(context, attrs);
   initPaint();
}

②绘制颜色:绘制颜色是填充整个画布,常用于绘制底色。

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

③绘制点:可以绘制一个点,也可以绘制一组点,代码如下:

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

自定义View精炼详解第(三)课:onDraw()方法解析和小白级案例实现_第1张图片   自定义View精炼详解第(三)课:onDraw()方法解析和小白级案例实现_第2张图片

④绘制直线:绘制直线需要两个点,初始点和结束点,同样绘制直线也可以绘制一条或者绘制一组:

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

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

关于绘制矩形,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);

自定义View精炼详解第(三)课:onDraw()方法解析和小白级案例实现_第3张图片    自定义View精炼详解第(三)课:onDraw()方法解析和小白级案例实现_第4张图片

⑥绘制圆角矩形:绘制圆角矩形也提供了两种重载方式,如下:

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

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

⑦绘制圆:绘制圆形也比较简单, 绘制圆形有四个参数,前两个是圆心坐标,第三个是半径,最后一个是画笔,如下:

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

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

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

自定义View精炼详解第(三)课:onDraw()方法解析和小白级案例实现_第5张图片    自定义View精炼详解第(三)课:onDraw()方法解析和小白级案例实现_第6张图片

⑧绘制圆弧:绘制圆弧就比较神奇一点了,为了理解这个比较神奇的东西,我们先看一下它需要的几个参数:

RectF rectF2 = new RectF(100,700,600,1200);
// 绘制背景矩形
mPaint.setColor(Color.GRAY);
canvas.drawRect(rectF2,mPaint);

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

————————————————————我是分隔线——————————————————————

简要介绍Paint:看了上面这么多,相信有一部分人会产生一个疑问,如果我想绘制一个圆,只要边不要里面的颜色怎么办?很简单,绘制的基本形状由Canvas确定,但绘制出来的颜色,具体效果则由Paint确定。如果你注意到了的话,在一开始我们设置画笔样式的时候是这样的:mPaint.setStyle(Paint.Style.FILL);  //设置画笔模式为填充。为了展示方便,容易看出效果,之前使用的模式一直为填充模式,实际上画笔有三种模式,如下:

STROKE                //描边

FILL                  //填充

FILL_AND_STROKE       //描边加填充

为了区分三者效果我们做如下实验:

Paint paint = new Paint();
paint.setColor(Color.BLUE);
paint.setStrokeWidth(40);     //为了实验效果明显,特地设置描边宽度非常大

// 描边
paint.setStyle(Paint.Style.STROKE);
canvas.drawCircle(200,200,100,paint);

// 填充
paint.setStyle(Paint.Style.FILL);
canvas.drawCircle(200,500,100,paint);

// 描边加填充
paint.setStyle(Paint.Style.FILL_AND_STROKE);
canvas.drawCircle(200, 800, 100, paint);

自定义View精炼详解第(三)课:onDraw()方法解析和小白级案例实现_第7张图片

————————————————————我是分隔线——————————————————————

三、小白级示例实现

上效果图:

 自定义View精炼详解第(三)课:onDraw()方法解析和小白级案例实现_第8张图片

上完整代码:

public class PieView extends View {
    // 颜色表 
    private int[] mColors = {0xFFCCFF00, 0xFF6495ED, 0xFFE32636, 0xFF800000, 0xFF808000, 0xFFFF8C69, 0xFF808080,
            0xFFE6B800, 0xFF7CFC00};
    // 饼状图初始绘制角度
    private float mStartAngle = 0;
    // 数据
    private ArrayList mData;
    // 宽高
    private int mWidth, mHeight;
    // 画笔
    private Paint mPaint = new Paint();

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

    public PieView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mPaint.setStyle(Paint.Style.FILL);
        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;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (null == mData)
            return;
        float currentStartAngle = mStartAngle;                    // 当前起始角度
        canvas.translate(mWidth / 2, mHeight / 2);                // 将画布坐标原点移动到中心位置
        float r = (float) (Math.min(mWidth, mHeight) / 2 * 0.8);  // 饼状图半径
        RectF rect = new RectF(-r, -r, r, r);                     // 饼状图绘制区域

        for (int i = 0; i < mData.size(); i++) {
            PieData pie = mData.get(i);
            mPaint.setColor(pie.getColor());
            canvas.drawArc(rect, currentStartAngle, pie.getAngle(), true, mPaint);
            currentStartAngle += pie.getAngle();
        }

    }

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

    // 设置数据
    public void setData(ArrayList mData) {
        this.mData = mData;
        initData(mData);
        invalidate();   // 刷新
    }

    // 初始化数据
    private void initData(ArrayList mData) {
        if (null == mData || mData.size() == 0)   // 数据有问题 直接返回
            return;

        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.i("angle", "" + pie.getAngle());
        }
    }
}

至此关于自定义View的第三个阶段draw先写这么多吧。其实,在该阶段涉及到的东西非常多,我们这提到的仅仅是九牛一毛,也只是一些常用的基础性的东西。笔者也期望自己以后有时间和精力和大家一道深入地学习draw部分的相关知识和开发技能。

最后奉上一张重要的Draw属性常用表,开发中可以参考:

操作类型

相关API

备注

绘制颜色

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比较难以理解和使用,故封装了一些常用的方法。

 

你可能感兴趣的:(高级要求,高级技巧-自定义View,自定义View,onDraw(),Canvas)