Canvas的变换

来源:互联网 发布:windows api大全 编辑:程序博客网 时间:2024/06/08 04:31

Canvas变换

之前看了Canvas的一些基本用法,由于时间问题有些东西没有总结出来,今天继续Canvas变换相关笔记的总结!


一、变换主要有四种,name我们就从下面的几个方面入手

  • Canvas的区域、以及坐标原点
  • Canvas的平移(translate)
  • Canvas的旋转(rotate)
  • Canvas的缩放(scale)
  • Canvas的错切(skew)

二、Canvas概述

首先我们需要建立一个意识,就是Canvas是无边界的,不要觉得我们给View设置了大小就认为Canvas的默认大小也跟view的大小一样,其实很好理解,当我们在Canvas上进行绘制的时候(比如说绘制一个点),我们的坐标时候可以填写负值得,这时如果我们没有对Canvas做过任何转换操作的话我们是看不到那个点的,但是,点是存在的,只是在可见区域外面而已。其次就是,Canvas默认的坐标原点是跟View的坐标原点是重合的。看下图: 



三、变换的几个关键方法 

1、translate(),这个方法只有两个参数,一个是坐标系原点的x坐标,一个是坐标系原点的y坐标,当调用这个方法后,后面的所有canvas的操作都以最新的坐标系为标准,下面我们在默认状态下,还有将坐标系到View的中心状态下画圆看看会有什么不同的效果。 

