Region和Rect的一点比较

在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)。姑且这么理解吧……

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值