安卓原生Region:

今天没有讲flutter,貌似,flutter并没有与之对应的内容。

Region,中文意思即区域的意思,它表示的是canvas图层上的某一块封闭的区域。

你可能会问,既然已经有canvas,为什么还要有region?

这是因为,安卓原生的组件无法像flutter那样,通过使用GestureDetector或者GestureRecognizer对组件进行包裹从而非常方便的实现控件的手势控制,而是要进行实例化与调用,那么在自绘组件的时候如何实现手势控制呢?这其实就是region的功能之一了。

首先放上基本上所有的API:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
/**构造方法*/
public Region() //创建一个空的区域
public Region(Region region) //拷贝一个region的范围
public Region(Rect r) //创建一个矩形的区域
public Region(int left, int top, int right, int bottom) //创建一个矩形的区域

/**一系列set方法,这些set方法,和上面构造方法形式差不多*/
public void setEmpty() {
public boolean set(Region region)
public boolean set(Rect r)
public boolean set(int left, int top, int right, int bottom)
/*往一个Region中添加一个Path只有这种方法,参数clip代表这个整个Region的区域,在在里面裁剪出path范围的区域*/
public boolean setPath(Path path, Region clip) //用指定的Path和裁剪范围构建一个区域

/**几个判断方法*/
public native boolean isEmpty();//判断该区域是否为空
public native boolean isRect(); //是否是一个矩阵
public native boolean isComplex();//是否是多个矩阵组合

/**一系列的getBound方法,返回一个Region的边界*/
public Rect getBounds()
public boolean getBounds(Rect r)
public Path getBoundaryPath()
public boolean getBoundaryPath(Path path)

/**一系列的判断是否包含某点 和是否相交*/
public native boolean contains(int x, int y);//是否包含某点
public boolean quickContains(Rect r)//是否包含某矩阵
public native boolean quickContains(int left, int top, int right,int bottom)//是否包含某矩阵
public boolean quickReject(Rect r) //是否没和该矩阵相交
public native boolean quickReject(int left, int top, int right, int bottom); //是否没和该矩阵相交
public native boolean quickReject(Region rgn); //是否没和该矩阵相交

/**几个平移变换的方法*/
public void translate(int dx, int dy)
public native void translate(int dx, int dy, Region dst);
public void scale(float scale) //hide
public native void scale(float scale, Region dst);//hide

/**一系列组合的方法*/
public final boolean union(Rect r)
public boolean op(Rect r, Op op) {
public boolean op(int left, int top, int right, int bottom, Op op)
public boolean op(Region region, Op op)
public boolean op(Rect rect, Region region, Op op)

说明一下最后的一组关于Region组合的方式。组合即当前的Region和另外的一个Region组合,可以用不同的Op方式来进行组合,Op是一个枚举,定义在Region类中:

1
2
3
4
5
6
7
8
public enum Op {
DIFFERENCE(0), //最终区域为region1 与 region2不同的区域
INTERSECT(1), // 最终区域为region1 与 region2相交的区域
UNION(2), //最终区域为region1 与 region2组合一起的区域
XOR(3), //最终区域为region1 与 region2相交之外的区域
REVERSE_DIFFERENCE(4), //最终区域为region2 与 region1不同的区域
REPLACE(5); //最终区域为为region2的区域
}

另外还有矩形集枚举区域——RegionIterator类

对于特定的区域,我们都可以使用多个矩形来表示其大致形状。事实上,如果矩形足够小,一定数量的矩形就能够精确表示区域的形状,也就是说,一定数量的矩形所合成的形状,也可以代表区域的形状。RegionIterator类,实现了获取组成区域的矩形集的功能,其实RegionIterator类非常简单,总共就两个函数,一个构造函数和一个获取下一个矩形的函数;
RegionIterator(Region region) //根据区域构建对应的矩形集
boolean next(Rect r) //获取下一个矩形,结果保存在参数Rect r 中

由于在Canvas中没有直接绘制Region的函数,我们想要绘制一个区域,就只能通过利用RegionIterator构造矩形集来逼近的显示区域。用法如下:

1
2
3
4
5
6
7
8
9
private void drawRegion(Canvas canvas,Region rgn,Paint paint)
{
RegionIterator iter = new RegionIterator(rgn);
Rect r = new Rect();

while (iter.next(r)) {
canvas.drawRect(r, paint);
}
}

各个API具体使用在zhoushenxian的这篇博客里有详细案例,但我感觉有点太过啰嗦。

下面讲几个我认为比较重要的方法:

使用SetPath()构造不规则区域

1
boolean setPath (Path path, Region clip)

参数说明:
Path path:用来构造的区域的路径
Region clip:与前面的path所构成的路径取交集,并将两交集设置为最终的区域

由于路径有很多种构造方法,而且可以轻意构造出非矩形的路径,这就摆脱了前面的构造函数只能构造矩形区域的限制。但这里有个问题是要指定另一个区域来取共同的交集,当然如果想显示路径构造的区域,Region clip参数可以传一个比Path范围大的多的区域,取完交集之后,当然是Path参数所对应的区域喽。机智的孩子。

下面,先构造一个椭圆路径,然后在SetPath时,传进去一个比Path小的矩形区域,让它们两个取交集

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class MyRegionView extends View {
public MyRegionView(Context context) {
super(context);
// TODO Auto-generated constructor stub
}

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//初始化Paint
Paint paint = new Paint();
paint.setColor(Color.RED);
paint.setStyle(Style.FILL);
paint.setStrokeWidth(2);
//构造一个椭圆路径
Path ovalPath = new Path();
RectF rect = new RectF(50, 50, 200, 500);
ovalPath.addOval(rect, Direction.CCW);
//SetPath时,传入一个比椭圆区域小的矩形区域,让其取交集
Region rgn = new Region();
rgn.setPath(ovalPath,new Region(50, 50, 200, 200));
//画出路径
drawRegion(canvas, rgn, paint);
}

private void drawRegion(Canvas canvas,Region rgn,Paint paint)
{
RegionIterator iter = new RegionIterator(rgn);
Rect r = new Rect();

while (iter.next(r)) {
canvas.drawRect(r, paint);
}
}
}

上面的例子其实可以看出region的一个重要作用是对图形的截取,因为有些时候我们并不能通过绘制的方法完美地绘制出我们想要的图形。但其实也有一定的局限性,就是一次只能有一个路径与region取交集。多个路径的话,先把路径合并不就完了嘛。

区域的合并、交叉等操作

无论是区域还是矩形,都会涉及到与另一个区域的一些操作,比如取交集、取并集等,涉及到的函数有:

1
2
3
4
5
public final boolean union(Rect r)   
public boolean op(Rect r, Op op) {
public boolean op(int left, int top, int right, int bottom, Op op)
public boolean op(Region region, Op op)
public boolean op(Rect rect, Region region, Op op)

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.Region;
import android.graphics.Paint.Style;
import android.graphics.Region.Op;
import android.graphics.RegionIterator;
import android.view.View;

public class MyRegionView extends View {

public MyRegionView(Context context) {
super(context);
}

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);

//构造两个矩形
Rect rect1 = new Rect(100,100,400,200);
Rect rect2 = new Rect(200,0,300,300);

//构造一个画笔,画出矩形轮廓
Paint paint = new Paint();
paint.setColor(Color.RED);
paint.setStyle(Style.STROKE);
paint.setStrokeWidth(2);

canvas.drawRect(rect1, paint);
canvas.drawRect(rect2, paint);

//构造两个Region
Region region = new Region(rect1);
Region region2= new Region(rect2);

//取两个区域的交集
region.op(region2, Op.INTERSECT);

//再构造一个画笔,填充Region操作结果
Paint paint_fill = new Paint();
paint_fill.setColor(Color.GREEN);
paint_fill.setStyle(Style.FILL);
drawRegion(canvas, region, paint_fill);

}

private void drawRegion(Canvas canvas,Region rgn,Paint paint)
{
RegionIterator iter = new RegionIterator(rgn);
Rect r = new Rect();

while (iter.next(r)) {
canvas.drawRect(r, paint);
}
}
}

