Canvas和Path有关的方法总结

来源:互联网 发布:汽车有必要贴膜吗 知乎 编辑:程序博客网 时间:2024/06/02 18:19

Canvas之translate、scale、rotate、skew方法讲解!

转自: http://blog.csdn.net/tianjian4592


Canvas大致可以分为三类:

1. save、restore 等与层的保存和回滚相关的方法;

2. scale、rotate、clipXXX 等对画布进行操作的方法;

3. drawXXX 等一系列绘画相关的方法;


先让我们明确两个基本概念:

1.Canvas 的左上角是(0,0);

2.基于左上角往右 X 为正,往下 Y 为正,反之为负;


一、canvas.translate() - 画布的平移:

首先咱们在画布上画一个400 X 400 红色的矩形

[html] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. canvas.drawRect(new Rect(0, 0, 400, 400), mPaint);  

此时整个画布的左上角出现了一个红色的矩形(为了更清楚,蓝色打个底)该矩形大小为400 X 400 ,效果如下:


接下来我们canvas.translate( )玩玩

[html] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. @Override  
  2. protected void onDraw(Canvas canvas) {  
  3.     super.onDraw(canvas);  
  4.     canvas.drawColor(Color.BLUE);  
  5.     canvas.translate(100, 100);  
  6.     canvas.drawRect(new Rect(0, 0, 400, 400), mPaint);  
  7. }  

看下效果:

此时可以看到,虽然是绘制同样的矩形,但矩形在画布上的位置已经向右和向下各移动了100px;

既然如此,这个时候如果我们再将canvas 平移(translate)(100,100),再绘制一个同样的矩形会出现什么情况呢?会与之前的矩形重叠吗?咱们拭目以待:

[html] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. @Override  
  2. protected void onDraw(Canvas canvas) {  
  3.     super.onDraw(canvas);  
  4.     canvas.drawColor(Color.BLUE);  
  5.     canvas.translate(100, 100);  
  6.     canvas.drawRect(new Rect(0, 0, 400, 400), mPaint);  
  7.     canvas.translate(100, 100);  
  8.     canvas.drawRect(new Rect(0, 0, 400, 400), mPaint);  
  9. }  


从效果上看,两次translate 进行了叠加,绘制第二个矩形的时候画布已经偏移了(200,200);

好了,了解到这里,咱们利用canvas.translate( )一起来做个小栗子,绘制一个生活中比较常用的刻度尺;

咱们先从网上找个用于参考的刻度尺图片:

从图上看,刻度尺的元素有:外框、刻度线(不同的数值刻度线长短不一)、数字

所以我们所要做的就是对上面的元素在onDraw里分别绘制:

[html] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. @Override  
  2. protected void onDraw(Canvas canvas) {  
  3.     super.onDraw(canvas);  
  4.     // 绘制外框  
  5.     drawOuter(canvas);  
  6.     // 绘制刻度线  
  7.     drawLines(canvas);  
  8.     // 绘制数字  
  9.     drawNumbers(canvas);  
  10. }  
咱们先简单分析一下,刻度尺有个外框,外框距离左右都有一定的边距,第一根和最后一根刻度线距离边框也有一定的边距,其余刻度线之间距离相同,另外一些特殊的刻度线长短不一;

有了上面的分析,咱们一个一个来,先绘制外框,外框也就是一个矩形,只需要确定边框的位置和大小,然后使用canvas.drawRect( )绘制即可:

咱们先定义几个需要的数据,为了屏幕适配,数据均为dp:

[html] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. // 刻度尺高度  
  2. private static final int DIVIDING_RULE_HEIGHT = 70;  
  3. // 距离左右间  
  4. private static final int DIVIDING_RULE_MARGIN_LEFT_RIGHT = 10;  
  5.   
  6. // 第一条线距离边框距离  
  7. private static final int FIRST_LINE_MARGIN = 5;  
  8. // 打算绘制的厘米数  
  9. private static final int DEFAULT_COUNT = 9;  
然后将以上数据转为对应像素值:

