自定义View之绘图篇(六):Canvas那些你应该知道的变换

来我的怀里

或者

让我住进你的心里

                                            一仓央嘉措

一、什么是Canvas?

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

Canvas 类是用于绘图的,绘制图形,你需要4个基本元素:

  • 画在哪。画在Bitmap上。(相当于纸张,我们把图画在纸张上面)
  • 怎么画。(调用canvas执行绘图操作。比如canvas.drawCircle(),canvas.drawLine(),canvas.drawPath()将我们需要的图像画出来。)
  • 画的内容。(比如我想在纸张画一朵花,根据自己需求画圆,画直线,画路径等)
  • 用什么画。(在纸张上画一朵花,肯定是用笔来画的,这里的笔指的是 Paint)

Canvas 画布无限大,它并没有边界。怎么来理解这句话呢?打个比方:画布就是窗外的景色,而手机屏幕就是窗口,你在窗口看到窗外的景色是有限的。同样我也可以把图形画到屏幕之外,通过对 Canvas 的变换与操作,让屏幕之外的图形显示到屏幕里面。

二、Canvas 绘图

Canvas 绘制一些常见的图形:

        mPaint.setColor(Color.RED);
        //绘制直线
        canvas.drawLine(100,100,600,100,mPaint);

        //绘制矩形
        canvas.drawRect(100,200,600,400,mPaint);

        //绘制路劲
        mPaint.setTextSize(60);
        mPaint.setStrokeWidth(2);
        mPaint.setStyle(Paint.Style.FILL);
        canvas.drawText("我是一颗石头",100,500,mPaint);

三、Canvas 的变换与操作

有时候我们还需要对 Canvas 做一些操作,比如旋转,裁剪,平移等等。

  • canvas.translate 平移
  • canvas.rotate 旋转
  • canvas.scale 缩放
  • canvas.skew 错切
  • canvas.clipRect 裁剪
  • canvas.save和canvas.restore 保存和恢复
  • PorterDuffXfermode 图像混合 (paint相关方法)

哟也,我们来挨着看一看。

1、(平移)translate

canvas.translate() 方法是用来实现画布平移的,画布的原状是以左上角为原点,向右是X轴正方向,向下是Y轴正方向,我相信大家已经非常熟悉了。

方法预览:

translate(float dx, float dy)

参数含义:

float dx:水平方向平移的距离,正数指向正方向(向右)平移的量,负数指向负方向(向左)平移的量

flaot dy:垂直方向平移的距离,正数指向正方向(向下)平移的量,负数指向负方向(向上)平移的量

这段代码中,同一个圆形,在画布平移前画一次,平移后再画一次,大家会觉得结果会怎样?


        mPaint.setColor(Color.RED);
        canvas.drawCircle(200,200,200,mPaint);

        mPaint.setColor(Color.GREEN);
        canvas.translate(200,200);
        canvas.drawCircle(200,200,200,mPaint);

你觉得最终的效果图是不是2个圆重合:

实际效果是这样的:

蛋都碎了一地,为啥红色的框框没有移动呢?

引用启航大神的话说:


这是由于屏幕显示与Canvas根本不是一个概念!Canvas是一个很虚幻的概念,相当于一个透明图层(用过PS的同学应该都知道),每次Canvas画图时(即调用Draw系列函数),都会产生一个透明图层,然后在这个图层上画图,画完之后覆盖在屏幕上显示。

translate 函数其实实际相当于平移坐标系,即平移坐标系的原点的位置。


2、旋转(Rotate)

旋转画布,看起来和图片旋转效果有点类似。默认是围绕坐标系原点旋转,同理也可以设定中心点旋转。

方法预览:

rotate(float degrees)

rotate(float degrees, float px, float py)

第一个构造函数 degrees 参数表示旋转的度数。正数表示顺时针旋转,负数表示逆时针旋转,0 度表示水平X轴方向。默认以坐标原点作为中心点。

第二个构造函数 px , py 表示中心点坐标。

接着我们来看一个例子:

        mPaint.setColor(Color.RED);
        //未旋转的直线
        canvas.drawLine(200, 200, 600, 200,mPaint);
        //顺时针旋转30度
        canvas.rotate(30);
        mPaint.setColor(Color.GREEN);
        canvas.drawLine(200, 200, 600, 200,mPaint);

这里是以第一个构造函数为例的,首先绘制红色的不带旋转的直线,然后顺时针旋转画布30度,最后绘制旋转后的绿色直线。

注意:旋转画布和图片旋转之间的区别。


缩3、放(scale )

画布缩放,同样也有2个构造方法。方法预览:

