概述

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

@interface panelView : UIView

@end

#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 / 2 - 30, -30, 60, 60);
roundBtn.backgroundColor = [UIColor blueColor];
roundBtn.layer.cornerRadius = 30;
roundBtn.tag = 10086;
[roundBtn addTarget:self action:@selector(onBtnPressed:)
forControlEvents:UIControlEventTouchUpInside];
[self addSubview:roundBtn];

UIButton *leftBtn = [UIButton buttonWithType:UIButtonTypeCustom];
leftBtn.frame = CGRectMake(0, 15, 30, 30);
leftBtn.backgroundColor = [UIColor blueColor];
leftBtn.tag = 10087;
[leftBtn addTarget:self action:@selector(onBtnPressed:)
forControlEvents:UIControlEventTouchUpInside];
[self addSubview:leftBtn];

UIButton *rightBtn = [UIButton buttonWithType:UIButtonTypeCustom];
rightBtn.frame = CGRectMake(self.frame.size.width - 30, 15, 30, 30);
rightBtn.backgroundColor = [UIColor blueColor];
rightBtn.tag = 10088;
[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: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;
}

- (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(控件初始化,不展开)

 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 / 2 - 30, -30, 60, 60);
18 roundBtn.backgroundColor = [UIColor blueColor];
19 roundBtn.layer.cornerRadius = 30;
20 roundBtn.tag = 10086;
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(0, 15, 30, 30);
27 leftBtn.backgroundColor = [UIColor blueColor];
28 leftBtn.tag = 10087;
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 - 30, 15, 30, 30);
35 rightBtn.backgroundColor = [UIColor blueColor];
36 rightBtn.tag = 10088;
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:10086];
47 UIButton *leftBtn = (UIButton *)[self viewWithTag:10087];
48 UIButton *rightBtn = (UIButton *)[self viewWithTag:10088];
49 BOOL pointInRound = [self touchPointInsideCircle:roundBtn.center radius:30 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
70 - (void)onBtnPressed:(id)sender
71 {
72 UIButton *btn = (UIButton *)sender;
73 NSLog(@"btn tag:%d", btn.tag);
74 }
75
76 @end

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

1
2
3
4
5
6
- (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:

1
bool CGRectContainsPoint(CGRect rect, CGPoint point)

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- (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

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. 【Unity游戏开发】UGUI不规则区域点击的实现

    一.简介 马三从上一家公司离职了,最近一直在出去面试,忙得很,所以这一篇博客拖到现在才写出来.马三在上家公司工作的时候,曾处理了一个UGUI不规则区域点击的问题,制作过程中也有一些收获和需要注意坑,因 ...

  7. UGUI实现不规则区域点击响应

    UGUI实现不规则区域点击响应 前言 大家吼啊!最近工作上事情特别多,没怎么打理博客.今天无意打开cnblog才想起该写点东西了.今天给大家讲一个Unity中不规则区域点击响应的实现方法,使用UGUI ...

  8. OSPF --- 不规则区域实验

    OSPF不规则区域实验: 一.知识点整理: OSPF中路由器的角色(看图): 骨干路由器:路由器所有接口属于area 0  -->R3 非骨干路由器:路由器所有接口属于非area 0  --&g ...

  9. iOS开发系列--通讯录、蓝牙、内购、GameCenter、iCloud、Passbook系统服务开发汇总

    --系统应用与系统服务 iOS开发过程中有时候难免会使用iOS内置的一些应用软件和服务,例如QQ通讯录.微信电话本会使用iOS的通讯录,一些第三方软件会在应用内发送短信等.今天将和大家一起学习如何使用 ...

随机推荐

  1. lintcode735. Replace With Greatest From Right

    Given an array of integers, replace every element with the next greatest element (greatest element o ...

  2. openstack对接VMware浅析

    前言 本文是对openstack对接vmware的浅析,所以本文重点是以下两点: 先了解它的整体架构,搞清楚为什么要用这样的架构: 然后再了解架构中的各个组件,组件提供的主要功能与各个组件之间的交互 ...

  3. qt qchart缩放后坐标轴间隔取整

    使用qt的qchart显示数据曲线,坐标轴QValueAxis可以设置刻度间隔数量,但每个刻度的数值是根据坐标的极值除以间隔数量得到的,不一定是整数,导致曲线控件的显示刻度不适合观察. 如图: 纵坐标 ...

  4. Python高级编程-多线程

    (一)进程线程概述: 很多同学都听说过,现代操作系统比如Mac OS X,UNIX,Linux,Windows等,都是支持“多任务”的操作系统. 什么叫“多任务”呢?简单地说,就是操作系统可以同时运行 ...

  5. css模仿微信弹出菜单

      css模仿微信弹出菜单 效果图: html: <div class="action-sheet-backdrop"> <div class="act ...

  6. Python字符串中的r前缀

    在Python中,如果字符串的前面有r/R前缀,那么,就会禁用转义符\的功能: >>>path = r'C:\new\text.dat' >>>pah 'C:\\n ...

  7. 每周psp-第五周

    PSP表格: 类别 任务 开始时间 结束时间 中断时间 delta时间 开会 scrum立会 10.13下午6:04 10.13下午6:34 0 30 开会 scrum立会 10.14下午6:02 1 ...

  8. Java实验二实验报告:java面向对象程序设计

    java实验二实验报告 实验内容 1. 初步掌握单元测试和TDD 2. 理解并掌握面向对象三要素:封装.继承.多态 3. 初步掌握UML建模 4. 熟悉S.O.L.I.D原则 5. 了解设计模式 实验 ...

  9. c# 读取xml文件 编写form

    主要思想:xml保存控件的数据,c#读取出来并加以显示. 难点:1.控件有父容器和子控件的关系:2.控件事件的添加. 1.控件有父容器和子控件的关系: 可以用绝对坐标在xml文件中先读取子控件再读取父 ...

  10. sql 至少含有

    查询Score表中至少有5名学生选修的并以3开头的课程的平均分数: select avg(degree),cnofrom scorewhere cno like '3%'group by cnohav ...