概述

在正常的使用场景中,我们处理了比较多的矩形区域内触摸事件,比如UIButton、UIControl。一般来说,这些控件的图形以及触摸区域都是矩形或者圆角矩形的。但是在一些特殊应用场景中我们有时不得不面对这样一种比较严苛的需求,比如要求程序只对某个圆形、五角形等非常规区域的点击事件进行处理,这就需要花点功夫了。本文以圆形为例子来介绍此类场景的处理方法。

先看下面一张图(附图1),我们的目标是实现如下自定义tabbar。中间带突起圆形的自定义tabbar曾一度流行,今天我们来粗糙地实现一下。

在附图一中,红色代表tabbar,上面有三个蓝色按钮。在三个按钮中我们重点解决按钮A,因为它有一半的区域突在tabbar的有效区域外。

对于按钮A,我们有以下两个问题需要解决:

1、如何准确过滤掉A外接矩形里非蓝色区域的点击事件?

2、如何让A的上半部分也能响应触摸事件?

其实两个问题的解决方法是基本一致的。在iOS中所有控件都是以矩形的方式存在的,在图2中尽管蓝色部分看起来是圆形,但当点击外接矩形内的非圆形区域时也会默认触发点击事件。因此,我们需要用一些手段把触摸事件“拦截”下来。想要“拦截”事件,就必须了解iOS的事件分发机制,也就是当你点击设备屏幕后,iOS是如何决定由那个view去最终响应你的触摸!下面插播一小段关于iOS事件分发的介绍:

==================================

当你手指触摸屏幕后会发生以下事情:触摸事件被封装成一个UIEvent事件,去当前iOS操作系统的active app队列中取当前活跃的APP,把event传给它--->event传给UIApplication--->传给UIWindow的root view controller(rootVC)--->调用rootVC.view的所有subviews的hitTest:event:方法。哪个view的hitTest:event方法返回非nil值,则触摸事件就交给该view处理。关于事件分发的详细机制及举例可以参考技术哥大神的文章

==================================

分析

让我们重新回到探讨的问题上。通过以上简介我们可以知道,想“拦截”触摸事件,则应该在tabbar的hitTest:event方法中做处理(坐标判断等)。以下是具体的demo源码:

  1. #import <UIKit/UIKit.h>
  2.  
  3. @interface panelView : UIView
  4.  
  5. @end

panelView.h

  1. #import "panelView.h"
  2.  
  3. @implementation panelView
  4.  
  5. - (id)initWithFrame:(CGRect)frame
  6. {
  7. self = [super initWithFrame:frame];
  8. if (self) {
  9. [self initSubviews];
  10. }
  11. return self;
  12. }
  13.  
  14. - (void)initSubviews
  15. {
  16. UIButton *roundBtn = [UIButton buttonWithType:UIButtonTypeCustom];
  17. roundBtn.frame = CGRectMake(self.frame.size.width / - , -, , );
  18. roundBtn.backgroundColor = [UIColor blueColor];
  19. roundBtn.layer.cornerRadius = ;
  20. roundBtn.tag = ;
  21. [roundBtn addTarget:self action:@selector(onBtnPressed:)
  22. forControlEvents:UIControlEventTouchUpInside];
  23. [self addSubview:roundBtn];
  24.  
  25. UIButton *leftBtn = [UIButton buttonWithType:UIButtonTypeCustom];
  26. leftBtn.frame = CGRectMake(, , , );
  27. leftBtn.backgroundColor = [UIColor blueColor];
  28. leftBtn.tag = ;
  29. [leftBtn addTarget:self action:@selector(onBtnPressed:)
  30. forControlEvents:UIControlEventTouchUpInside];
  31. [self addSubview:leftBtn];
  32.  
  33. UIButton *rightBtn = [UIButton buttonWithType:UIButtonTypeCustom];
  34. rightBtn.frame = CGRectMake(self.frame.size.width - , , , );
  35. rightBtn.backgroundColor = [UIColor blueColor];
  36. rightBtn.tag = ;
  37. [rightBtn addTarget:self action:@selector(onBtnPressed:)
  38. forControlEvents:UIControlEventTouchUpInside];
  39. [self addSubview:rightBtn];
  40. }
  41.  
  42. - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
  43. {
  44. UIView *hitView = nil;
  45. //NSLog(@"point:%@", NSStringFromCGPoint(point));
  46. UIButton *roundBtn = (UIButton *)[self viewWithTag:];
  47. UIButton *leftBtn = (UIButton *)[self viewWithTag:];
  48. UIButton *rightBtn = (UIButton *)[self viewWithTag:];
  49. BOOL pointInRound = [self touchPointInsideCircle:roundBtn.center radius: targetPoint:point];
  50. if (pointInRound) {
  51. hitView = roundBtn;
  52. } else if(CGRectContainsPoint(leftBtn.frame, point)) {
  53. hitView = leftBtn;
  54. } else if(CGRectContainsPoint(rightBtn.frame, point)) {
  55. hitView = rightBtn;
  56. } else {
  57. hitView = self;
  58. }
  59. return hitView;
  60. }
  61.  
  62. - (BOOL)touchPointInsideCircle:(CGPoint)center radius:(CGFloat)radius targetPoint:(CGPoint)point
  63. {
  64. CGFloat dist = sqrtf((point.x - center.x) * (point.x - center.x) +
  65. (point.y - center.y) * (point.y - center.y));
  66. return (dist <= radius);
  67. }
  68.  
  69. - (void)onBtnPressed:(id)sender
  70. {
  71. UIButton *btn = (UIButton *)sender;
  72. NSLog(@"btn tag:%d", btn.tag);
  73. }
  74.  
  75. @end