[html] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. private void initData() {  
  2.     mDividRuleHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,  
  3.             DIVIDING_RULE_HEIGHT, mResources.getDisplayMetrics());  
  4.     mHalfRuleHeight = mDividRuleHeight / 2;  
  5.   
  6.     mDividRuleLeftMargin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,  
  7.             DIVIDING_RULE_MARGIN_LEFT_RIGHT, mResources.getDisplayMetrics());  
  8.     mFirstLineMargin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,  
  9.             FIRST_LINE_MARGIN, mResources.getDisplayMetrics());  
  10.   
  11. }  
有了以上数据,则可以确定外边框的Rect为:

[html] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. mOutRect = new Rect(mDividRuleLeftMargin, top, mTotalWidth - mDividRuleLeftMargin,  
  2.                 mRuleBottom);  

接下来看刻度线的绘制,根据厘米可以计算出中间的格数,根据厘米占用屏幕宽度和所占格数可以计算出每一格所占屏幕宽度:

[html] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. mLineInterval = (mTotalWidth - 2 * mDividRuleLeftMargin - 2 * mFirstLineMargin)  
  2.                 / (DEFAULT_COUNT * 10 - 1);  

有了每一格所占宽度,我们只需要在绘制刻度线的时候不断将画布右移对应宽度即可:

[html] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. /**  
  2.  * 绘制刻度线  
  3.  * @param canvas  
  4.  */  
  5. private void drawLines(Canvas canvas) {  
  6.     canvas.save();  
  7.     canvas.translate(mLineStartX, 0);  
  8.     int top = mMaxLineTop;  
  9.     for (int i = 0; i <= DEFAULT_COUNT * 10; i++) {  
  10.         if (i % 10 == 0) {  
  11.             top = mMaxLineTop;  
  12.         } else if (i % 5 == 0) {  
  13.             top = mMiddleLineTop;  
  14.         } else {  
  15.             top = mMinLineTop;  
  16.         }  
  17.   
  18.         canvas.drawLine(0, mRuleBottom, 0, top, mLinePaint);  
  19.         canvas.translate(mLineInterval, 0);  
  20.   
  21.     }  
  22.     canvas.restore();  
  23.   
  24. }  
由于刻度尺上分三种长短的刻度线,我们也做对应处理,10的整数倍的刻度线最长,5的整数倍的刻度线中等长度,其余较短;

此时绘制出的刻度尺效果为:


此时刻度尺的基本样子就出来了,对应文字大家有兴趣可以自己加上;

俗话说,条条大路通罗马,我们除了使用canvas.translate ,还能不能使用别的方式进行实现呢,答案当然是可以,比如在绘制的时候根据for循环里的 i 值也可以直接计算出每一根刻度线的位置,然后直接进行绘制,相比之下,这两种方式的优劣大家也可以自行比较一下,好了,canvas.translate() 就说这么多;


二、canvas.scale( ) - 画布的缩放:

关于scale,Android 提供了以下两个接口:

[html] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. /**  
  2.  * Preconcat the current matrix with the specified scale.  
  3.  *  
  4.  * @param sx The amount to scale in X  
  5.  * @param sy The amount to scale in Y  
  6.  */  
  7. public native void scale(float sx, float sy);  
  8.   
  9. /**  
  10.  * Preconcat the current matrix with the specified scale.  
  11.  *  
  12.  * @param sx The amount to scale in X  
  13.  * @param sy The amount to scale in Y  
  14.  * @param px The x-coord for the pivot point (unchanged by the scale)  
  15.  * @param py The y-coord for the pivot point (unchanged by the scale)  
  16.  */  
  17. public final void scale(float sx, float sy, float px, float py) {  
  18.     translate(px, py);  
  19.     scale(sx, sy);  
  20.     translate(-px, -py);  
  21. }  
我们先看下scale(float sx , float sy),我们还是以上面的正方形作为栗子,调用canvas.scale(float sx , float sy)之后看下效果;

[html] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. @Override  
  2. protected void onDraw(Canvas canvas) {  
  3.     super.onDraw(canvas);  
  4.     canvas.drawColor(Color.BLUE);  
  5.     canvas.drawRect(new Rect(0, 0, 400, 400), mPaint);  
  6.     canvas.scale(0.5f, 0.5f);  
  7.     mPaint.setColor(Color.YELLOW);  
  8.     canvas.drawRect(new Rect(0, 0, 400, 400), mPaint);  
  9. }  
我们将画布在x,y方向上均缩放为 0.5 倍,使用默认基准点(原点 0,0),效果如下:


