绘图基础

这是第一次写博客,之前一直觉得写博客浪费时间,况且写完了,看不看还要看自己的心情,悲哀。直到有次看到一个公开课讲的是关于“如何学习”,这个课程核心思想就是两个字“重复”,记得里面有句话对我的印象很深 “在传教士眼里,人的记忆就像筛子,刚学到的知识用不了就多久就会漏没!所以为了让教徒记住只有不停的重复学过的!(俗称洗脑)”;最近在看启舰大佬的一本自定义的书籍,写的很好。看完了第一章,也看完了第二章,并且代码也跟着敲了一遍,等到我看第三章的时候,我发现我第一章记得东西已经快没有印象了。。果然,我的记忆就是筛子。。。。。。再三考虑还用博客记录下来,也方便自己以后复习重点,毕竟有时候在工位上拿起书的时候,组长就马上给你安排新的任务了。。。

概述

平时我们画画需要纸和笔,安卓中,Paint类就是画笔,Canvas就是纸;凡是设置画笔的大小,粗细,画笔颜色,透明度,字体样式都在Paint类里面设置;凡是要画出,原形,矩形,文字,都调用Canvas函数生成;

Paint相关的函数方法

void setAntiAlias(boolean aa) 是否开启抗锯齿功能;一般在绘制原形,文字不规则图形的时候需要开启;棱角分明的图像不需要开启(矩形,位图)。打开后,所绘图像产生平滑的边缘。

void setColor(int color). 设置画笔的颜色

void setStyle(Style style)  设置画笔的填充样式
  • Paint.Style.FILL:仅填充内部。

  • Paint.Style.FILL_AND_STROKE:填充内部和描边。

  • Paint.Style.STROKE:仅描边。

    void setStrokeWidth(float width)

于设置描边宽度值,单位是 px。当画笔的 Style 样式是 STROKE 和 FILL_AND_STROKE
时有效

Canvas相关的函数

画布背景设置:
void drawColor(int color) 该参数color取值必须是8为的0xAARRGGBB样式颜色值
void drawARGB(int a, int r, int g, int b)
void drawRGB(int r, int g, int b)

画直线
void drawLine(float startX, float startY, float stopX, float stopY, Paint paint)

  • startX:起始点 X 坐标。
  • startY:起始点 Y 坐标。
  • stopX:终点 X 坐标。
  • stopY:终点 Y 坐标。

画多条直线
void drawLines(float[] pts, Paint paint)

  • pts:点的集合,每两个点形成一条直线

void drawLines(float[] pts, int offset, int count, Paint paint)

  • int offset:集合中跳过的数值个数。注意不是点的个数!一个点有两个数值。
  • int count:参与绘制的数值个数,指 pts 数组中数值的个数,而不是点的个数,因为一 个点有两个数值。

画点

void drawPoint(float x, float y, Paint paint)
  • float x:点的 X 坐标。
  • float y:点的 Y 坐标。

画多个点
void drawPoints(float[] pts, Paint paint)
void drawPoints(float[] pts, int offset, int count, Paint paint)

  • float[] pts:点的合集,与上面的直线一致,样式为{x1,y1,x2,y2,x3,y3,…}
  • int offset:集合中跳过的数值个数。注意不是点的个数!一个点有两个数值。
  • int count:参与绘制的数值个数,指 pts 数组中数值的个数,而不是点的个数。

矩形工具类 RectF、Rect 概述
RectF()
RectF(float left, float top, float right, float bottom)
RectF(RectF r)
RectF(Rect r)

Rect()
Rect(int left, int top, int right, int bottom)
Rect(Rect r)

要构造一个矩形通常可以用:
//方法一:直接构造
Rect rect = new Rect(10,10,100,100);
//方法二:间接构造
Rect rect = new Rect();
rect.set(10,10,100,100);

画矩形
void drawRect(float left, float top, float right, float bottom, Paint paint)
void drawRect(RectF rect, Paint paint)
void drawRect(Rect r, Paint paint)

画圆角矩形
void drawRoundRect(RectF rect, float rx, float ry, Paint paint)

  • RectF rect:要绘制的矩形
  • float rx:生成圆角的椭圆的 X 轴半径。
  • float ry:生成圆角的椭圆的 Y 轴半径。

画圆形
void drawCircle(float cx, float cy, float radius, Paint paint)

  • float cx:圆心点的 X 轴坐标。
  • float cy:圆心点的 Y 轴坐标
  • float radius:圆的半径。

画椭圆
void drawOval(RectF oval, Paint paint)

画弧
void drawArc(RectF oval, float startAngle, float sweepAngle, boolean useCenter,
Paint paint)

  • RectF oval:生成椭圆的矩形
  • float startAngle:弧开始的角度,以 X 轴正方向为 0°。
  • float sweepAngle:弧持续的角度(顺时针旋转)
  • boolean useCenter:是否有弧的两边。为 true 时,表示带有两边;为 false 时,只有一条弧。

Rect与RectF

由于在绘制基本图形中经常出现Rect、RectF的身影;下面来总结下该对像常用的方法

1,是否包含点、矩形

1> 判断是否包含某个点
–> boolean contains(int x , int y); 判断某个点(x,y)是否包含在矩形中,包含返回true
利用这个函数,可以定义一个简单的控件,当手指在这个区域,矩形变为绿色;否则返回红色;

在这里插入图片描述扫下二维码看下这个控件的效果;下面会给出关键代码来讲解小demo实现思路、