panelView.m(控件初始化,不展开)

在hitTest方法中最重要的是判断按钮A所在的区域,其实仅仅用到两点的距离公式来圈出蓝色部分所在的圆形,判断方法如下:

  1. - (BOOL)touchPointInsideCircle:(CGPoint)center radius:(CGFloat)radius targetPoint:(CGPoint)point
  2. {
  3. CGFloat dist = sqrtf((point.x - center.x) * (point.x - center.x) +
  4. (point.y - center.y) * (point.y - center.y));
  5. return (dist <= radius);
  6. }

而判断点是否在按钮B/C内就更简单了,系统提供了封装好的api:

  1. bool CGRectContainsPoint(CGRect rect, CGPoint point)

最终,关于事件“拦截”的判断如下:

  1. - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
  2. {
  3. UIView *hitView = nil;
  4. //NSLog(@"point:%@", NSStringFromCGPoint(point));
  5. UIButton *roundBtn = (UIButton *)[self viewWithTag:10086];
  6. UIButton *leftBtn = (UIButton *)[self viewWithTag:10087];
  7. UIButton *rightBtn = (UIButton *)[self viewWithTag:10088];
  8. BOOL pointInRound = [self touchPointInsideCircle:roundBtn.center radius:30 targetPoint:point];
  9. if (pointInRound) {
  10. hitView = roundBtn;
  11. } else if(CGRectContainsPoint(leftBtn.frame, point)) {
  12. hitView = leftBtn;
  13. } else if(CGRectContainsPoint(rightBtn.frame, point)) {
  14. hitView = rightBtn;
  15. } else {
  16. hitView = self;
  17. }
  18. return hitView;
  19. }

  

此外,在hitTest中还可以玩其他花样,比如将本该由按钮A响应的时间强制性转发给其他按钮,这只需在hitTest的返回值中修改一下即可!

本文所用demo完整源码点这里test.zip下载。

   

图1                   图2

=======================================================

原创文章,转载请注明 编程小翁@博客园,邮件zilin_weng@163.com,欢迎各位与我在C/C++/Objective-C/机器视觉等领域展开交流!

欢迎跳转我的GitHub主页,关注我的开源代码。也欢迎你Star/Fork/Watch我的项目。

=======================================================