@RequiresApi(api = Build.VERSION_CODES.KITKAT)@Overrideprotected void onDraw(Canvas canvas) {    super.onDraw(canvas);    //设置画布背景颜色    canvas.drawColor(Color.YELLOW);    //创建画笔    Paint paint = new Paint();    paint.setColor(Color.RED);    paint.setStyle(Paint.Style.FILL);    paint.setStrokeWidth(10.0f);    paint.setAntiAlias(true);    /**    * 在默认坐标系绘制矩形    */    canvas.drawRect(0,0,100,100,paint);    //将坐标系的原点移动到控件的中心    canvas.translate(getMeasuredWidth() / 2, getMeasuredHeight() / 2);    /**    * 在新坐标系绘制矩形    */    canvas.drawRect(0,0,100,100,paint);}



2、rotate(),旋转,这个方法应该是写自定义控件用的比较多的一个,特别是在绘制多样化的圆时,就比如我们要绘制一个圆形时钟,那么上面的刻度都是平均分配的,都是每1度就一个刻度,这个时候如果我们每绘制一个刻度都去计算一次刻度的坐标,那就相当麻烦了,相比之我们每绘制一个刻度就按照一定的比例旋转一下画布,这样就省事多了!那么下面就简单的画一个时钟的刻度瞅瞅! 

@RequiresApi(api = Build.VERSION_CODES.KITKAT)@Overrideprotected void onDraw(Canvas canvas) {    super.onDraw(canvas);    //设置画布背景颜色    canvas.drawColor(Color.YELLOW);    /**     * 要注意的是,这些笔的初始化这里只是为了减少篇幅,所以在onDraw中初始化,正确的做法应该是在控件的构造方法中     */    //绘制时钟外圈跟(3、6、9、12)这几个整点的刻度的笔    Paint pc = new Paint();    pc.setColor(Color.RED);    pc.setStyle(Paint.Style.STROKE);    pc.setStrokeWidth(5.0f);    pc.setAntiAlias(true);    //绘制小时刻度    Paint ph = new Paint();    ph.setColor(Color.RED);    ph.setStyle(Paint.Style.STROKE);    ph.setStrokeWidth(3.0f);    ph.setAntiAlias(true);    //绘制分钟刻度的笔    Paint pm = new Paint();    pm.setColor(Color.RED);    pm.setStyle(Paint.Style.STROKE);    pm.setStrokeWidth(1.0f);    pm.setAntiAlias(true);    //创建画文字专用画笔    TextPaint pt = new TextPaint();    pt.setColor(Color.RED);    pt.setStyle(Paint.Style.FILL);    pt.setAntiAlias(true);    pt.setTextSize(24);    //获取view宽高    float width = getMeasuredWidth();    float height = getMeasuredHeight();    //移动坐标系到view中心    canvas.translate(width / 2 + 0.5f, height / 2 + 0.5f);    //以较小的为标准    float radius = width > height ? height / 2 - 50.0f + 0.5f : width / 2 - 50.0f + 0.5f;    //画外圈    canvas.drawCircle(0, 0, radius, pc);    //中心点    canvas.drawCircle(0, 0, 20, pc);    //时钟一圈有60分,所以canvas需要旋转60次    int m = 60;    //记录当前处于哪个刻度    int count = 0;    //每个小时的文字    int time = 0;    for (int i = 0; i < m; i++) {        //绘制12、3、6、9四个刻度        if (count % 15 == 0) {            canvas.drawLine(0, -radius + 2.5f, 0, -radius + 22.5f, pc);            if (count == 0) {                //绘制文字这里正常来说需要获得文字区域的宽度,才能确定文字的会之后坐标的(我偷懒就估摸着写了一些值进去)                canvas.drawText("12", -12, -radius + 50.0f, pt);            } else {                canvas.drawText(String.valueOf(time), -12, -radius + 50.0f, pt);            }            canvas.rotate(6);            count += 1;            time += 1;            continue;        }        //绘制整点刻度        if (count % 5 == 0) {            canvas.drawLine(0, -radius + 2.5f, 0, -radius + 12.5f, ph);            canvas.drawText(String.valueOf(time), -12, -radius + 50.0f, pt);            canvas.rotate(6);            count += 1;            time += 1;            continue;        }        //绘制分钟刻度        canvas.drawLine(0, -radius + 2.5f, 0, -radius + 6.5f, pm);        //旋转画布(每次旋转6度),注意默认旋转的方向是逆时针的,默认旋转中心是坐标系原点,rotate()还有一个重载方法,可以根据参数指定旋转中心        canvas.rotate(6);        count++;    }}

最终效果图如下,至于几根针就懒的弄上去了,无非是坐标的计算问题,难度不大,就是繁琐一些而已: 


3、scale(),缩放的方法是我觉得最不好理解的,因为当我们缩放之前画一个区域A,然后进行缩放再画一个区域B,起初我还以为A也会被缩放,因为A是被绘制在Canvas上的,既然我们对canvas进行了缩放,那么应该所有的元素都会被影响,可是代码写完后发现受影响的只有B,而A还是原来的鬼样子,看代码! 


@RequiresApi(api = Build.VERSION_CODES.KITKAT)@Overrideprotected void onDraw(Canvas canvas) {    super.onDraw(canvas);    //设置画布背景颜色    canvas.drawColor(Color.YELLOW);    Paint pc = new Paint();    pc.setColor(Color.RED);    pc.setStyle(Paint.Style.FILL);    pc.setAntiAlias(true);    //先画一个红色小块    canvas.drawRect(50,50,200,200, pc);    /**    * 参数1、x轴方向缩放(0-1之间为缩小,大于1为放大)    * 参数2、y轴方向缩放(0-1之间为缩小,大于1为放大)    * 参数3、缩放中心点的x坐标    * 参数4、缩放中心点的y坐标    */    canvas.scale(0.5f,0.5f,0,0);    pc.setColor(Color.BLUE);    //进行缩放后再画个蓝色小块    canvas.drawRect(50,50,200,200,pc);}

面代码比较简单,就不多说了,记住几个参数的意义就可以,为了更好的理解缩放中心点对整个缩放效果的影响,我把上面的缩放中心的改成了50,50,看看两张图的效果对比下: 

/*** 参数1、x轴方向缩放(0-1之间为缩小,大于1为放大)* 参数2、y轴方向缩放(0-1之间为缩小,大于1为放大)* 参数3、缩放中心点的x坐标* 参数4、缩放中心点的y坐标*/canvas.scale(0.5f,0.5f,50,50);



4、接下来看下错切,说实在话,要做出很炫的效果,特别是图片的效果,这个东西经常用到,而且在矩阵部分也用得非常多,但是有点难理解,那么我们先写个例子出来看看效果怎么样,再慢慢分析吧! 


mPaint.setColor(Color.RED);mPaint.setStyle(Paint.Style.FILL);mPaint.setAntiAlias(true);RectF rectF = new RectF(0, 0, 300, 300);canvas.drawRect(rectF,mPaint);/*** 参数1、水平方向的错切因子* 参数2、垂直方向的错切因子*/canvas.skew(1f,0);mPaint.setColor(Color.BLUE);canvas.drawRect(rectF,mPaint);

下面第一张图是对x方向进行错切,第二张图时候对y方向进行错切 




由图中我们可以看出,由于我们只对x轴进行的错切,所以图形是在x轴方向上变化了,在y轴方向上还是在原来的范围内的,我之前看了一些网上的资料,看到了一个公式:
x轴错切:原始坐标(x,y)=》错切后坐标(x+ay,y)
y轴错切:原始坐标(x,y)=》错切后坐标(x,y+ax)

上面的公式,适用于所有的点,而那个a就是我们在代码中设置的错切因子,也就是canvas(a,a),那么现在的主要问题就是,这个a时候怎么来的?下面我们再看一张图:




看看这张图,当我们的的错切因子为1的时候,蓝色框框的一条边刚好跟正方形的对角线重合,也就是说角a=45°,那么当错切因子不断变大的时候,角a也是不断增大的,这跟高中学的三角函数的正切老像了,其实就是了,也就是tana,可以多填些角度进去进行验证!既然知道错切因子就是偏移角度的正切值,那么以后想要得到对应偏移量的错切因子也是so easy了,y方向也是同样道理的!!