首先在初始化方法中创建所需要对象;注意不能在onDraw()方法中创建对象,由于该方法会多次调用,频繁创建对象会导致内存抖动。
 private void init() {
        mPaint = new Paint();
        mPaint.setStyle(Paint.Style.STROKE);
        mrect = new Rect(100, 10, 300, 100);
    }
然后,在onTouchEvent方法中记录点击位置的x,y点,在ACTION_DOWN消息,调用invalidate() 让屏幕重绘;
需要注意的是MotionEvent.ACTION_DOWN事件中返回true,表示需要消费这个消息,后续的ACTION_MOVE,ACTION_UP消息仍然继续传递过来。
如果返回false,表示当前控件不需要这个消息,那么后续的消息不会在传递到这个控件上。
当手指抬起的时候将x,y点设置为-1;并且调用postInvalidate()重绘控件。
 public boolean onTouchEvent(MotionEvent event) {
        mX = (int) event.getX();
        mY = (int) event.getY();
        if (event.getAction() == MotionEvent.ACTION_DOWN) {
            invalidate();
            return true;
        } else if (event.getAction() == MotionEvent.ACTION_UP) {
            mX = -1;
            mY = -1;
        }
        //postInvalidate()和 invalidate()函数都是用来重绘控件的,区别是 invalidate()函数一定要在主线程中执行,否则就会报错;
		//而 postInvalidate()函数可以在任何线程中执行;在 postInvalidate()函数中就是利用 handler 给主线
		//程发送刷新界面的消息来实现的,所以它可以在任何线程中执行而不会出错。而正因为它是
		//通过发送消息来实现的,所以它的界面刷新速度可能没有直接调用 invalidate()函数那么快。
        postInvalidate();
        return super.onTouchEvent(event);
    }

在onDraw方法中判断当前点是否在mrect 这个区域内,然后设置相应的颜色值

 protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        if (mrect.contains(mX, mY)) {
            mPaint.setColor(Color.RED);
        } else {
            mPaint.setColor(Color.GREEN);
        }
        canvas.drawRect(mrect, mPaint);
    }

2>判断是否包含某个矩形
->Boolean contains(int left, int top, int right, int bottom)
->boolean contains(Rect r)
根据四个点或者rect矩形判断这个矩形是否在当前矩形的区域中。

2,判断两个矩形是否相交

1>通过静态类方法来判断
->static boolean intersects(Rect a, Rect b)
在这里插入图片描述
代码片段

	Rect rect_1 = new Rect(10,10,200,200);
    Rect rect_2 = new Rect(190,10,250,200);
    Rect rect_3 = new Rect(10,210,200,300);
//分别画出三个矩形
    paint.setColor(Color.RED);
    canvas.drawRect(rect_1,paint);
    paint.setColor(Color.GREEN);
    canvas.drawRect(rect_2,paint);
    paint.setColor(Color.BLUE);
    canvas.drawRect(rect_3,paint);
//判断是否相交
    Boolean interset1_2 = Rect.intersects(rect_1,rect_2);
    Boolean interset1_3 = Rect.intersects(rect_1,rect_3);
    //输出: rect_1&rect_2:true rect_1&rect_3:false
    Log.d("aa","rect_1&rect_2:"+interset1_2+" rect_1&rect_3:"+interset1_3);

2>成员方法判断是否相交
-> boolean intersects(int left, int top, int right, int bottom)
使用 Rect 类中自带的方法来判断当前 Rect 对象与其他矩形是否相交

3>判断相交并返回结果
->boolean intersect(int left, int top, int right, int bottom)
->boolean intersect(Rect r)
这两个成员方法与 intersects()方法的区别是,不仅会返回是否相交的结果,而且会把相交
部分的矩形赋给当前 Rect 对象
。如果两个矩形不相交,则当前 Rect 对象的值不变。

        Rect rect_1 = new Rect(10, 10, 200, 200);
        Boolean result_1 = rect_1.intersects(190, 10, 250, 200);
        printResult(result_1,rect_1);
        Boolean result_2 = rect_1.intersect(210, 10, 250, 200);
        printResult(result_2, rect_1);
        Boolean result_3 = rect_1.intersect(190, 10, 250, 200);
        printResult(result_3,rect_1);

    private void printResult(Boolean result, Rect rect) {
        Log.d("aa", rect.toShortString() + " result:" + result);
    }

在这里插入图片描述
intersects()函数只是判断是否相交,并不会改变原矩形 rect_1 的值。当 intersect()函数判断的两个矩形不相交时,也不会改变 rect_1 的值;只有当两个矩形相交时,intersect()函数才会把结果赋给 rect_1

3,合并

1>合并两个矩形
将两个矩形合并成一个矩形,即无论这两个矩形是否相交,取两个矩形最小左上角点作为结果矩形的左上角点,取两个矩形最大右下角点作为结果矩形的右下角点。如果要合并的两个矩形有一方为空,则将有值的一方作为最终结果。

public void union(int left, int top, int right, int bottom) 
public void union(Rect r)
	    Paint paint = new Paint();
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(3);
        Rect rect_1 = new Rect(30,10,60,40);
        Rect rect_2 = new Rect(100,100,130,130);
        //分别画出源矩形 rect_1、rect_2
        paint.setColor(Color.RED);
        canvas.drawRect(rect_1,paint);
        paint.setColor(Color.GREEN);
        canvas.drawRect(rect_2,paint);
        //画出合并之后的结果 rect_1
        paint.setColor(Color.YELLOW);
        rect_1.union(rect_2);
        canvas.drawRect(rect_1,paint);

在这里插入图片描述效果图

