Android饼图好画,但不规则区域点击如何区分?

2018-06-12  本文已影响0人  Marco黑八

前言

前阵子帮团队招人,我很喜欢问候选人这个问题。
点击区域是不规则的,该如何让手势事件在不规则区域内做出响应?
如果候选人的答案是通过数学计算的方式来确定落点。
我会微微一笑的反问他,如果是五角星怎么办?


本文主要会介绍以下内容

1、绘制饼图的几种方式

2、不规则区域的点击

3、文字居中公式

先看一下最终实现的效果,代码地址在文末

自定义的饼图

如何绘制饼图?

基础知识

Path

类型 API 描述
添加弧形 addArc 添加弧形
逻辑运算 op A\B(DIFFERENCE), A∩B(INTERSECT), B\A(REVERSE_DIFFERENCE), A∪B(UNION), A⊕B(XOR)
计算边界 computeBounds 计算路径的边界

因为之后会用到逻辑运算,先来看一下逻辑运算的API,minSdkVersion必须大于等于19

op

Added in API level 19

boolean op (Path path1, Path path2, Path.Op op)

Set this path to the result of applying the Op to the two specified paths. The resulting path will be constructed from non-overlapping contours. The curve order is reduced where possible so that cubics may be turned into quadratics, and quadratics maybe turned into lines.

参数1,参数2是需要计算的两个路径。参数3是运算种类。两个path经过逻辑运算后得到新的路径会存放在调用这个api的路径当中。

逻辑运算包含五种类型

描述 示意图
Path.Op. DIFFERENCE Subtract the second path from the first path. 从第一路径中减去第二个路径。 difference
Path.Op. REVERSE_DIFFERENCE Subtract the first path from the second path. 从第二个路径中减去第一个路径。 reverse_diferecne
Path.Op. INTERSECT Intersect the two paths. 两个路径的交集。 intersect
Path.Op. UNION Union (inclusive-or) the two paths. 两个路径的合集。 union
Path.Op. XOR Exclusive-or the two paths. 两个路径的异或。即两个路径的合集减去两个路径的交集。 xor

Canvas

类型 API 描述
绘制弧形 drawArc 绘制弧形
绘制路径 drawPath 绘制路径,路径样式取决于paint

饼图绘制

结合上面的API绘制饼图的方式有很多,我先后尝试了以下3种方式

//方法1 调用canvas.drawArc方法绘制不闭合的弧形
canvas.drawArc(pieInRectF, startAngle, sweepAngle, false, piePaint);
//方法2 添加弧形路径,调用canvas.drawPath绘制
Path path = new Path();
path.addArc(pieInRectF, startAngle, sweepAngle);
canvas.drawPath(path, piePaint);
//以上两种绘制弧形的思路均是绘制不闭合的弧形路径,通过设置路径宽度最终达到环形效果
//方法3
Path path1 = new Path();
path1.moveTo(in.centerX(), in.centerY());
path1.arcTo(in, startAngle, angle);
Path path2 = new Path();
path2.moveTo(out.centerX(), out.centerY());
path2.arcTo(out, startAngle, angle);
Path path = new Path();
path.op(path2, path1, Path.Op.DIFFERENCE);
//从path2中减去path1获取新的路径
通过path绘制饼图

画了一个示意图,简单感受一下...

Path2闭合后是个扇形,Path1是个较小的扇形,Path2 - Path1后就能得到一块饼图所需的区域了。

那么问题来了,已经有两种简单易懂绘制饼图的方法,为什么要舍近求远搞个这么复杂的?

如果你只需要完成饼图绘制,前两种方法确实够用,但如果想实现饼图不同区域点击效果,推荐使用第三种方式。


如何实现不规则区域的点击?

关于不规则区域的点击实现,先提供一些其他思路

1、像素点判断

缺点:容易OOM、图形不能动态修改

png图都是矩形的,无效部分都设置为透明色,把图片作为控件的背景图片。点击时判断点击点的像素是否为透明,不透明则的控件响应点击事件。具体实现方法请见《Android不规则点击区域详解》。

2、数学逻辑判断

在研究不规则区域点击的时候,看到了这种方案。

采用数学公式判断点击坐标是否在圆环上,再通过角度判断落在具体哪一块上。但由于考虑到数学计算方式的复杂(其实是我懒...),最终我没有尝试这种方式。感兴趣的同学具体实现方法请见《Android自定义饼状图,且能区分点击的区域》和《PieChar扇形图实现》。

3、通过Region判断坐标落点

由于Path是由多个坐标点构成的,将path转化成region,可以通过region.contains(int x,int y)来判断点击坐标是否在该区域。该方法不仅适用于饼图,其他更加复杂图形同样适用,当然前提是你能构建出绘制这个图形所需要的路径。

public void setRegion(Path path) {
    Region re = new Region();
    RectF rectF = new RectF();
    path.computeBounds(rectF, true);
    re.setPath(path, new Region((int) rectF.left, (int) rectF.top, (int) rectF.right, (int) rectF.bottom));
    this.region = re;
}

上文中已经通过Path的逻辑运算获取到饼图块的path,通过这个path再生成region。

判断是否在这个区域也非常简单

public boolean isInRegion(float x, float y) {
    return region != null && region.contains((int)x, (int)y);
}

有人可能会有疑问,调用path.addArc方法,也是通过path绘制图案,这种方式行不行呢?事实是检验真理的唯一标准。亲测使用第二种方案获得的path去生成region获得的范围区域完全错误。详见下图。

错误的饼图

每个饼图块都对应一个单独的region,黑色的点是我手指点击的时候绘制的落点,即region.contains((int)x, (int)y)返回true。可以看到判断点位是否在区域内的位置完全错了。


文字居中的公式

在此记录一下文字居中公式,具体推导过程网上很多博客讲解了,就不展开了。

 Paint.FontMetrics fm = mTxtPaint.getFontMetrics();
 float y = centerY - fm.top / 2 - fm.bottom / 2;

本文源码

感兴趣的朋友可以点击本文源码查看。


参考链接

Path图形与逻辑运算

PieChar扇形图实现

上一篇 下一篇

猜你喜欢

热点阅读