效果就相当于用个钉子钉在(0,0)处,然后把矩形的x,y缩放为一半,我们再来看看第二个接口scale(float sx , float sy, float px,float py):

前两个参数为将画布在x、y方向上缩放的倍数,而px和py 分别为缩放的基准点,从源码上可以非常清楚的看出和scale(float sx , float sy)的差别:

[html] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. translate(px, py);  
  2. scale(sx, sy);  
  3. translate(-px, -py);  
即先将画布平移px,py,然后scale,scale结束之后再将画布平移回原基准点;

我们再在之前的基础上绘制一个同样的矩形,x , y 均缩放为 0.5 倍,缩放中心为矩形的中心:

[html] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. @Override  
  2. protected void onDraw(Canvas canvas) {  
  3.     super.onDraw(canvas);  
  4.     canvas.drawColor(Color.BLUE);  
  5.     canvas.drawRect(new Rect(0, 0, 400, 400), mPaint);  
  6.   
  7.     // 保存画布状态  
  8.     canvas.save();  
  9.     canvas.scale(0.5f, 0.5f);  
  10.     mPaint.setColor(Color.YELLOW);  
  11.     canvas.drawRect(new Rect(0, 0, 400, 400), mPaint);  
  12.     // 画布状态回滚  
  13.     canvas.restore();  
  14.   
  15.     canvas.scale(0.5f, 0.5f, 200, 200);  
  16.     mPaint.setColor(Color.BLACK);  
  17.     canvas.drawRect(new Rect(0, 0, 400, 400), mPaint);  
  18. }  

一起来看下效果:


效果就相当于用个钉子钉在矩形的中心,然后进行缩放;

根据上面android 的实现,我们其实可以使用以下代码实现同样的效果:

[html] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. // 先将画布平移到矩形的中心  
  2. canvas.translate(200, 200);  
  3. // 将画布进行缩放  
  4. canvas.scale(0.5f, 0.5f);  
  5. // 将画布移回原基准点  
  6. canvas.translate(-200, -200);  
  7. mPaint.setColor(Color.BLACK);  
  8. canvas.drawRect(new Rect(0, 0, 400, 400), mPaint);  

到此为止,我们也就了解了对画布的缩放,基于canvas.scale(),我们一起完成一个小例子:


上面是网络上找的一张让人产生视觉误差的静态图,我们模拟绘制出上面的效果;

思路非常的简单:

1. 绘制一个和屏幕等宽的正方形;

2. 将画布以正方形中心为基准点进行缩放;

3. 在缩放的过程中绘制原正方形;

注:每次绘制都得使用canvas.save()  和 canvas.restore()进行画布的锁定和回滚,以免除对后面绘制的影响(后面会单独讲)

先初始化画笔,注意此时画笔需要设置成空心:

[html] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. /**  
  2.  * 初始化画笔  
  3.  */  
  4. private void initPaint() {  
  5.     mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);  
  6.     // 将画笔设置为空心  
  7.     mPaint.setStyle(Style.STROKE);  
  8.     // 设置画笔颜色  
  9.     mPaint.setColor(Color.BLACK);  
  10.     // 设置画笔宽度  
  11.     mPaint.setStrokeWidth(mLineWidth);  
  12. }  
然后循环的将画布缩放的同时绘制原正方形:

[html] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. /**  
  2.  * 绘制正方形  
  3.  *   
  4.  * @param canvas  
  5.  */  
  6. private void drawSquare(Canvas canvas) {  
  7.     for (int i = 0; i < TOTAL_SQUARE_COUNT; i++) {  
  8.         // 保存画布  
  9.         canvas.save();  
  10.         float fraction = (float) i / TOTAL_SQUARE_COUNT;  
  11.         // 将画布以正方形中心进行缩放  
  12.         canvas.scale(fraction, fraction, mHalfWidth, mHalfHeight);  
  13.         canvas.drawRect(mSquareRect, mPaint);  
  14.         // 画布回滚  
  15.         canvas.restore();  
  16.     }  
  17. }  
一起来看下绘制的效果:

其实最终效果和网上找的还是有点小差别的,由于画布的缩放,越小的时候画笔宽度越细,而原图是所有的都一样宽度,但似乎画笔宽度缩放之后效果更佳,哈哈 ... ... 