2>合并矩形与某个点
先判断当前矩形与目标合并点的关系,如果不相交,则根据目标点(x,y)的位置,将目标点设置为当前矩形的左上角点或者右下角点。如果当前矩形是一个空矩形,则最后的结果矩形为([0,0],[x,y]),即结果矩形的左上角点为[0,0],右下角点为[x,y]。

public void union(int x, int y)
        Rect rect_1 = new Rect(10, 10, 20, 20);
        rect_1.union(100,100);
        Log.d("aa", rect_1.toShortString());

        rect_1 = new Rect();
        rect_1.union(100,100);
        Log.d("aa", rect_1.toShortString());

在这里插入图片描述

路径

在 Canvas 中绘制路径的方法如下:

void drawPath(Path path, Paint paint)

1,直线路径

画一条直线路径,一般涉及下面三个函数。

void moveTo(float x1, float y1) 

(x1,y1)是直线的起始点,即将直线路径的绘制点定在(x1,y1)位置。

void lineTo(float x2, float y2) 

(x2,y2)是直线的终点,又是下一次绘制直线路径的起始点;lineTo()函数可以一直使用。

void close()

如果连续画了几条直线,但没有形成闭环,那么调用 close()函数会将路径首尾点连接起
来,形成闭环。

	    Paint paint=new Paint();
        paint.setColor(Color.RED); //设置画笔颜色
        paint.setStyle(Paint.Style.STROKE); //填充样式改为描边
        paint.setStrokeWidth(5); //设置画笔宽度
        Path path = new Path();
        path.moveTo(10, 10); //设定起始点
        path.lineTo(10, 100); //第一条直线的终点,也是第二条直线的起始点
        path.lineTo(300, 100); //画第二条直线
        path.close(); //闭环
        canvas.drawPath(path, paint);

在这里插入图片描述

2,弧线路径

void arcTo(RectF oval, float startAngle, float sweepAngle)

弧线是从椭圆上截取的一部分。
参数:
RectF oval:生成椭圆的矩形。
float startAngle:弧开始的角度,以 X 轴正方向为 0°。
float sweepAngle:弧持续的角度,如果sweepAngle是正数,那么就是按顺时针方向旋转,如果是负数就是按逆时针方向旋转。
在这里插入图片描述

	    Path path = new Path();
        path.moveTo(10,10);
        RectF rectF = new RectF(100,10,200,100);
        paint.setColor(BLUE);
        //绘制弧的那个矩形
        canvas.drawRect(rectF,paint);

        path.arcTo(rectF,-90,90);
        paint.setColor(RED);
        canvas.drawPath(path,paint);

在这里插入图片描述
在默认情况下路径都是连贯的,弧会和起始点(10,10)连接起来;除非以下两种情况:

  • 调用 addXXX 系列函数(参见 1.2.4 节),将直接添加固定形状的路径。
  • 调用 moveTo()函数改变绘制起始位置。

Path 类也提供了另外两个重载方法。

void arcTo(float left, float top, float right, float bottom, float startAngle, float sweepAngle, boolean forceMoveTo) 
void arcTo(RectF oval, float startAngle, float sweepAngle, boolean forceMoveTo)

参数 boolean forceMoveTo 的含义是是否强制将弧的起始点作为绘制起始位置。

3,addXXX系列函数

路径一般都是连贯的,而 addXXX 系列函数可以让我们直接往 Path 中添加一些曲线,而不必考虑连贯性。注意“添加”这个词。

        Path path = new Path();
        path.moveTo(10,10);
        path.lineTo(100,50);
        RectF rectF = new RectF(100,100,150,150);
        path.addArc(rectF,0,90);
        canvas.drawPath(path, paint);

在这里插入图片描述

1,添加矩形路径
void addRect(float left, float top, float right, float bottom, Path.Direction dir) 
void addRect(RectF rect, Path.Direction dir)

此方法增加了Path.Direction 参数。Path.Direction 参数有两个值。

  • Path.Direction.CCW:是 counter-clockwise 的缩写,指创建逆时针方向的矩形路径。
  • Path.Direction.CW:是 clockwise 的缩写,指创建顺时针方向的矩形路径。
2,添加圆角矩形路径
void addRoundRect(RectF rect, float[] radii, Path.Direction dir) 
void addRoundRect(RectF rect, float rx, float ry, Path.Direction dir)

在第一个构造函数中,我们可以指定每个角的圆角大小。
float[] radii:必须传入 8 个数值,分 4 组,分别对应每个角所使用的椭圆的横轴半径和纵轴半径,如{x1,y1,x2,y2,x3,y3,x4,y4},其中,x1,y1 对应第一个角(左上角)的用来生成圆角的椭圆的横轴半径和纵轴半径,其他类推……
而在第二个构造函数中,只能构建统一的圆角大小。

  • float rx:生成圆角的椭圆的横轴半径。
  • float ry:生成圆角的椭圆的纵轴半径。
3,添加圆形路径
void addCircle(float x, float y, float radius, Path.Direction dir)

参数:

  • float x:圆心 X 轴坐标。
  • float y:圆心 Y 轴坐标。
  • float radius:圆半径。
4,添加椭圆路径
void addOval(RectF oval, Path.Direction dir)
5,添加弧形路径
void addArc(float left, float top, float right, float bottom, float startAngle, float sweepAngle) 
void addArc(RectF oval, float startAngle, float sweepAngle)

1.2.5 填充模式

