上一篇文章咱們介紹了UIView的觸摸響應鏈原理,順便學習UIView的基本屬性和方法。在iOS自定義控件教程(二)關於響應鏈的那些事中咱們講解了觸摸原理,但並未具體實現其功能,接下來咱們具體講講點擊效果的實現和響應的動畫效果的實現。
最終實現的效果:Github下載源碼git
UIView
繼承自UIResponder
(響應鏈類),繼承了相應的響應鏈方法:github
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event; - (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event; - (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event; - (void)touchesCancelled:(nullable NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event; - (void)touchesEstimatedPropertiesUpdated:(NSSet * _Nonnull)touches NS_AVAILABLE_IOS(9_1); // Generally, all responders which do custom press handling should override all four of these methods. // Your responder will receive either pressesEnded:withEvent or pressesCancelled:withEvent: for each // press it is handling (those presses it received in pressesBegan:withEvent:). // pressesChanged:withEvent: will be invoked for presses that provide an analog value // (like thumbsticks or analog push buttons) // *** You must handle cancelled presses to ensure correct behavior in your application. Failure to // do so is very likely to lead to incorrect behavior or crashes. - (void)pressesBegan:(NSSet<UIPress *> *)presses withEvent:(nullable UIPressesEvent *)event NS_AVAILABLE_IOS(9_0); - (void)pressesChanged:(NSSet<UIPress *> *)presses withEvent:(nullable UIPressesEvent *)event NS_AVAILABLE_IOS(9_0); - (void)pressesEnded:(NSSet<UIPress *> *)presses withEvent:(nullable UIPressesEvent *)event NS_AVAILABLE_IOS(9_0); - (void)pressesCancelled:(NSSet<UIPress *> *)presses withEvent:(nullable UIPressesEvent *)event NS_AVAILABLE_IOS(9_0); - (void)motionBegan:(UIEventSubtype)motion withEvent:(nullable UIEvent *)event NS_AVAILABLE_IOS(3_0); - (void)motionEnded:(UIEventSubtype)motion withEvent:(nullable UIEvent *)event NS_AVAILABLE_IOS(3_0); - (void)motionCancelled:(UIEventSubtype)motion withEvent:(nullable UIEvent *)event NS_AVAILABLE_IOS(3_0);
其中touches
開頭的方法是觸摸事件相關的方法;presses
開頭的方法,是iOS9加入的給iPhone6s等支持Deep Press
功能的設備使用的相關方法;motion
開頭的則是給設備的陀螺儀和加速傳感器使用的方法,用於獲取晃動等事件。app
細心的朋友可能會發現,UIViewController
(後面簡稱VC) 和 UIView
一樣繼承自UIResponder
,這樣是爲了方便UIViewController
處理他的view
屬性的響應事件,咱們也就不用繼承UIView
重寫他的響應鏈來處理VC
主View
的響應鏈了,VC
的View
默認將響應鏈穿給本身的VC
,在VC
中處理就能夠了。ide
觸摸事件能夠經過觸摸鏈響應,也可使用UIGesture
(手勢類)來響應。今天咱們的例子中用了比較原始的響應鏈方式。學習
XXXSegmentView.m動畫
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { [super touchesBegan:touches withEvent:event]; } - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { [super touchesEnded:touches withEvent:event]; UITouch* touch = [touches anyObject]; CGPoint point = [touch locationInView:self]; NSInteger detla = self.frame.size.width / self.number; NSInteger touchNumber = point.x / detla; self.selected = touchNumber; // [self setNeedsLayout]; [self setSelectItemAtIndex:touchNumber animate:YES]; }
解釋 touchesBegan
是手指按下事件的方法,這裏按下咱們不作響應,單寫了super方法調用父類方法,仍是那句話,父類方法多是空的不須要調用,單這是一個好習慣,能夠避免一些由於繼承類不調用父類方法形成的BUG。atom
touchesEnded
是手指擡起事件的方法,首先touches
這個集合中的UITouch對象,對應的就是觸摸的手指。咱們用anyObject
取出其中之一,用locationInView:
方法獲取觸摸點的位置。這個CGPoint是一個C中的結構體類型,只有x,y兩個屬性表示位置。spa
接下來咱們根據Label的數量(self.number)來計算每一個label的間隔,用總寬度除以個數。code
NSInteger detla = self.frame.size.width / self.number;
接下來計算咱們觸摸的位置,在第幾個區域對象
NSInteger touchNumber = point.x / detla;
這個touchNumber
即是咱們觸摸的按鈕的index。
接下來咱們調用setSelectItemAtIndex
方法來響應觸摸這個Label的事件
- (void)setSelectItemAtIndex:(NSUInteger)idx animate:(BOOL)animated { self.idx = idx; [self.subviews enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { if ([obj isKindOfClass:[UILabel class]]) { UILabel* label = obj; if (self.selected + 981 == label.tag) { label.textColor = self.tintColor; } else { label.textColor = self.baseColor; } } }]; //這裏是咱們下方的那根線,移動的動畫,下面詳細說 [UIView animateWithDuration:0.2 animations:^{ UILabel* label = (UILabel*)[self viewWithTag:idx + 981]; [self.selectView setFrame:CGRectMake(label.frame.origin.x - self.leftAndRightLineEdge, label.frame.size.height + label.frame.origin.y + 10, label.frame.size.width + self.leftAndRightLineEdge*2, 2)]; }]; }
解釋 每一次觸摸,咱們都須要遍歷全部的Label,將選中的,也就是self.selected + 981 == label.tag
的Label改變他的顏色。
咱們在上面代碼中,調用self.selectView這個屬性,是這麼定義的
@interface
@property (nonatomic, strong) UIView* selectView;
@implementation
- (UIView *)selectView { if (!_selectView) { _selectView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 2)]; [self addSubview:_selectView]; _selectView.backgroundColor = self.tintColor; } return _selectView; }
這個View在第一次調用self.selectView
的時候會初始化,第二次調用時候,_selectView已經初始化過了,就不會再執行初始化方法了。
注意,關於getter和setter的內容,咱們就不細說了,新手只要知道,當前的版本的編譯器,自動爲@property添加了@synthesize(合成)語句,因此iOS6之後,代碼中不多見到
@synthesize selectView = _selectView;
這樣的寫法了,由於編譯器自動幫你作了。因此_selectView是內部變量,單不要直接這樣調用,而應該在除了- (UIView *)selectView這個getter方法外的地方,使用self.selectView
來調用。
咱們再來看看上面觸摸代碼中的動畫代碼:
[UIView animateWithDuration:0.2 animations:^{ UILabel* label = (UILabel*)[self viewWithTag:idx + 981]; [self.selectView setFrame:CGRectMake(label.frame.origin.x - self.leftAndRightLineEdge, label.frame.size.height + label.frame.origin.y + 10, label.frame.size.width + self.leftAndRightLineEdge*2, 2)]; }];
解釋 先使用viewWithTag:
方法獲取Label,注意這裏返回的是UIView類,因此須要使用(UILabel*)進行強行類型轉換。
UIView animateWithDuration:animations:
這個方法,是iOS4.0加入的簡化版的補幀動畫方法。在這個block中去改變一個UIView的frame,系統則會根據傳入的Duration(單位:秒),來自動完成補間動畫,相似於Flash
中的補間動畫。
若是不在animateWithDuration中改變view的frame,view則會直接變爲新的frame,則不會有中間改變的動畫過程了,是否是很簡單。
最後self.leftAndRightLineEdge
這是一個CGFloat浮點型數值,用來控制下面線兩端超出Label的長度,咱們demo中第二個例子中這個數值爲20。
這樣,咱們的最終效果就完成。
下一篇教程,咱們將着手分析UIView控件響應事件並傳遞給其餘VC或View的具體實現方法,有什麼疑問請在下方留言。