scale(float sx, float sy)

scale(float sx, float sy, float px, float py)

第一个构造函数,参数 sx表示 水平方向的缩放比例,大于1为放大,小于1为缩小。比如 水平方向为100个px,缩放比例为1.5f,那么实际的像素为100*1.5=150px 。 参数 sy表示垂直方向的缩放比例。

第二个方法源码如下:

    /**
     * Preconcat the current matrix with the specified scale.
     *
     * @param sx The amount to scale in X
     * @param sy The amount to scale in Y
     * @param px The x-coord for the pivot point (unchanged by the scale)
     * @param py The y-coord for the pivot point (unchanged by the scale)
     */
    public final void scale(float sx, float sy, float px, float py) {
        translate(px, py);
        scale(sx, sy);
        translate(-px, -py);
    }

px 和 py 分别为缩放的基准点,从源码上可以非常清楚的看出和 scale(float sx , float sy)的差别:

translate(px, py);
scale(sx, sy);
translate(-px, -py);

先将画布平移px,py,然后scale,scale结束之后再将画布平移回原基准点。

具体我们来看看下面例子:

        canvas.save();
        mPaint.setColor(Color.RED);
        //未缩放的圆形
        canvas.drawCircle(200, 200, 100, mPaint);

        //画布缩放
        canvas.scale(1.5f, 1.5f);
        mPaint.setColor(Color.GREEN);
        canvas.drawCircle(200, 200, 100, mPaint);
        canvas.restore();

        canvas.scale(1.5f, 1.5f, 200, 200);
        mPaint.setColor(Color.YELLOW);
        canvas.drawCircle(200, 200, 100, mPaint);

save,restore后面会详解介绍,用于画布的保存和恢复。红色圆圈为未缩放画布,绿色为放大1.5倍的圆圈,黄色为先平移(200,200)再放大1.5倍再平移(-200,-200)的圆圈。


4、错切(skew)

画布扭曲的方法预览:

skew(float sx, float sy)

参数:

float sx:将画布在x方向上倾斜相应的角度,sx倾斜角度的tan值

float sy:将画布在y轴方向上倾斜相应的角度,sy为倾斜角度的tan值

下面的例子,我将矩形在 x 方向倾斜30度,tan30=1/√3 约等于 0.56。

        mPaint.setColor(Color.RED);
        //未旋转的圆形
        canvas.drawRect(100, 100, 500, 400, mPaint);

        canvas.skew(0.56f, 0f);
        mPaint.setColor(Color.GREEN);
        canvas.drawRect(100, 100, 500, 400, mPaint);

可以从效果图当中看出来,我们设置 x 方向倾斜,反而 y 方向倾斜了,这就是为什么要叫做错切了。你心中一定又会有疑问?每个点的坐标又是怎么计算的呢?那下面我们一起来分析下:

canvas.drawRect(100, 100, 500, 400, mPaint);

A(100,100),B(500,100),C(500,400),D(100,400)设置画布 x 方向倾斜0.56f,y 方向没有变化。

A 点横坐标倾斜后的值为 A点的横坐标+A点的横坐标*倾斜值

B 点横坐标倾斜后的值为 B点的横坐标+A点的横坐标*倾斜值

C 点横坐标倾斜后的值为 C点的横坐标+矩形宽度*倾斜值

D 点横坐标倾斜后的值为 D点的横坐标+矩形宽度*倾斜值


5、裁剪画布(clip系列函数)

裁剪画布是利用 Clip系列函数,通过与Rect、Path、Region取交、并、差等集合运算来获得最新的画布形状(默认取交集)。除了调用Save、Restore函数以外,这个操作是不可逆的,Canvas画布一但被裁剪,就不能再被恢复。

Clip系列函数方法预览:

  • clipRect(RectF rect, Op op)
  • clipRect(Rect rect, Op op)
  • clipRect(RectF rect)
  • clipRect(Rect rect)
  • clipRect(float left, float top, float right, float bottom, Op op)
  • clipRect(float left, float top, float right, float bottom)
  • clipRect(int left, int top, int right, int bottom)
  • clipPath(Path path, Op op)
  • clipPath(Path path)
  • clipRegion(Region region, Op op)
  • clipRegion(Region region)

裁剪的函数比较多哈,使用难度都不是很大。来看一个简单的例子:

 canvas.drawColor(Color.GREEN);
 canvas.clipRect(new Rect(200, 200, 500, 400), Region.Op.DIFFERENCE);
 canvas.drawColor(Color.YELLOW);

先把画布背景涂成绿色,然后裁剪矩形,这里去的是差集(Region.Op.DIFFERENCE),用过 ps 的同学就知道取的所选区域的反向。