【原】ios的hitTest方法以及不规则区域内触摸事件处理方法的更多相关文章

  1. ios的hitTest方法以及不规则区域内触摸事件处理方法

    概述 在正常的使用场景中,我们处理了比较多的矩形区域内触摸事件,比如UIButton.UIControl.一般来说,这些控件的图形以及触摸区域都是矩形或者圆角矩形的.但是在一些特殊应用场景中我们有时不 ...

  2. 【GIS新探索】算法实现在不规则区域内均匀分布点

    1 概要 在不规则区域内均匀分布点,这个需求初看可能不好理解.如果设想一下需求场景就比较简单了. 场景1:在某个地区范围内,例如A市区有100W人口,需要将这100W人口在地图上面相对均匀的标识出来. ...

  3. iOS开发中常见bug!(内附解答方法)

    序言 你是否曾经修复了一个 bug ,随后又发现了一个跟刚修复 bug 有关的 bug ,又或是修复 bug 的方式引起了另一个 bug ? 然而这些问题是绝佳的学习机会.所以我们怎样尽可能多地从修复 ...

  4. AS3 在不规则区域内拖动

    原理: 1.确保拖动对象在鼠标点上,如果不确定会出现瞬间移动的感觉 2.确保触碰到非通行区域,跳回到没触碰的点 源码: import flash.events.MouseEvent; import f ...

  5. iOS开发——UI进阶篇(十二)事件处理,触摸事件,UITouch,UIEvent,响应者链条,手势识别

    触摸事件 在用户使用app过程中,会产生各种各样的事件 一.iOS中的事件可以分为3大类型 触摸事件加速计事件远程控制事件 响应者对象在iOS中不是任何对象都能处理事件,只有继承了UIResponde ...

  6. 打印web页面指定区域的三种方法

    本文和大家分享一下web页面实现指定区域打印功能的三种方法,一起来看下吧. 第一种方法:使用CSS 定义一 个.noprint的class,将不打印的内容放入这个class内. 代码如下: <s ...

  7. 原 IOS之NSValue整理

    原 IOS之NSValue整理 发表于2年前(2013-02-28 23:02)   阅读(1974) | 评论(0) 5人收藏此文章, 我要收藏 赞3 IOS NSValue 值对象(value o ...

  8. 原 iOS面试题收集

    原 iOS面试题收集 发表于2年前(2013-07-22 13:47)   阅读(369) | 评论(0) 4人收藏此文章, 我要收藏 赞0 听云性能监测产品App.Server.CDN免费试用,绑定 ...

  9. 原 iOS深入学习(Block全面分析)http://my.oschina.net/leejan97/blog/268536

    原 iOS深入学习(Block全面分析) 发表于1年前(2014-05-24 16:45)   阅读(26949) | 评论(14) 39人收藏此文章, 我要收藏 赞21 12月12日北京OSC源创会 ...

随机推荐

  1. Android Studio快捷键每日一练(5)

    原文地址:http://www.developerphil.com/android-studio-tips-of-the-day-roundup-5/ 42.Enter键和Tab键补全 快捷键:Ent ...

  2. Mac下github项目检出与提交

    项目检出 如果你的git还没有代码仓库,可以用过git的代码仓库页面新建一个你的仓库 创建git上的仓库后,我们还需要建立本地的仓库,所以打开Mac终端,建立本地仓库文件夹(这里我用HelloC),然 ...

  3. C# 3DES加密

    最近一个项目中,因为服务端是用的java开发的,客户端是用的C#,由于通信部分采用到了3DES加密,所以做个记录,以备以后需要的时候直接用. 这是对方(java)的加密算法,和网上流传的代码也差不多( ...

  4. ExtJs动态生成treepanel的Json格式

    在节点中加上"checked"属性,会自动生成checkbox. 获取选中节点 var nodeArray = ""; var nodesObj = mytre ...

  5. Wojilu学习笔记 (02)

    使用RequireJS (1)整个页面,应该只有一个 <script src="" > 标签,并且放在页面底部,用来引入 RequireJS 和 main.js 文件 ...

  6. Qt的tablewidget行列头自适应宽度

    Qt构造一个TableWidget后,窗口最大化后,列头默认不能自适应宽度,研究了一下,Qt提供了两种方式来处理这个问题,如下:   1. 使用horizontalHeader()->setRe ...

  7. CodeSmith连接Oracle

    Win7上仅安装了Oracle32位客户端,此时CodeSmith无法连接Oracle数据库. 解决方法一:如果同一台电脑安装了Oracle64位数据库,这样CodeSmith可以连接数据库. 解决方 ...

  8. 【Java每日一题】20161201

    20161130问题解析请点击今日问题下方的"[Java每日一题]20161201"查看 package Dec2016; public class Ques1201 { publ ...

  9. hibernate------java-delete-insert-update

    **************************************************************************************************** ...

  10. windows下打包react-native应用程序

    P.S.0:不截图了,上传图片太麻烦,每次只能上传一张.... 先生成签名文件,如果已有签名文件略过此步: keytool -genkey -v -keystore my-release-key.ke ...