概述

在正常的使用场景中,我们处理了比较多的矩形区域内触摸事件,比如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源码:

#import <UIKit/UIKit.h>

@interface panelView : UIView

@end

panelView.h

 #import "panelView.h"

 @implementation panelView

 - (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self initSubviews];
}
return self;
} - (void)initSubviews
{
UIButton *roundBtn = [UIButton buttonWithType:UIButtonTypeCustom];
roundBtn.frame = CGRectMake(self.frame.size.width / - , -, , );
roundBtn.backgroundColor = [UIColor blueColor];
roundBtn.layer.cornerRadius = ;
roundBtn.tag = ;
[roundBtn addTarget:self action:@selector(onBtnPressed:)
forControlEvents:UIControlEventTouchUpInside];
[self addSubview:roundBtn]; UIButton *leftBtn = [UIButton buttonWithType:UIButtonTypeCustom];
leftBtn.frame = CGRectMake(, , , );
leftBtn.backgroundColor = [UIColor blueColor];
leftBtn.tag = ;
[leftBtn addTarget:self action:@selector(onBtnPressed:)
forControlEvents:UIControlEventTouchUpInside];
[self addSubview:leftBtn]; UIButton *rightBtn = [UIButton buttonWithType:UIButtonTypeCustom];
rightBtn.frame = CGRectMake(self.frame.size.width - , , , );
rightBtn.backgroundColor = [UIColor blueColor];
rightBtn.tag = ;
[rightBtn addTarget:self action:@selector(onBtnPressed:)
forControlEvents:UIControlEventTouchUpInside];
[self addSubview:rightBtn];
} - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
UIView *hitView = nil;
//NSLog(@"point:%@", NSStringFromCGPoint(point));
UIButton *roundBtn = (UIButton *)[self viewWithTag:];
UIButton *leftBtn = (UIButton *)[self viewWithTag:];
UIButton *rightBtn = (UIButton *)[self viewWithTag:];
BOOL pointInRound = [self touchPointInsideCircle:roundBtn.center radius: targetPoint:point];
if (pointInRound) {
hitView = roundBtn;
} else if(CGRectContainsPoint(leftBtn.frame, point)) {
hitView = leftBtn;
} else if(CGRectContainsPoint(rightBtn.frame, point)) {
hitView = rightBtn;
} else {
hitView = self;
}
return hitView;
} - (BOOL)touchPointInsideCircle:(CGPoint)center radius:(CGFloat)radius targetPoint:(CGPoint)point
{
CGFloat dist = sqrtf((point.x - center.x) * (point.x - center.x) +
(point.y - center.y) * (point.y - center.y));
return (dist <= radius);
} - (void)onBtnPressed:(id)sender
{
UIButton *btn = (UIButton *)sender;
NSLog(@"btn tag:%d", btn.tag);
} @end

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

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

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

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

 bool CGRectContainsPoint(CGRect rect, CGPoint point)

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

 - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
UIView *hitView = nil;
//NSLog(@"point:%@", NSStringFromCGPoint(point));
UIButton *roundBtn = (UIButton *)[self viewWithTag:10086];
UIButton *leftBtn = (UIButton *)[self viewWithTag:10087];
UIButton *rightBtn = (UIButton *)[self viewWithTag:10088];
BOOL pointInRound = [self touchPointInsideCircle:roundBtn.center radius:30 targetPoint:point];
if (pointInRound) {
hitView = roundBtn;
} else if(CGRectContainsPoint(leftBtn.frame, point)) {
hitView = leftBtn;
} else if(CGRectContainsPoint(rightBtn.frame, point)) {
hitView = rightBtn;
} else {
hitView = self;
}
return hitView;
}

  

此外,在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. call、apply、bind的区别

    me.setname.call(me,"dpd","dpd12");      通过call执行say方法 ,第一个参数为作用对象,后边的一些列参数作为实参传入 ...

  2. ASP.NET中Request.RawUrl、Request.Url的区别

    如果访问的地址是: http://hovertree.com/guestbook/addmessage.aspx?key=hovertree%3C&n=myslider#zonemenu 那么 ...

  3. C#利用浏览按钮获得文件路径和文件夹路径

    生成文件夹路径 private void btnChoose_Click(object sender, EventArgs e) {            using (OpenFileDialog ...

  4. Guava并发:ListenableFuture与RateLimiter示例

    ListenableFuture顾名思义就是可以监听的Future,它是对java原生Future的扩展增强 RateLimiter类似于JDK的信号量Semphore,他用来限制对资源并发访问的线程 ...

  5. Android对话框自定义标题

    Android自带的对话框标题不好看,如果我们需要给弹出的对话框设置一个自己定义的标题,可以使用AlertDialog.Builder的setCustomTitle()方法. 定义一个对话框标题的ti ...

  6. XE8 for iOS 状态栏的几种效果

    XE8 实现 iOS 状态栏的几种效果: 一.状态栏底色: 开一个新工程. 设定 Fill.Color 颜色属性. 设定 Fill.Kind = Solid. 无需修改任何官方源码. 二.隐藏状态栏( ...

  7. LinuxMint 17.1 Cinnamon桌面窗口焦点bug

    LinuxMint 升级到 17.1后,发现桌面彻底不爽了. 发现各应用窗口经常无法正常获取焦点,必须用Alt + Tab切换才能解锁. 乌龙...

  8. Java并发编程:synchronized

    Java并发编程:synchronized 虽然多线程编程极大地提高了效率,但是也会带来一定的隐患.比如说两个线程同时往一个数据库表中插入不重复的数据,就可能会导致数据库中插入了相同的数据.今天我们就 ...

  9. java 本地目录文件删除 ***最爱那水货

    /** * @note 删除目录下的所有文件 * @param path * @return */ public static boolean delAllFile(String path){ boo ...

  10. Vue从零开始(一)

    一.什么是Vue? Vue.js(读音 /vjuː/, 类似于 view) 是一套构建用户界面的 渐进式框架.与其他重量级框架不同的是,Vue 采用自底向上增量开发的设计.Vue 的核心库只关注视图层 ...