Path.FillType 表示 Path 的填充模式,它有 4 个枚举值。

  • FillType.WINDING:默认值,当两个图形相交时,取相交部分显示。
  • FillType.EVEN_ODD:取 path所在并不相交的区域。
  • FillType.INVERSE_WINDING:取 path 的外部区域。
  • FillType.INVERSE_EVEN_ODD:取 path 的外部和相交区域。

FillType.INVERSE_WINDING 就是取 FillType.WINDING
的相反部分;同理,FillType.INVERSE_EVEN_ODD 就是取 FillType.EVEN_ODD 的相反部分。

设置填充模式使用 setFillType(FillType filltype)函数。

   	Paint paint = new Paint(); 
	paint.setColor(Color.RED); 
	paint.setStyle(Paint.Style.FILL); 
	Path path = new Path(); 
	path.addRect(100, 100, 300, 300, Path.Direction.CW); 
	path.addCircle(300, 300, 100, Path.Direction.CW); 
	path.setFillType(Path.FillType.WINDING); 
	canvas.drawPath(path,paint);

在这里插入图片描述

1.2.6 重置路径

当需要重绘一条全新的路径时,为了重复利用空间,可以重置路径对象。一旦被重置,其中保存的所有路径都将被清空。通过重置方法可以减少对象的创建。

void reset() 
void rewind()

设置填充模式使用 setFillType(FillType filltype)函数。

这两个函数的共同点是都会清空内部所保存的所有路径,但二者也有区别

  • rewind()函数会清除 FillType 及所有的直线、曲线、点的数据等,但是会保留数据结构。这样可以实现快速重用,提高一定的性能。例如,重复绘制一类线段,它们的点的数量都相等,那么使用 rewind()函数可以保留装载点数据的数据结构,效率会更高。一定要注意的是,只有在重复绘制相同的路径时,这些数据结构才是可以复用的。
  • reset()函数类似于新建一个路径对象,它的所有数据空间都会被回收并重新分配,但不会清除 FillType
	//效果图一,
	//首先设置 Path 的填充类型为 FillType.INVERSE_WINDING,
	//在调用 reset()函数以后,画了一个圆,但结果却是圆形以外的区域。
	//很明显,调用 reset()函数并没有清除原有的 Path 填充类型。
   	Path path = new Path(); 
	path.setFillType(Path.FillType.INVERSE_WINDING); 
	path.reset(); 
	path.addCircle(100, 100, 50, Path.Direction.CW); 
	canvas.drawPath(path, paint);
	
	//效果图二 
	//使用 rewind()函数清空了填充类型
	Path path = new Path(); 
	path.setFillType(Path.FillType.INVERSE_WINDING); 
	path.rewind(); 
	path.addCircle(100, 100, 50, Path.Direction.CW); 
	canvas.drawPath(path, paint);

在这里插入图片描述
在这里插入图片描述

1.2.7 插个小demo 现实蜘蛛网状图

在这里插入图片描述

初始化参数

    //数据个数
    private int count = 6;
    private float radius;//网格最大半径
    private int centerX;//中心X
    private int centerY;//中心Y
    private Paint radarPaint, valuePaint;
    //计算出每个夹角的度数
    private float angle = (float) (Math.PI * 2 / count);
    //数据
    private double[] data = {2, 5, 1, 6, 4, 5};
    //最大值
    private float maxValue = 6;
    
	private void init() {
        radarPaint = new Paint();
        radarPaint.setStyle(Paint.Style.STROKE);
        radarPaint.setColor(Color.GREEN);

        valuePaint = new Paint();
        valuePaint.setColor(Color.BLUE);
        valuePaint.setStyle(Paint.Style.FILL);
    }

获取半径,中心点坐标

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        radius = Math.min(h, w) / 2 * 0.9f;
        //中心坐标
        centerX = w / 2;
        centerY = h / 2;
        postInvalidate();
        super.onSizeChanged(w, h, oldw, oldh);
    }

分别绘制直线和正多边形,数据图

    /**
     * 绘制正多边形
     */
    private void drawPolygon(Canvas canvas) {
        Path path = new Path();
        float r = radius / (count);//r是蜘蛛丝之间的间距
        for (int i = 1; i <= count; i++) {//中心点不用绘制
            float curR = r * i;//当前半径
            path.reset();
            for (int j = 0; j < count; j++) {
                if (j == 0) {
                    path.moveTo(centerX + curR, centerY);
                } else {
                    //根据半径,计算出蜘蛛丝上每个点的坐标
                    float x = (float) (centerX + curR * Math.cos(angle * j));
                    float y = (float) (centerY + curR * Math.sin(angle * j));
                    path.lineTo(x, y);
                }
            }
            path.close();//闭合路径
            canvas.drawPath(path, radarPaint);
        }
    }

    /**
     * 绘制直线
     */
    private void drawLines(Canvas canvas) {
        Path path = new Path();
        for (int i = 0; i < count; i++) {
            path.reset();
            path.moveTo(centerX, centerY);
            float x = (float) (centerX + radius * Math.cos(angle * i));
            float y = (float) (centerY + radius * Math.sin(angle * i));
            path.lineTo(x, y);
            canvas.drawPath(path, radarPaint);
        }
    }

    /**
     * 绘制区域
     *
     * @param canvas
     */
    private void drawRegion(Canvas canvas) {
        Path path = new Path();
        valuePaint.setAlpha(127);
        for (int i = 0; i < count; i++) {
            double percent = data[i] / maxValue;
            float x = (float) (centerX + radius * Math.cos(angle * i) * percent);
            float y = (float) (centerY + radius * Math.sin(angle * i) * percent);
            if (i == 0) {
                path.moveTo(x, centerY);
            } else {
                path.lineTo(x, y);
            }
            //绘制小圆点
            canvas.drawCircle(x, y, 10, valuePaint);
        }
        //绘制填充区域
        valuePaint.setStyle(Paint.Style.FILL_AND_STROKE);
        canvas.drawPath(path, valuePaint);
    }