效果图:


6、保存和恢复(save()、restore())

对画布的平移,旋转,缩放,错切,裁剪都是不可逆的操作,如果我们还需要返回到原始状态对画布进行操作怎么办呢?细心的同学肯定知道 save(),restore() 方法在上文已经出现过了,对的,它就是用来对画布状态的保存与恢复,这样我们就可以愉快的进行可逆操作了。

方法预览:

save():每次调用 save() 函数,都会把当前的画布的状态进行保存,然后放入特定的栈中;

restore():每当调用 restore() 函数,就会把栈中最顶层的画布状态取出来,并按照这个状态恢复当前的画布,并在这个画布上做画。

来看一个简单的例子,首先把整个画布涂成绿色,然后保存画布,接着裁剪矩形,给裁剪后的画布涂上黄色,然后恢复画布,给画布涂上红色:

        canvas.drawColor(Color.GREEN);

        //保存当前画布
        canvas.save();

        canvas.clipRect(new Rect(200, 200, 500, 400));

        canvas.drawColor(Color.YELLOW);

        //恢复画布
        canvas.restore();

        canvas.drawColor(Color.RED);

阶段图如下,最终为全屏红色:

注意:在使用canvas.save和canvas.restore时最好配对使用,若restore( )的调用次数比save( )多可能会造成异常。


7、图像混合 (PorterDuffXfermode)

PorterDuffXfermode 图像混合的构造函数如下:

public PorterDuffXfermode(PorterDuff.Mode mode)  

参数PorterDuff.Mode表示混合模式,枚举值有18个。

自定义View之绘图篇(五):圆形水波,已经对图形混合有了初步的介绍,我这里就不再重复写了。

我们在项目开发中,经常会用到这样的功能:显示圆角图片。 具体来看看它是怎么实现的:

效果图:

    /**
     * @param bitmap 原图
     * @param pixels 圆角大小
     * @return
     */
    public Bitmap getRoundCornerBitmap(Bitmap bitmap, float pixels) {
        //获取bitmap的宽高
        int width = bitmap.getWidth();
        int height = bitmap.getHeight();

        Bitmap cornerBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        Paint paint = new Paint();
        Canvas canvas = new Canvas(cornerBitmap);
        paint.setAntiAlias(true);

        canvas.drawRoundRect(new RectF(0, 0, width, height), pixels, pixels, paint);
        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
        canvas.drawBitmap(bitmap, null, new RectF(0, 0, width, height), paint);

        //绘制边框
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(6);
        paint.setColor(Color.GREEN);
        canvas.drawRoundRect(new RectF(0, 0, width, height), pixels, pixels, paint);

        return cornerBitmap;
    }

1、首先通过Bitmap cornerBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);生成cornerBitmap 实例,注意了Bitmap 只能通过静态方法来获取它的实例,并不能直接 new出来。

2、绘制圆角矩形canvas.drawRoundRect(new RectF(0, 0, width, height), pixels, pixels, paint);

3、为Paint设置PorterDuffXfermode。参数 PorterDuff.Mode.SRC_IN 取交集。

4、绘制原图。canvas.drawBitmap(bitmap, null, new RectF(0, 0, width, height), paint);

5、绘制边框圆角。 canvas.drawRoundRect(new RectF(0, 0, width, height), pixels, pixels, paint);

简单的13行代码就实现了圆角带边框的效果。

Canvas 相关的知识,就讲到这里了,如果本文有帮到你,记得加关注哦

欢迎关注github

时间: 2024-12-19 15:00:54

自定义View之绘图篇(六):Canvas那些你应该知道的变换的相关文章

自定义View之绘图篇(四):baseLine和FontMetrics

乐观是一首激昂优美的进行曲,时刻鼓舞着你向事业的大路勇猛前进.--大仲马 相关文章: 自定义View之绘图篇(一):基础图形的绘制 自定义View之绘图篇(二):路径(Path) 自定义View之绘图篇(三):文字(Text) 了解baseLine和FontMetrics有助于我们理解drawText()绘制文字的原理,下面我们一起来看看呗. 一.baseLine 基线 记得小时候练习字母用的是四线格本,把字母写在四线格内,如下: 那么在canvas中drawText绘制文字时候,也是有规则的,

自定义View之绘图篇(三):文字(Text)