这里是取了交集,其余情况如图:

判断包含与相交:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**一系列的判断是否包含某点 和是否相交*/
public native boolean contains(int x, int y);//是否包含某点
public boolean quickContains(Rect r)//是否包含某矩阵
public native boolean quickContains(int left, int top, int right,int bottom)//是否包含某矩阵

public boolean quickReject(Rect r) //是否和该矩阵不相交
//官方注释:
/**
* Return true if the region is empty, or if the specified rectangle does
* not intersect the region. Returning false is not a guarantee that they
* intersect, but returning true is a guarantee that they do not.
*/

public native boolean quickReject(int left, int top, int right, int bottom); //是否和该矩阵不相交
public native boolean quickReject(Region rgn); //是否和该矩阵不相交

判断触点是否在某region内:

1
2
3
4
5
6
7
8
9
10
11
12
@Override
public boolean onTouchEvent(MotionEvent event) {
int downX = (int)event.getX();
int downY = (int)event.getY();
boolean isContain = region.contains(downX,downY);
if(isContain){
Toast.makeText(getContext(),"这个点在这个区域内",Toast.LENGTH_LONG).show();
}else{
Toast.makeText(getContext(),"这个点不在这个区域内",Toast.LENGTH_LONG).show();
}
return true;
}

个人的一些理解:

region的使用,给了开发者更高的灵活性,毕竟canvas只有一个,将组件的各个部分利用region解耦,能够在开发中与开发后带来更大的便利。这里类似于将flutter的组件组合与绘制融合起来的一种策略,细细想来也确实是显得更加便利,毕竟region既拥有类似于canvas的“绘制”能力,也拥有类似于GestureDetector的点击监听,虽然没有其他手势监听,确实显得有些遗憾,但region的任务更偏向于绘制,也显得更加内聚了。

参考文章:自定义控件之绘图篇(三):区域(Range)