概述
在正常的使用場景中赁项,我們處理了比較多的矩形區(qū)域內(nèi)觸摸事件,比如UIButton澈段、UIControl悠菜。一般來說,這些控件的圖形以及觸摸區(qū)域都是矩形或者圓角矩形的败富。但是在一些特殊應(yīng)用場景中我們有時不得不面對這樣一種比較嚴(yán)苛的需求悔醋,比如要求程序只對某個圓形、五角形等非常規(guī)區(qū)域的點(diǎn)擊事件進(jìn)行處理兽叮,這就需要花點(diǎn)功夫了芬骄。本文以圓形為例子來介紹此類場景的處理方法。
先看下面一張圖(附圖1)鹦聪,我們的目標(biāo)是實(shí)現(xiàn)如下自定義tabbar账阻。中間帶突起圓形的自定義tabbar曾一度流行,今天我們來粗糙地實(shí)現(xiàn)一下泽本。
在附圖一中淘太,紅色代表tabbar,上面有三個藍(lán)色按鈕规丽。在三個按鈕中我們重點(diǎn)解決按鈕A蒲牧,因?yàn)樗幸话氲膮^(qū)域突在tabbar的有效區(qū)域外。
對于按鈕A嘁捷,我們有以下兩個問題需要解決:
1造成、如何準(zhǔn)確過濾掉A外接矩形里非藍(lán)色區(qū)域的點(diǎn)擊事件?
2雄嚣、如何讓A的上半部分也能響應(yīng)觸摸事件晒屎?
其實(shí)兩個問題的解決方法是基本一致的喘蟆。在iOS中所有控件都是以矩形的方式存在的,在圖2中盡管藍(lán)色部分看起來是圓形鼓鲁,但當(dāng)點(diǎn)擊外接矩形內(nèi)的非圓形區(qū)域時也會默認(rèn)觸發(fā)點(diǎn)擊事件蕴轨。因此,我們需要用一些手段把觸摸事件“攔截”下來骇吭。想要“攔截”事件橙弱,就必須了解iOS的事件分發(fā)機(jī)制,也就是當(dāng)你點(diǎn)擊設(shè)備屏幕后燥狰,iOS是如何決定由那個view去最終響應(yīng)你的觸摸棘脐!下面插播一小段關(guān)于iOS事件分發(fā)的介紹:
==================================
當(dāng)你手指觸摸屏幕后會發(fā)生以下事情:觸摸事件被封裝成一個UIEvent事件,去當(dāng)前iOS操作系統(tǒng)的active app隊(duì)列中取當(dāng)前活躍的APP龙致,把event傳給它--->event傳給UIApplication--->傳給UIWindow的root view controller(rootVC)--->調(diào)用rootVC.view的所有subviews的hitTest:event:方法蛀缝。哪個view的hitTest:event方法返回非nil值,則觸摸事件就交給該view處理目代。關(guān)于事件分發(fā)的詳細(xì)機(jī)制及舉例可以參考技術(shù)哥大神的文章
==================================
分析
讓我們重新回到探討的問題上屈梁。通過以上簡介我們可以知道,想“攔截”觸摸事件榛了,則應(yīng)該在tabbar的hitTest:event方法中做處理(坐標(biāo)判斷等)在讶。以下是具體的demo源碼:
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
在hitTest方法中最重要的是判斷按鈕A所在的區(qū)域,其實(shí)僅僅用到兩點(diǎn)的距離公式來圈出藍(lán)色部分所在的圓形霜大,判斷方法如下:
- (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);
}
而判斷點(diǎn)是否在按鈕B/C內(nèi)就更簡單了构哺,系統(tǒng)提供了封裝好的api:
1
bool CGRectContainsPoint(CGRect rect, CGPoint point)
最終,關(guān)于事件“攔截”的判斷如下:
- (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響應(yīng)的時間強(qiáng)制性轉(zhuǎn)發(fā)給其他按鈕,這只需在hitTest的返回值中修改一下即可湖笨!