顺境也好,逆境也好,人生就是一场对种种困难无尽无休的斗争,一场以寡敌众的战斗.--泰戈尔 相关文章: 自定义View之绘图篇(一):基础图形的绘制 自定义View之绘图篇(二):路径(Path) 一.文字 相关方法预览: //普通设置 paint.setAntiAlias(true); //指定是否使用抗锯齿功能 如果使用会使绘图速度变慢 默认false setStyle(Paint.Style.FILL);//绘图样式 对于设文字和几何图形都有效 setTextAlign(Align.LEFT

自定义View之绘图篇(一):基础图形的绘制

生活是一面镜子,你对它笑,它就对你笑:你对它哭,它也对你哭.--萨克雷 在正文开始之前,我先抛一个脑洞大开的题目给大家:商人以45元一双进购了2双鞋子,然后亏本30一双出售.某个顾客给了他100买了2双鞋子,商人没零钱找于是拿着这100找邻居换了100的零钱,后来邻居发现这100是假的,商人只得陪了邻居100真的... 请问商人亏了多少?? 相关文章: Android自定义View之Path解析 一.Paint与Canvas 绘图需要两个工具,笔和纸.这里的 Paint相当于笔,而 Canvas

自定义View(二),强大的Canvas

本文转自:http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2012/1212/703.html Android中使用图形处理引擎,2D部分是android SDK内部自己提供,3D部分是用Open GL ES 1.0.今天我们主要要了解的是2D相关的,如果你想看3D的话那么可以跳过这篇文章. 大部分2D使用的api都在android.graphics和android.graphics.drawable包中.他们提供了图形处理相关的: C

Android自定义view(初级篇)

Q1:为什么要自定义view? A:由于很多系统自带的view满足不了当前设计需求或者为了达到更良好的用户体验,增加UI的美化效果,就需要自定view Q2:自定义view有那几个步骤? A:>用户可根据需要extends View这个父类,然后重写父类的方法:如:onDraw();onMeasure()等: >如果用户在自定义View事需要添加属性,则必须在values文件夹下新建"attrs.xml"文件,在其中添加自定义属性. 下面来进行自定义view的学习. 一.最

自定义View之案列篇(三):仿QQ小红点

光棍节快到了,提前祝愿广大的单身猿猴,早日脱单,尽快找到另一半. 一直觉得 QQ 的小红点非常具有创新,新颖.要是自己也能实现类似的效果,那怎一个爽字了得. 先来看看它的最终效果: 效果图具有哪些效果: 在拉伸范围内的拉伸效果 未拉出拉伸范围释放后的效果 拉出拉伸范围再拉回的释放后的效果 拉出拉伸范围释放后的爆炸效果 涉及的相关知识点: onLayout 视图位置 saveLayer 图层相关知识 Path 的贝赛尔曲线 手势监听 ValueAnimator 属性动画 一.拉伸效果 我们先来讲解

android 自定义View【2】对话框取色&色盘取色的实现

android 自定义View[2]对话框取色&色盘取色的实现    上一篇文章基本介绍了android自定义view的流程:继承view,复写view的一些方法.实现简单的自定义view.这篇文章主要介绍的是系统对话框取色功能,然后顺便介绍升级版,色盘取色[类似于ps中的吸管,对图片点击相应位置,获取那个位置的颜色]. 一.概述:通过该例子了解以下内容: 1.进一步了解android 自定义view. 2.知道如何获取图片上的颜色值. 3.监听屏幕touch,实现移动的时候自动取色.[onDr

Android自定义View的三种实现方式

在毕设项目中多处用到自定义控件,一直打算总结一下自定义控件的实现方式,今天就来总结一下吧.在此之前学习了郭霖大神博客上面关于自定义View的几篇博文,感觉受益良多,本文中就参考了其中的一些内容. 总结来说,自定义控件的实现有三种方式,分别是:组合控件.自绘控件和继承控件.下面将分别对这三种方式进行介绍. (一)组合控件 组合控件,顾名思义就是将一些小的控件组合起来形成一个新的控件,这些小的控件多是系统自带的控件.比如很多应用中普遍使用的标题栏控件,其实用的就是组合控件,那么下面将通过实现一个简单

Android进阶之自定义View实战(二)九宫格手势解锁实现

一.引言 在上篇博客Android进阶之自定义View实战(一)仿iOS UISwitch控件实现中我们主要介绍了自定义View的最基本的实现方法.作为自定义View的入门篇,仅仅介绍了Canvas的基本使用方法,而对用户交互层面仅仅处理了单击事件接口,在实际的业务中,常常涉及到手势操作,本篇博客以九宫格手势解锁View为例,来说明自定义View如何根据需求处理用户的手势操作.虽然九宫格手势解锁自定义View网上资料有很多,实现原理大同小异,但这里我只是根据自己觉得最优的思路来实现它,目的是让更