1.3文字

1.3.1 Paint设置

Paint 与文字相关的设置方法

//普通设置
paint.setStrokeWidth (5); //设置画笔宽度
paint.setAntiAlias(true); //指定是否使用抗锯齿功能。如果使用,则会使绘图速度变慢
paint.setStyle(Paint.Style.FILL); //绘图样式,对于文字和几何图形都有效
paint.setTextAlign(Align.CENTER); //设置文字对齐方式,取值为 Align.CENTER、
 //Align.LEFT 或 Align.RIGHT 
paint.setTextSize(12); //设置文字大小
//样式设置
paint.setFakeBoldText(true); //设置是否为粗体文字
paint.setUnderlineText(true); //设置下画线
paint.setTextSkewX((float) -0.25); //设置字体水平倾斜度,普通斜体字设为-0.25 
paint.setStrikeThruText(true); //设置带有删除线效果
//其他设置
paint.setTextScaleX(2); //只会将水平方向拉伸,高度不会变

1.3.2 Canvas绘制文本

1.普通绘制
void drawText(String text, float x, float y, Paint paint)

参数x,y为文字绘制起始点坐标

void drawText(CharSequence text, int start, int end, float x, float y, Paint paint) 
void drawText(String text, int start, int end, float x, float y, Paint paint)

通过指定字符串中字符的起始和终止位置截取字符串的一部分绘制;参数的含义如下

  • start:表示起始绘制字符所在字符串中的索引。
  • end:表示结束绘制字符所在字符串中的索引。
  • x,y:起始点坐标。
void drawText(char[] text, int index, int count, float x, float y, Paint paint)

该函数截取字符串的一部分绘制,参数:

  • int index:指定起始绘制字符的位置。
  • int count:指定从起始绘制字符开始绘制几个字符。
  • x,y:起始点坐标。
2.逐个指定文字位置

使用Canvas指定每个要绘制的文字具体位置

void drawPosText(String text, float[] pos, Paint paint) 
void drawPosText(char[] text, int index, int count, float[] pos, Paint paint)

第二个构造函数先将字符串截取一段,再指定所截取的文字的绘制位置。
参数:

  • char[] text/String text:要绘制的字符串。
  • int index:第一个要绘制的文字的索引。
  • int count:要绘制的文字的个数,用来计算最后一个文字的位置,从第一个绘制的文字
    开始算起、
  • float[] pos:要绘制的每个文字的具体位置,同样两两一组,如{x1,y1,x2,y2,x3,y3,…}
3.沿路径绘制

使用Canvas指定每个要绘制的文字具体位置

void drawTextOnPath (String text, Path path, float hOffset, float vOffset, Paint paint) 
void drawTextOnPath (char[] text, int index, int count, Path path, float hOffset, float vOffset, Paint paint)

第二个函数能实现截取一部分文字绘制。着重看一下hOffset 和 vOffset 参数的含义。

  • float hOffset:与路径起始点的水平偏移量。
  • float vOffset:与路径中心的垂直偏移量。
Paint paint=new Paint(); 
paint.setColor(Color.RED); 
paint.setStrokeWidth (5); 
paint.setTextSize(45); 
paint.setStyle(Paint.Style.STROKE); 
//先创建两条相同的圆形路径,并画出两条路径原形
Path circlePath=new Path(); 
circlePath.addCircle(220,300, 150, Path.Direction.CCW);//逆向绘制
canvas.drawPath(circlePath, paint); //绘制出路径原形
Path circlePath2=new Path(); 
circlePath2.addCircle(600,300, 150, Path.Direction.CCW); 
canvas.drawPath(circlePath2, paint); //绘制出路径原形
//绘制原始文字与偏移文字
String string="床前明月光,疑是地上霜"; 
paint.setColor(Color.GREEN); 
//将 hOffset、vOffset 参数值全部设为 0,看原始状态是怎样的
canvas.drawTextOnPath(string, circlePath, 0, 0, paint); 
//第二条路径,改变 hOffset、vOffset 参数值
canvas.drawTextOnPath(string, circlePath2, 80, 30, paint);

在这里插入图片描述

1.3.3 设置字体样式

在 Paint 中有一个函数是专门用来设置字体样式的

Typeface setTypeface(Typeface typeface)
1.使用系统中的字体样式
1)使用 Android 自带的字体样式

Typeface 类中保存着三种自带的字体样式:Typeface.SANS_SERIF、Typeface.MONOSPACE和 Typeface.SERIF。我们可以直接使用这三种字体样式。

2)defaultFromStyle()函数
Typeface defaultFromStyle(int style)

该函数会根据字体样式获取对应的默认字体。参数 int style 的取值如下。

  • Typeface.NORMAL:正常字体。
  • Typeface.BOLD:粗体。
  • Typeface.ITALIC:斜体。
  • Typeface.BOLD_ITALIC:粗斜体。

如果系统默认的字体是宋体,那么,当指定 defaultFromStyle(Typeface.BOLD_ITALIC)时,
获取的将是粗斜体的宋体样式。

3)create(String familyName, int style)函数
Typeface create(String familyName, int style)

该函数直接通过指定字体名来加载系统中自带的字体样式。如果字体样式不存在,则会
用系统样式替代并返回。

2.自定义字体样式