三、canvas.rotate( ) - 画布的旋转:

canvas.rotate( )和canvas.scale()可以类比起来看,如果理解了canvas.scale( ),那么canvas.rotate( )将会非常简单实用;

简单来讲,canvas.rotate( )即是将画布进行旋转,和canvas.scale( )类似的是,它也有两个可以使用的方法:

[html] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. /**  
  2.  * Preconcat the current matrix with the specified rotation.  
  3.  *  
  4.  * @param degrees The amount to rotate, in degrees  
  5.  */  
  6. public native void rotate(float degrees);  
  7.   
  8. /**  
  9.  * Preconcat the current matrix with the specified rotation.  
  10.  *  
  11.  * @param degrees The amount to rotate, in degrees  
  12.  * @param px The x-coord for the pivot point (unchanged by the rotation)  
  13.  * @param py The y-coord for the pivot point (unchanged by the rotation)  
  14.  */  
  15. public final void rotate(float degrees, float px, float py) {  
  16.     translate(px, py);  
  17.     rotate(degrees);  
  18.     translate(-px, -py);  
  19. }  

两个方法的区别也是在于基准点的选取,默认是以原点作为基准点,另一个则是以传入的x,y 作为基准点,是不是和scale 一模一样,咱们一起来rotate一下:

咱们先转转左上角的矩形,转多少度呢?先来个90度玩玩吧;

[html] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. @Override  
  2. protected void onDraw(Canvas canvas) {  
  3.     super.onDraw(canvas);  
  4.     canvas.drawColor(Color.BLUE);  
  5.     canvas.drawRect(new Rect(0, 0, 400, 400), mPaint);  
  6.     mPaint.setColor(Color.YELLOW);  
  7.     canvas.rotate(90);  
  8.     canvas.drawRect(new Rect(0, 0, 400, 400), mPaint);  
  9. }  
我们的预期是屏幕上有个旋转了的骚黄色矩形,一起来看看;


擦,黄色的矩形呢?

由于基准点是原点,我们直接旋转了90 度,所以已经将矩形旋转出屏幕,当然看不到了,我们将角度调小一点,改为45 度:

[html] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. @Override  
  2. protected void onDraw(Canvas canvas) {  
  3.     super.onDraw(canvas);  
  4.     canvas.drawColor(Color.BLUE);  
  5.     canvas.drawRect(new Rect(0, 0, 400, 400), mPaint);  
  6.     mPaint.setColor(Color.YELLOW);  
  7.     canvas.rotate(45);  
  8.     canvas.drawRect(new Rect(0, 0, 400, 400), mPaint);  
  9. }  

此时我们可以可以清楚的看到黄色的矩形是红色矩形绕原点(0,0)旋转45度之后的结果;


我们再将旋转基准点改为矩形中心看看:

[html] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. canvas.rotate(45,200,200);  

可以看到现在黄色矩形是红色矩形绕着中心旋转后的结果:


到这里,我们已经了解了canvas.rotate(float degrees)和 canvas.rotate(float degrees,float px , float py)的使用,同样也应该清楚后者的实现如下:

[html] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. translate(px, py);  
  2. rotate(degrees);  
  3. translate(-px, -py);  

好了,我们再利用canvas.rotate()完成个闹钟表盘的小例子:

闹钟表盘其实和刻度尺类似,只是一个是在一条直线上绘制,一个是在一个圆周上绘制,说到底都是确定一个位置绘制刻度线;

既然是圆周,最简单的方式莫过于在闹钟的12点钟处划线,通过canvas的旋转绘制到对应圆周处,我们一起实现一下:

整个圆周是360 度,每隔 30 度为一个整时间刻度,整刻度与刻度之间有四个短刻度,划分出5个小段,每个段为6度,有了这些分析,我们则可以采用如下代码进行绘制:

