1. 程式人生 > >iOS 自定義 中間帶突起圓形的tabbar

iOS 自定義 中間帶突起圓形的tabbar


概述

在正常的使用場景中,我們處理了比較多的矩形區域內觸控事件,比如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原始碼:

 panelView.h  panelView.m(控制元件初始化,不展開)

在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

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

原創文章,轉載請註明 程式設計小翁@部落格園,郵件[email protected],歡迎各位與我在C/C++/Objective-C/機器視覺等領域展開交流!

歡迎跳轉我的,關注我的開原始碼。也歡迎你Star/Fork/Watch我的專案。