要自定义字体样式,就需要从外部字体文件中加载我们所需的字形,这时所使用的Typeface 构造函数有如下三个

Typeface createFromAsset(AssetManager mgr, String path) 
Typeface createFromFile(String path) 
Typeface createFromFile(File path)

1.4 Region

Region 译为“区域”,顾名思义,区域是一块任意形状的封闭图形

1.4.1 构造Region

1.直接构造
public Region(Region region) //复制一个 Region 的范围 
public Region(Rect r) //创建一个矩形区域 
public Region(int left, int top, int right, int bottom) //创建一个矩形区域 

第一个构造函数通过其他 Region 来复制一个同样的 Region 变量。
第二、三个构造函数才是常用的,根据一个矩形或矩形的左上角点和右下角点构造出一个矩形区域。

由于 Canvas 中并没有用来画 Region 的方法,所以,如果我们想将 Region 画出来,就定义一个drawRegion()函数将整个 Region 画出来。drawRegion()函数的定义如下:

private void drawRegion(Canvas canvas,Region rgn,Paint paint) { 
	 RegionIterator iter = new RegionIterator(rgn); 
	 Rect r = new Rect(); 
	 while (iter.next(r)) { 
	 	canvas.drawRect(r, paint); 
	 } 
}
2.间接构造

间接构造主要是通过 public Region()的空构造函数与 set 系列函数相结合来实现的。

public Region() 

set 系列函数:

public void setEmpty() //置空
public boolean set(Region region) 
public boolean set(Rect r) 
public boolean set(int left, int top, int right, int bottom) 
public boolean setPath(Path path, Region clip) 

注意:无论调用 set 系列函数的 Region 是不是有区域值,当调用 set 系列函数后,原来的
区域值就会被替换成 set 系列函数里的区域值。

各函数的含义如下。

  • setEmpty():从某种意义上讲,置空也是一个构造函数,即将原来的一个区域变量变成空变量,再利用其他的 set 函数重新构造区域。
  • set(Region region):利用新的区域替换原来的区域。
  • set(Rect r):利用矩形所代表的区域替换原来的区域。
  • set(int left, int top, int right, int bottom):根据矩形的两个角点构造出矩形区域来替换原来的区域。
  • setPath(Path path, Region clip):根据路径的区域与某区域的交集构造出新的区域。

主要讲解利用 setPath()函数构造不规则区域的方法
参数:

  • Path path:用来构造区域的路径。
  • Region clip:与前面的 path 所构成的路径取交集,并将该交集设置为最终的区域。
Paint paint = new Paint(); 
paint.setColor(Color.RED); 
paint.setStyle(Paint.Style.FILL); 
//构造一条椭圆路径
Path ovalPath = new Path(); 
RectF rect = new RectF(50, 50, 200, 500); 
ovalPath.addOval(rect, Path.Direction.CCW); 
//在 setPath()函数中传入一个比椭圆区域小的矩形区域,让其取交集
Region rgn = new Region(); 
rgn.setPath(ovalPath, new Region(50, 50, 200, 200)); 
//画出路径
drawRegion(canvas, rgn, paint);

左侧分别画出了所构造的椭圆和矩形,二者相交之后,所画出的 Region 对象是如右侧图
像所示的椭圆上部分。
在这里插入图片描述

1.4.2 枚举区域——RegionIterator类

对于特定的区域,可以使用多个矩形来表示其大致形状。事实上,如果矩形足够小,一定数量的矩形就能够精确表示区域的形状。也就是说,一定数量的矩形所合成的形状也可以代表区域的形状。RegionIterator 类就实现了获取组成区域的矩形集的功能。包含两个函数:一个构造函数和一个获取下一个矩形的函数。
(1)构造函数:根据区域构建对应的矩形集。

RegionIterator(Region region) 

(2)获取下一个矩形,将结果保存在参数 Rect r 中。

boolean next(Rect r)

由于在 Canvas 中没有直接绘制 Region 的函数,想要绘制一个区域,就只能
通过 RegionIterator 类构造矩形集来逼近显示区域
,所以 drawRegion()函数的具体实现如下:

private void drawRegion(Canvas canvas,Region rgn,Paint paint) { 
	 RegionIterator iter = new RegionIterator(rgn); 
	 Rect r = new Rect(); 
	 while (iter.next(r)) { 
	 	canvas.drawRect(r, paint); 
	 } 
}

首先根据区域构造一个矩形集,然后利用 next(Rect r)函数来逐个获取所有矩形并绘制出来,最终得到的就是整个区域

Paint paint = new Paint(); 
paint.setColor(Color.RED); 
paint.setStyle(Paint.Style.STROKE); 
//构造一条椭圆路径
Path ovalPath = new Path(); 
RectF rect = new RectF(50, 50, 200, 500); 
ovalPath.addOval(rect, Path.Direction.CCW); 
//构造椭圆区域
Region rgn = new Region(); 
rgn.setPath(ovalPath, new Region(50, 50, 200, 500)); 
drawRegion(canvas,rgn,paint);

在代码中,先构造了一条椭圆路径,然后在形成 Region 时传入一个与构造的椭圆区域相同大小的矩形,所以取交集之后的结果就是椭圆路径所对应的区域。从效果图中可以明显看出,在绘制 Region 对象时,其实就是先将其转换成矩形集,然后利用画笔将每个矩形画出来而已。
在这里插入图片描述

1.4.3 区域相交

Region 不是用来绘图的,所以 Region 最重要的功能在区域的相交操作中。

1.union()函数
boolean union(Rect r)