[html] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. /**  
  2.  * 绘制刻度  
  3.  *   
  4.  * @param canvas  
  5.  */  
  6. private void drawLines(Canvas canvas) {  
  7.     for (int i = 0; i <= 360; i++) {  
  8.         if (i % 30 == 0) {  
  9.             mLineBottom = mLineTop + mLongLineHeight;  
  10.             mLinePaint.setStrokeWidth(mLineWidth);  
  11.         } else {  
  12.             mLineBottom = mLineTop + mShortLineHeight;  
  13.             mLinePaint.setStrokeWidth(mHalfLineWidth);  
  14.         }  
  15.   
  16.         if (i % 6 == 0) {  
  17.             canvas.save();  
  18.             canvas.rotate(i, mHalfWidth, mHalfHeight);  
  19.             canvas.drawLine(mLineLeft, mLineTop, mLineLeft, mLineBottom, mLinePaint);  
  20.             canvas.restore();  
  21.         }  
  22.     }  
  23. }  

此时效果如下:


整体代码如下:

[html] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. /**  
  2.  * 闹钟表盘  
  3.  *   
  4.  * @author AJian  
  5.  */  
  6. public class RotateClockView extends View {  
  7.   
  8.     private static final int LONG_LINE_HEIGHT = 35;  
  9.     private static final int SHORT_LINE_HEIGHT = 25;  
  10.     private Paint mCirclePaint, mLinePaint;  
  11.     private DrawFilter mDrawFilter;  
  12.     private int mHalfWidth, mHalfHeight;  
  13.   
  14.     // 圆环线宽度  
  15.     private int mCircleLineWidth, mHalfCircleLineWidth;  
  16.     // 直线刻度线宽度  
  17.     private int mLineWidth, mHalfLineWidth;  
  18.     // 长线长度  
  19.     private int mLongLineHeight;  
  20.     // 短线长度  
  21.     private int mShortLineHeight;  
  22.     // 刻度线的左、上位置  
  23.     private int mLineLeft, mLineTop;  
  24.   
  25.     // 刻度线的下边位置  
  26.     private int mLineBottom;  
  27.     // 用于控制刻度线位置  
  28.     private int mFixLineHeight;  
  29.   
  30.     public RotateClockView(Context context) {  
  31.         super(context);  
  32.         mDrawFilter = new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG  
  33.                 | Paint.FILTER_BITMAP_FLAG);  
  34.   
  35.         mCircleLineWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8,  
  36.                 getResources().getDisplayMetrics());  
  37.         mHalfCircleLineWidth = mCircleLineWidth;  
  38.         mLineWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 4,  
  39.                 getResources().getDisplayMetrics());  
  40.         mHalfLineWidth = mLineWidth / 2;  
  41.   
  42.         mFixLineHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 4,  
  43.                 getResources().getDisplayMetrics());  
  44.   
  45.         mLongLineHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,  
  46.                 LONG_LINE_HEIGHT,  
  47.                 getResources().getDisplayMetrics());  
  48.         mShortLineHeight = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,  
  49.                 SHORT_LINE_HEIGHT,  
  50.                 getResources().getDisplayMetrics());  
  51.         initPaint();  
  52.     }  
  53.   
  54.     private void initPaint() {  
  55.         mCirclePaint = new Paint(Paint.ANTI_ALIAS_FLAG);  
  56.         mCirclePaint.setColor(Color.RED);  
  57.         // 将画笔设置为空心  
  58.         mCirclePaint.setStyle(Style.STROKE);  
  59.         // 设置画笔宽度  
  60.         mCirclePaint.setStrokeWidth(mCircleLineWidth);  
  61.   
  62.         mLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG);  
  63.         mLinePaint.setColor(Color.RED);  
  64.         mLinePaint.setStyle(Style.FILL_AND_STROKE);  
  65.         // 设置画笔宽度  
  66.         mLinePaint.setStrokeWidth(mLineWidth);  
  67.     }  
  68.   
  69.     @Override  
  70.     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  71.         super.onMeasure(widthMeasureSpec, heightMeasureSpec);  
  72.     }  
  73.   
  74.     @Override  
  75.     protected void onDraw(Canvas canvas) {  
  76.         canvas.setDrawFilter(mDrawFilter);  
  77.         super.onDraw(canvas);  
  78.         // 绘制表盘  
  79.         drawCircle(canvas);  
  80.         // 绘制刻度  
  81.         drawLines(canvas);  
  82.     }  
  83.   
  84.     /**  
  85.      * 绘制刻度  
  86.      *   
  87.      * @param canvas  
  88.      */  
  89.     private void drawLines(Canvas canvas) {  
  90.         for (int i = 0; i <= 360; i++) {  
  91.             if (i % 30 == 0) {  
  92.                 mLineBottom = mLineTop + mLongLineHeight;  
  93.                 mLinePaint.setStrokeWidth(mLineWidth);  
  94.             } else {  
  95.                 mLineBottom = mLineTop + mShortLineHeight;  
  96.                 mLinePaint.setStrokeWidth(mHalfLineWidth);  
  97.             }  
  98.   
  99.             if (i % 6 == 0) {  
  100.                 canvas.save();  
  101.                 canvas.rotate(i, mHalfWidth, mHalfHeight);  
  102.                 canvas.drawLine(mLineLeft, mLineTop, mLineLeft, mLineBottom, mLinePaint);  
  103.                 canvas.restore();  
  104.             }  
  105.         }  
  106.     }  
  107.   
  108.     /**  
  109.      * 绘制表盘  
  110.      *   
  111.      * @param canvas  
  112.      */  
  113.     private void drawCircle(Canvas canvas) {  
  114.         canvas.drawCircle(mHalfWidth, mHalfHeight, mHalfWidth - mHalfCircleLineWidth, mCirclePaint);  
  115.     }  
  116.   
  117.     @Override  
  118.     protected void onSizeChanged(int w, int h, int oldw, int oldh) {  
  119.         super.onSizeChanged(w, h, oldw, oldh);  
  120.         mHalfWidth = w / 2;  
  121.         mHalfHeight = h / 2;  
  122.   
  123.         mLineLeft = mHalfWidth - mHalfLineWidth;  
  124.         mLineTop = mHalfHeight - mHalfWidth + mFixLineHeight;  
  125.     }  
  126. }  

