iOS自定義控件教程(三)UIView動畫入門

上一篇文章咱們介紹了UIView的觸摸響應鏈原理,順便學習UIView的基本屬性和方法。在iOS自定義控件教程(二)關於響應鏈的那些事中咱們講解了觸摸原理,但並未具體實現其功能,接下來咱們具體講講點擊效果的實現和響應的動畫效果的實現。
最終實現的效果:Github下載源碼git

觸摸響應鏈UIResponder

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重寫他的響應鏈來處理VCView的響應鏈了,VCView默認將響應鏈穿給本身的VC,在VC中處理就能夠了。ide

strip

觸摸事件

觸摸事件能夠經過觸摸鏈響應,也可使用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改變他的顏色。

UIView動畫基礎

咱們在上面代碼中,調用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的具體實現方法,有什麼疑問請在下方留言。

相關文章
相關標籤/搜索