用于与指定矩形取并集,即将 Rect 所指定的矩形加入当前区域中。

Paint paint = new Paint(); 
paint.setColor(Color.RED); 
paint.setStyle(Paint.Style.FILL); 
Region region = new Region(10,10,200,100); 
region.union(new Rect(10,10,50,300)); 
drawRegion(canvas,region,paint);

先在横向、竖向分别画两个矩形区域,然后利用 union()函数将两个矩形
区域合并。
在这里插入图片描述

2.区域操作
boolean op(Rect r, Op op) 
boolean op(int left, int top, int right, int bottom, Op op) 
boolean op(Region region, Op op)

用当前的 Region 对象与指定的一个 Rect 对象或者 Region 对象执行
相交操作,并将结果赋给当前的 Region 对象。如果计算成功,则返回 true;否则返回 false。

常用指定操作类型的 Op 参数。Op 参数值有如下 6 个。

public enum Op { 
 DIFFERENCE(0), //最终区域为 region1 与 region2 不同的区域
 INTERSECT(1), // 最终区域为 region1 与 region2 相交的区域
 UNION(2), //最终区域为 region1 与 region2 组合在一起的区域
 XOR(3), //最终区域为 region1 与 region2 相交之外的区域
 REVERSE_DIFFERENCE(4), //最终区域为 region2 与 region1 不同的区域
 REPLACE(5); //最终区域为 region2 的区域
}

代码演示:Op 参数6种情况

//1,构造两个矩形
Rect rect1 = new Rect(100,100,400,200); 
Rect rect2 = new Rect(200,0,300,300); 
//构造一个画笔,画出矩形的轮廓
Paint paint = new Paint(); 
paint.setColor(Color.RED); 
paint.setStyle(Style.STROKE);
paint.setStrokeWidth(2); 
canvas.drawRect(rect1, paint); 
canvas.drawRect(rect2, paint);


//构造两个区域,后利用上面的两个 rect(rect1 和 rect2)来构造 Region,
//并在 rect1 的基础上取与 rect2的交集。
Region region = new Region(rect1); 
Region region2= new Region(rect2); 
//取两个区域的交集
region.op(region2, Op.INTERSECT);

//最后构造一个填充画笔,将所选区域用绿色填充。
Paint paint_fill = new Paint(); 
paint_fill.setColor(Color.GREEN); 
paint_fill.setStyle(Style.FILL); 
drawRegion(canvas, region, paint_fill);

最终对比图
在这里插入图片描述

boolean op(Rect rect, Region region, Op op) 
boolean op(Region region1, Region region2, Region.Op op)

允许我们传入两个 Region 对象进行区域操作,并将操作结果赋给当前的Region 对象。同样,当操作成功时,返回 true;否则返回 false;将 region1、region2 相交的结果赋给 Region 对象。

Region region1 = new Region(100,100,400,200); 
Region region2 = new Region(200,0,300,300); 
Region region = new Region(); 
region.op(region1,region2, Region.Op.INTERSECT);

1.4.4 其他函数

public boolean isEmpty(); 

该函数用于判断该区域是否为空

public boolean isRect(); 

该函数用于判断该区域是否是一个矩阵。

public boolean isComplex(); 

该函数用于判断该区域是否是多个矩阵的组合。

public Rect getBounds() 
public boolean getBounds(Rect r) 

getBound 系列函数用于返回一个 Region 的边界。

这两个函数用于返回能够包裹当前路径的最小矩形。

public Path getBoundaryPath() 
public boolean getBoundaryPath(Path path) 

这两个函数用于返回当前矩形所对应的 Path 对象。

public boolean contains(int x, int y); 

该函数用于判断该区域是否包含某个点。

public boolean quickContains(Rect r) 
public boolean quickContains(int left, int top, int right,int bottom) 

这两个函数用于判断该区域是否包含某个矩形。

public boolean quickReject(Rect r) 
public boolean quickReject(int left, int top, int right, int bottom); 

这两个函数用于判断该区域是否没有和指定矩形相交。

public boolean quickReject(Region rgn); 

该函数用于判断该区域是否没有和指定区域相交。

public void translate(int dx, int dy) 

该函数用于将 Region 对象向 X 轴平移 dx 距离,向 Y 轴平移 dy 距离,并将结果赋给当前的 Region 对象。X 轴向右是正方向,Y 轴向下是正方向。

public void translate(int dx, int dy, Region dst); 

该函数用于将 Region 对象向 X 轴平移 dx 距离,向 Y 轴平移 dy 距离。与上一个函数不同
的是,该函数将结果赋给 dst 对象,而当前 Region 对象的值保持不变。

1.5 Canvas(画布)

除可以在Canvas 上面绘图以外,还可以对画布进行变换及裁剪等操作。

1.5.1 Canvas变换

Canvas 中有一个函数 translate()是用来实现画布平移的。画布的原始状态是以左上角点为原点,向右是 X 轴正方向,向下是 Y 轴正方向,如下图所示。
在这里插入图片描述

1.平移
void translate(float dx, float dy)

参数:

  • float dx:水平方向平移的距离,正数为向正方向(向右)平移的量,负数为向负方向(向左)平移的量。
  • float dy:垂直方向平移的距离,正数为向正方向(向下)平移的量,负数为向负方向(向上)平移的量。
2.屏幕显示与 Canvas 的关系