同样的,有兴趣的同学可以自己补上文字;


四、canvas.skew( ) - 画布的错切: 

[html] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. /**  
  2.  * Preconcat the current matrix with the specified skew.  
  3.  *  
  4.  * @param sx The amount to skew in X  
  5.  * @param sy The amount to skew in Y  
  6.  */  
  7. public native void skew(float sx, float sy);  

这个方法只要理解了两个参数即可:

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

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

注意,这里全是倾斜角度的tan值,比如我们打算在X轴方向上倾斜45度,tan45=1;

先在X 轴上倾斜45 度,我们一起看看:

[html] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. @Override  
  2. protected void onDraw(Canvas canvas) {  
  3.     super.onDraw(canvas);  
  4.     canvas.drawColor(Color.BLUE);  
  5.     canvas.drawRect(new Rect(0, 0, 400, 400), mPaint);  
  6.   
  7.     // x 方向上倾斜45 度  
  8.     canvas.skew(1, 0);  
  9.     mPaint.setColor(0x8800ff00);  
  10.     canvas.drawRect(new Rect(0, 0, 400, 400), mPaint);  
  11. }  
效果如下:


再在y轴上倾斜45度看看:

[html] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. @Override  
  2. protected void onDraw(Canvas canvas) {  
  3.     super.onDraw(canvas);  
  4.     canvas.drawColor(Color.BLUE);  
  5.     canvas.drawRect(new Rect(0, 0, 400, 400), mPaint);  
  6.   
  7.     // y 方向上倾斜45 度  
  8.     canvas.skew(0, 1);  
  9.     mPaint.setColor(0x8800ff00);  
  10.     canvas.drawRect(new Rect(0, 0, 400, 400), mPaint);  
  11. }  
此时效果如下:



关于Canvas(画布)的translate(平移)、scale(缩放) 、rotate(旋转) 、skew(错切)就说这么多,这些方法都不复杂,而灵活的使用往往能解决绘制中很多看似复杂的问题,所以重在理解,并在看到与之相关的效果时能够及时恰当的进行关联。


当然对Canvas的操作往往使用Matrix(后面会单独讲)也能达到同样的效果,想看例子可参考 一个绚丽的loading动效分析与实现!

源码下载链接

 

