在Canvas上绘制Region和Rect,最大的区别是Region不受Canvas本身Matrix的影响,而Rect会。
最近我遇到这么一个问题:
先看代码
package com.example.customview;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.RectF;
import android.graphics.Region;
import android.view.View;
public class RegionTest extends View {
Region region;
RectF rectF;
public RegionTest(Context context) {
super(context);
region = new Region();
rectF = new RectF();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
// 将Region和Rect的长宽以及起点坐标设为一样
region.set(0, 0, w, h);
rectF.set(0, 0, w, h);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.save();
// canvas.clipRegion(region); //裁剪Region
canvas.clipRect(rectF); // 裁剪Rect
canvas.drawColor(Color.BLUE);
canvas.restore();
}
}
这段代码的作用是在一个自定义View里面创建了同样大小的Region和Rect,在onDraw(Canvas canvas)中调用Canvas的clip方法分别裁剪出一个Region和Rect大小的画布,然后涂上蓝色。
当调用
canvas.clipRect(rectF);
代码时,效果如下:
蓝色充满了整个屏幕(除了状态栏和通知栏以外的屏幕),可见Rect的大小是和自定义View的大小一样的。
下面再看调用canvas.clipRegion(region);时的效果:
屏幕下面居然有一块空白!!!
难道是这个Region不够大吗?
加几条输出语句来看一下
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
// 将Region和Rect的长宽以及起点坐标设为一样
region.set(0, 0, w, h);
rectF.set(0, 0, w, h);
// 增加几条输出语句
Log.d("RegionTest", "view的宽度:"+w+" view的高度:"+h);
Log.d("RegionTest", "Region的宽度:"+region.getBounds().width()+" Region的高度:"+region.getBounds().height());
Log.d("RegionTest", "Rect的宽度:"+rectF.width()+" Rect的高度:"+rectF.height());
}
查看Logcat日志
05-26 15:07:29.760: D/RegionTest(4860): view的宽度:480 view的高度:690
05-26 15:07:29.760: D/RegionTest(4860): Region的宽度:480 Region的高度:690
05-26 15:07:29.760: D/RegionTest(4860): Rect的宽度:480.0 Rect的高度:690.0
没问题!Region以及Rect的大小都和View是一样的。
我们再看看效果图,发现clipRegion的时候,底下的空白似乎和顶部的状态栏+标题栏的大小是一样的,难道是因为它们?
再加几条输出语句,看一下Region和Rect的起点坐标是不是(0,0):
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
// 将Region和Rect的长宽以及起点坐标设为一样
region.set(0, 0, w, h);
rectF.set(0, 0, w, h);
Log.d("RegionTest", "view的宽度:"+w+" view的高度:"+h);
Log.d("RegionTest", "Region的宽度:"+region.getBounds().width()+" Region的高度:"+region.getBounds().height());
Log.d("RegionTest", "Rect的宽度:"+rectF.width()+" Rect的高度:"+rectF.height());
// 再加几条输出语句
// 看一下Region和Rect的起点坐标是不是(0,0)
Log.d("RegionTest", "Region的起点:("+region.getBounds().left+","+region.getBounds().top+")");
Log.d("RegionTest", "Rect的起点:("+rectF.left+","+rectF.top+")");
}
此时Logcat输出如下:
05-26 15:16:12.950: D/RegionTest(5099): view的宽度:480 view的高度:690
05-26 15:16:12.950: D/RegionTest(5099): Region的宽度:480 Region的高度:690
05-26 15:16:12.950: D/RegionTest(5099): Rect的宽度:480.0 Rect的高度:690.0
05-26 15:16:12.950: D/RegionTest(5099): Region的起点:(0,0)
05-26 15:16:12.950: D/RegionTest(5099): Rect的起点:(0.0,0.0)
起点都是(0,0)哎,o(╯□╰)o
那问题会出在哪儿呢?既然不是在Region和Rect的创建阶段,那会不会是在裁剪的时候呢?
再试试!
在裁剪完之后输出画布的信息看看。
增加几条语句:
// 创建一个Rect用于保存裁剪后画布的边界信息
Rect clipBounds = new Rect();
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Log.d("RegionTest", "原始canvas的宽度:"+canvas.getWidth()+" 原始canvas的高度:"+canvas.getHeight());
canvas.save();
canvas.clipRect(rectF); // 裁剪Rect
canvas.getClipBounds(clipBounds);
// 增加输出语句,输出裁剪之后画布的信息
Log.d("RegionTest", "裁剪之后的Rect起点:("+clipBounds.left+","+clipBounds.top+")");
Log.d("RegionTest", "裁剪之后的Rect宽度:"+clipBounds.width()+" 裁剪之后的Rect高度:"+clipBounds.height());
canvas.drawColor(Color.BLUE);
canvas.restore();
canvas.save();
canvas.clipRegion(region);
canvas.getClipBounds(clipBounds);
Log.d("RegionTest", "裁剪之后的Region起点:("+clipBounds.left+","+clipBounds.top+")");
Log.d("RegionTest", "裁剪之后的Region宽度:"+clipBounds.width()+" 裁剪之后的Region高度:"+clipBounds.height());
canvas.drawColor(Color.RED);
canvas.restore();
}
来看看输出:
05-26 15:38:57.960: D/RegionTest(5694): 原始canvas的宽度:480 原始canvas的高度:800
05-26 15:38:57.960: D/RegionTest(5694): 裁剪之后的Rect起点:(0,0)
05-26 15:38:57.960: D/RegionTest(5694): 裁剪之后的Rect宽度:480 裁剪之后的Rect高度:690
05-26 15:38:57.960: D/RegionTest(5694): 裁剪之后的Region起点:(0,0)
05-26 15:38:57.960: D/RegionTest(5694): 裁剪之后的Region宽度:480 裁剪之后的Region高度:580
clipRegion后画布高度竟然变小了!!! How Can???
看来问题出在这儿。
我一直有个想法,既然在我们自定义的view上面还有状态栏和标题栏,那么我们的canvas会不会向下平移呢?
我们来看看:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Log.d("RegionTest", "原始canvas的宽度:"+canvas.getWidth()+" 原始canvas的高度:"+canvas.getHeight());
Log.d("RegionTest", "裁剪前canvas的Matrix:"+canvas.getMatrix().toString());
……
}
这里将无关代码都略去了,只看canvas的matrix是什么样的。
Logcat输出:
05-26 15:56:36.560: D/RegionTest(6168): 裁剪前canvas的Matrix:Matrix{[1.0, 0.0, 0.0][0.0, 1.0, 110.0][0.0, 0.0, 1.0]}
这个matrix是这样的
1.0 0.0 0.0
0.0 1.0 110.0
0.0 0.0 1.0
可见在Y轴有了正向偏移,偏移值为110.0。这和我的设想是一致的。
那么这就是问题的关键所在。对于Region来说,它是无视canvas的matrix变换的。canvas在Y轴平移了110.0,Region是不管的,它的起始位置永远是屏幕的左上角。而Rect是会跟随canvas变换的,它的起点永远是canvas的左上角。区别就在这儿了。
下面看一张简陋的图:
简单说明一下
ABCD的灰色区域代表屏幕(这里是480*800);
EFGH矩形代表画布,画布相对于屏幕左上角是沿着Y轴平移了110px的;
左边的灰色矩形代表Rect,它的起点是画布的左上角,底边是和屏幕的底边重合的;
右边的灰色矩形代表Region,它的起点是屏幕的左上角,底边和MN重合,因为高度是690,所以底边距离屏幕底边还有110px。
注:为了看得清楚,将各个矩形都错开来画了。
从这张图可以看出,在canvas上调用clipRect方法,裁剪出来的矩形是EFCD,加上顶部的状态栏和标题栏,刚好是屏幕大小;而调用clipRegion方法,裁剪出来的矩形是EFMN,加上顶部的状态栏和标题栏,底部还是缺一块(NMCD)。
为什么是EFMN而不是ABMN呢?因为我们所有的裁剪操作都是针对画布的啊,画布的顶边就是EF啊啊啊。
哎哟,总算是知道原因了,心累啊。
从上面的介绍还可以看出来一点,起始坐标(0,0)不一定代表屏幕左上角,也可能是画布的左上角,这个要注意。我们是不是可以这样理解,Region对应的是最底层的画布,它是不会Matrix变换的,总是代表屏幕,而我们rect对应的canvas是在它上面的,是会matrix变换的,我们求region和rect的起点坐标都是相对于各自画布左上角的坐标,所以都是(0,0)。姑且这么理解吧……