Canvas 相当于一个透明图层。每次在 Canvas 上画图时(调用 drawXXX 系列函数),都会先产生一个透明图层,然后在这个图层上画图,画完之后覆盖在屏幕上显示。在进行画图的时候经以下几个步骤形
成的:
(1)在调用 canvas.drawRect(r ect1, paint_green);时,产生一个 Canvas 透明图层,由于当时
还没有对坐标系进行平移,所以坐标原点是(0,0);在 Canvas 上画好之后,覆盖到屏幕上显示
出来。
(2)在调用 canvas.drawRect(rect1, paint_red);时,又会产生一个全新的 Canvas 透明图层,
但此时画布坐标已经改变了,即分别向右和向下移动了 100 像素,所以此时的绘图方式如下
图所示(合成视图,从上往下看的合成方式)。

注意:,在 Canvas 上虽然能画图,但超出了屏幕的范围,是不会显示的。
在这里插入图片描述
下面对做一下总结:

  • 每次调用 drawXXX 系列函数来绘图时,都会产生一个全新的 Canvas 透明图层。
  • 如果在调用 drawXXX 系列函数前,调用平移、旋转等函数对 Canvas 进行了操作,那么这个操作是不可逆的。每次产生的画布的最新位置都是执行这些操作后的位置。
  • 在 Canvas 图层与屏幕合成时,超出屏幕范围的图像是不会显示出来的。
3.旋转(Rotate)

画布的旋转默认是围绕坐标原点来进行的。这里容易产生错觉,看起来是图片旋转了,其实我们旋转的是画布,以后在此画布上绘制的图形显示出来的时候看起来都是旋转的。
rotate()函数有两个构造函数。

void rotate(float degrees) 
void rotate(float degrees, float px, float py)

第一个构造函数直接输入旋转的度数,正数指顺时针旋转,负数指逆时针旋转,它的旋转中心点是原点(0,0)。
第二个构造函数除度数以外,还可以指定旋转的中心点坐标(px,py)。

 Paint paint_green = generatePaint(Color.GREEN, Style.FILL, 5); 
 Paint paint_red = generatePaint(Color.RED, Style.STROKE, 5); 
 
 Rect rect1 = new Rect(300,10,500,100); 
 canvas.drawRect(rect1, paint_red); //画出原轮廓
 
canvas.rotate(30); //顺时针旋转画布
canvas.drawRect(rect1, paint_green); //画出旋转后的矩形

在这里插入图片描述

4.缩放(Scale)

该函数用于变更坐标轴密度,它有两个构造函数

public void scale(float sx, float sy)
  • float sx:水平方向伸缩的比例。假设原坐标轴的比例为 n,不变时为 1,变更后的 X轴密度为 n×sx。所以,sx 是小数表示缩小,sx 是整数表示放大。
  • float sy:垂直方向伸缩的比例。同样,sy 为小数表示缩小,sy 为整数表示放大。

这里多了两个参数 px,py,表示缩放中心位置。

public void scale(float sx, float sy, float px, float py)
 Paint paint_green = generatePaint(Color.GREEN, Style.STROKE, 5); 
 Paint paint_red = generatePaint(Color.RED, Style.STROKE, 5);
 Rect rect1 = new Rect(10,10,200,100); 
 canvas.drawRect(rect1, paint_green); 
 
 canvas.scale(0.5f, 1); 
 canvas.drawRect(rect1, paint_red)

在这里插入图片描述

5.扭曲(Skew)
void skew(float sx, float sy)
  • float sx:将画布在 X 轴方向上倾斜相应的角度,sx 为倾斜角度的正切值。
  • float sy:将画布在 Y 轴方向上倾斜相应的角度,sy 为倾斜角度的正切值。

注意:这里都是倾斜角度的正切值,比如,在 X 轴方向上倾斜 60°,tan60=1.732

6.裁剪画布(clip 系列函数)

裁剪画布是指利用 clip 系列函数,通过与 Rect、Path、Region 取交、并、差等集合运算来获得最新的画布形状。除调用 save()、restore()函数以外,这个操作是不可逆的,一旦 Canvas被裁剪,就不能恢复。
注意:在使用 clip 系列函数时,需要禁用硬件加速功能

setLayerType(LAYER_TYPE_SOFTWARE,null);

clip 系列函数如下:

boolean clipPath(Path path) 
boolean clipPath(Path path, Region.Op op)
boolean clipRect(Rect rect, Region.Op op)
boolean clipRect(RectF rect, Region.Op op) 
boolean clipRect(int left, int top, int right, int bottom) 
boolean clipRect(float left, float top, float right, float bottom) 
boolean clipRect(RectF rect) 
boolean clipRect(float left, float top, float right, float bottom, Region.Op op) 
boolean clipRect(Rect rect) 
boolean clipRegion(Region region) 
boolean clipRegion(Region region, Region.Op op)
canvas.drawColor(Color.RED); 
 canvas.clipRect(new Rect(100, 100, 200, 200)); 
 canvas.drawColor(Color.GREEN);

在这里插入图片描述

1.5.2 画布的保存与恢复

1.save()和 restore()函数

这两个函数的原型如下:

int save() 
void restore()
  • save():每次调用 save()函数,都会先保存当前画布的状态,然后将其放入特定的栈中。
  • restore():每次调用 restore()函数,都会把栈中顶层的画布状态取出来,并按照这个状态恢复当前的画布,然后在这个画布上作画。
2.restoreToCount(int saveCount)函数

在利用 save()函数保存画布时,会有一个 int 类型的返回值。该返回值是当前所保存的画布所在栈的索引

public void restoreToCount(int saveCount);

restoreToCount()函数的用法就是一直出栈,直到指定索引的画布出栈为止,即将指定索引的画布作为当前画布

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值