.Canvas的ClipPath, ClipRect, ClipRegion :通过Path, Rect ,Region 的不同组合,Android几乎可以支持任意现状的裁剪区域。 

    android.graphics包中定义了Point, Rect, Path, Region 这几种几何形状,Path可以为有圆弧,椭圆,二次曲线,三次曲线,线段,矩形等基本几何图形或是由这些基本几何图形组合而成,Path可以为开放或是闭合曲线。Rect提供了定义矩形的简洁方法。Region则支持通过区域的“加”,“减”,“并”,“异或”等逻辑运算由多个Region组合而成。Region.Op定义了Region支持的区域间运算种类。 

    Clipping 介绍有Region运算来为Canvs定义剪裁区域后,同一幅图最后显示的效果。canvas.save();和canvas.restore();用于保存和恢复Canvas的状态属性。 
    drawScene定义了绘图的内容:一条红线,一个绿圆,和“Clipping”文字。
 

Java代码  收藏代码
  1. private void drawScene(Canvas canvas) {  
  2.     canvas.clipRect(00100100);  
  3.       
  4.     canvas.drawColor(Color.WHITE);  
  5.    
  6.     mPaint.setColor(Color.RED);  
  7.     canvas.drawLine(00100100, mPaint);  
  8.    
  9.     mPaint.setColor(Color.GREEN);  
  10.     canvas.drawCircle(307030, mPaint);  
  11.    
  12.     mPaint.setColor(Color.BLUE);  
  13.     canvas.drawText("Clipping"10030, mPaint);  
  14. }  


剪裁区域为两个矩形相减: 
Java代码  收藏代码
  1. canvas.save();  
  2. canvas.translate(16010);  
  3. canvas.clipRect(10109090);  
  4. canvas.clipRect(30307070, Region.Op.DIFFERENCE);  
  5. drawScene(canvas);  
  6. canvas.restore();  

剪裁区域为一个圆: 
Java代码  收藏代码
  1. canvas.save();  
  2. canvas.translate(10160);  
  3. mPath.reset();  
  4. canvas.clipPath(mPath); // makes the clip empty  
  5. mPath.addCircle(505050, Path.Direction.CCW);  
  6. canvas.clipPath(mPath, Region.Op.REPLACE);  
  7. drawScene(canvas);  
  8. canvas.restore();  


剪裁区域为两个矩形的并集: 
Java代码  收藏代码
  1. canvas.save();  
  2. canvas.translate(160160);  
  3. canvas.clipRect(006060);  
  4. canvas.clipRect(4040100100, Region.Op.UNION);  
  5. drawScene(canvas);  
  6. canvas.restore();  


剪裁区域为两个矩形的异或集: 
Java代码  收藏代码
  1. canvas.save();  
  2. canvas.translate(10310);  
  3. canvas.clipRect(006060);  
  4. canvas.clipRect(4040100100, Region.Op.XOR);  
  5. drawScene(canvas);  
  6. canvas.restore();  


最后一个为两个矩形的逆向差集: 
Java代码  收藏代码
  1. canvas.save();  
  2. canvas.translate(160310);  
  3. canvas.clipRect(006060);  
  4. canvas.clipRect(4040100100,  
  5. Region.Op.REVERSE_DIFFERENCE);  
  6. drawScene(canvas);  
  7. canvas.restore();  


效果图片: 



/********************************************Path部分**********************************************/

Path中有关方法的讲解:

path.reset():清除掉path里的线条和曲线,但是不会改变它的fill-type(用于设置多个path重叠部分显示方式);

path.rewind():清除掉path里的线条和曲线,但是会保留内部的数据结构以便重用;

path.set(Path src);用src的内容"替换"原path的内容;

Direction.cw:设置绘制图形的方向(顺时针/逆时针方向)一起看个小例子:

创建一个path,添加一个实心圆到path里

[html] view plain copy
  1. mEndPath = new Path();  
  2. mEndPath.addCircle(300, 300, 100, Direction.CW);  
绘制该path:

[html] view plain copy
  1. canvas.drawPath(mEndPath, mPaint);  
效果如下,无可厚非:



此时在path里再添加一个矩形:

[html] view plain copy
  1. mEndPath = new Path();  
  2. mEndPath.addCircle(300, 300, 100, Direction.CW);  
  3. mEndPath.addRect(new RectF(50, 50, 250, 200), Direction.CW);  
效果如下:



做如下改动:

[html] view plain copy
  1. mEndPath = new Path();  
  2. mEndPath.addCircle(300, 300, 100, Direction.CW);  
  3. //mEndPath.addRect(new RectF(50, 50, 250, 200), Direction.CW);  
  4.   
  5. mSrcPath = new Path();  
  6. mSrcPath.addRect(new RectF(50, 50, 250, 200), Direction.CW);  
  7. mEndPath.set(mSrcPath);  
直接运行,如果在4.0以上的机器上(4.0及以上硬件加速默认开启),会发现屏幕上什么都没有了,说明该方法会受到硬件加速的影响,关掉硬件加速,再看效果:



下面一起来看看Path 的 FillType - 填充模式:

android里定义了四种FillType,分别是:

 WINDING          (0),取并集U,取二者所有元素的集合

 EVEN_ODD         (1),取二者的不同元素的集合

 INVERSE_WINDING    (2),取二者所有元素的反集合

 INVERSE_EVEN_ODD   (3)取交集n,取二者共同元素的集合

有张图可以专门用来说明这四种模式的差别:


以上图示已经非常清晰,我们还是用如下代码做下测试:

[html] view plain copy
  1. mEndPath = new Path();  
  2. mEndPath.addCircle(300, 300, 150, Direction.CW);  
  3. mEndPath.addCircle(380, 380, 150, Direction.CW);  
  4. mEndPath.setFillType(FillType.INVERSE_EVEN_ODD);  
  5.   
  6. mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);  
  7. mPaint.setStyle(Style.FILL);  
  8. mPaint.setColor(Color.RED);  
测试结果如下图:


不设置FillType:

           

setFillType(FillType.WINDING)          setFillType(FillType.EVEN_ODD): 


           

setFillType(FillType.INVERSE_WINDING):   setFillType(FillType.INVERSE_EVEN_ODD):


根据以上图示,Path的FillType可以总结如下:

1.Path的默认FillType为 FillType.WINDING;

2.作用的范围为绘制 Path 的 Canvas 整体,而非 path 所在区域;

3.FillType.WINDING:取path所有所在区域;

4.FillType.EVEN_ODD:取path所在并不相交区域;

5.FillType.INVERSE_WINDING:取path所有未占区域;

6.FillType.INVERSE_EVEN_ODD:取path未占或相交区域;

下面看看和填充模式相关的几个方法:

getFillType():不用多说,返回 Path 的填充模式;

setFillType():设置 Path 的填充模式;

isInverseFillType():是否是 逆 填充模式:

WINDING 和 EVEN_ODD 返回false,INVERSE_WINDING 和 INVERSE_EVEN_ODD 返回true;

toggleInverseFillType():切换相反的填充模式,举个小例子:

[html] view plain copy
  1. mEndPath = new Path();  
  2. mEndPath.addCircle(300, 300, 150, Direction.CW);  
  3. mEndPath.addCircle(380, 380, 150, Direction.CW);  
  4. mEndPath.setFillType(FillType.WINDING);  
  5. mEndPath.toggleInverseFillType();  
  6.   
  7. mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);  
  8. mPaint.setStyle(Style.FILL);   //设置图形填充类型(全充满)
  9. mPaint.setColor(Color.RED);  
此时给Path设置了WINDING的填充模式,调用toggleInverseFillType(),最终的模式为:

FillType.INVERSE_WINDING

isEmpty():path是否为空,如果path不包含任何线条和曲线,则返回true,否则返回false;

isRect(RectF rect):如果path指定的是一个rect,则返回true,否则返回false,如果返回true & rect 不为null,则将该rect设置为path 的区域;

computeBounds(RectF bounds,boolean exact):计算path所在区域,并将结果写入bounds,如果整个path只包含0或1个点,将返回(0,0,0,0):

用如下代码做下测试:

[html] view plain copy
  1. mComputeRect = new RectF();  
  2. mEndPath = new Path();  
  3. mEndPath.addCircle(380, 380, 150, Direction.CW);  
  4. mEndPath.addRect(new RectF(200, 300, 500, 500), Direction.CW);  
  5. mEndPath.computeBounds(mComputeRect, false);  
  6. Toast.makeText(  
  7.         mContext,  
  8.         "" + mComputeRect.left + "," + mComputeRect.top + "," + mComputeRect.right + ","  
  9.                 + mComputeRect.bottom,  
  10.         Toast.LENGTH_LONG).show();  

返回结果为(200,230,530,530),即path所含内容的边界区域


incReserve(int extraPtCount):提示path将会增加extraPtCount个点,这能使path有效率的分配它的存储空间;

0 0