設(shè)計(jì)復(fù)雜的iOS動(dòng)畫效果

動(dòng)畫的世界豐富多彩,令人陶醉

隨著工作的深入,其實(shí)每個(gè) App 中,最關(guān)鍵的莫過于用戶交互,隨著技術(shù)時(shí)代的發(fā)展,人們已經(jīng)不再滿足于手指點(diǎn)點(diǎn)碰碰就完成功能。用戶更加期待炫酷的操作體驗(yàn)。需要眼前一亮的感覺。這些都是 App 將要解決的問題。而動(dòng)畫交互無疑是最多的一種手段。但是,現(xiàn)在也不是簡(jiǎn)單動(dòng)畫的天下,需要更多的創(chuàng)意,繁雜而簡(jiǎn)潔的創(chuàng)意。

動(dòng)畫效果的UIView Object

結(jié)構(gòu)圖
// 顯示動(dòng)畫
- (void)showWithDuration:(CGFloat)duration animated:(BOOL)animated;

// 隱藏動(dòng)畫
- (void)hideWithDuration:(CGFloat)duration animated:(BOOL)animated;

// 創(chuàng)建view
- (void)buildView;

// 動(dòng)畫百分比(手動(dòng)設(shè)置動(dòng)畫的程度)
- (void)percent:(CGFloat)percent;

制定統(tǒng)一的動(dòng)畫接口

即:相關(guān)有動(dòng)畫效果的類,有同一的動(dòng)畫方法命名

  • 為了實(shí)現(xiàn)后續(xù)復(fù)雜的動(dòng)畫組合
  • 后續(xù)的代碼維護(hù)極為方便
  • 優(yōu)先考慮里氏代換原則

動(dòng)畫中的高內(nèi)聚低耦合原理

  • 高內(nèi)聚:有動(dòng)畫效果的類,自身具有動(dòng)畫方法
  • 不要把實(shí)現(xiàn)動(dòng)畫的細(xì)節(jié)暴露在外
  • 設(shè)計(jì)動(dòng)畫類盡量要符合單一職能原則,以便后續(xù)方便組合成復(fù)雜的動(dòng)畫效果

設(shè)計(jì)動(dòng)畫函數(shù)的注意事項(xiàng)

  • 動(dòng)畫方法的命名統(tǒng)一
  • 預(yù)留非動(dòng)畫情形的設(shè)計(jì)(tableView等reuse情況)
  • 用百分比來表示動(dòng)畫的執(zhí)行程度
  • 懶加載的使用

動(dòng)畫效果就是frame值的重新設(shè)定

[UIView animateWithDuration:duration animations:^{
        self.frame = self.midRect;
        self.alpha = 1.f;
    }];

一個(gè)具有動(dòng)畫效果的類

//  TranslateView.h
#import <UIKit/UIKit.h>

@interface TranslateView : UIView

//** 顯示動(dòng)畫 */
- (void)show;

/** 隱藏動(dòng)畫 */
- (void)hide;

/** 創(chuàng)建view */
- (void)buildView;

/** 動(dòng)畫百分比(手動(dòng)設(shè)置動(dòng)畫的程度) */
- (void)percent:(CGFloat)percent;

@end
//  TranslateView.m
#import "TranslateView.h"

@interface TranslateView ()

@property (nonatomic) CGFloat offsetY;
@property (nonatomic) CGRect startRect;
@property (nonatomic) CGRect midRect;
@property (nonatomic) CGRect endRect;
@property (nonatomic) CGRect curRect;

@end

@implementation TranslateView

- (instancetype)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        self.alpha = 0.f;
        self.offsetY = 20.0f;
        self.backgroundColor = [UIColor blackColor];
        [self buildView];
    }
    return self;
}

// 創(chuàng)建view
- (void)buildView {
    self.startRect = self.frame;
    self.midRect   = CGRectMake(self.startRect.origin.x,
                                self.startRect.origin.y + self.offsetY,
                                self.startRect.size.width,
                                self.startRect.size.height);
    self.endRect   = CGRectMake(self.startRect.origin.x,
                                self.startRect.origin.y + self.offsetY * 2,
                                self.startRect.size.width,
                                self.startRect.size.height);
}


// 顯示動(dòng)畫
- (void)showWithDuration:(CGFloat)duration animated:(BOOL)animated {
    if (animated == YES) {
        [UIView animateWithDuration:duration animations:^{
            self.frame = self.midRect;
            self.alpha = 1.f;
            self.curRect = self.frame;
        }];
    } else {
        self.frame = self.midRect;
        self.alpha = 1.f;
        self.curRect = self.frame;
    }
}

// 隱藏動(dòng)畫
- (void)hideWithDuration:(CGFloat)duration animated:(BOOL)animated {
    if (animated == YES) {
        [UIView animateWithDuration:duration animations:^{
            self.frame = self.endRect;
            self.alpha = 0.f;
        } completion:^(BOOL finished) {
            self.frame = self.startRect;
        }];
    } else {
        self.frame = self.startRect;
        self.alpha = 0.f;
    }
}



// 動(dòng)畫百分比(手動(dòng)設(shè)置動(dòng)畫的程度)
- (void)percent:(CGFloat)percent {
    CGFloat tmpOffsetY = 0;
    CGFloat stateHeight = 20;
    if (percent <= 0) {
        tmpOffsetY = percent * self.offsetY;
    } else if (percent >= 1) {
        tmpOffsetY = self.offsetY;
    } else {
        tmpOffsetY = percent * self.offsetY;
    }

    if (CGRectGetMinY(self.curRect) + tmpOffsetY < stateHeight || CGRectGetMaxY(self.curRect) + tmpOffsetY > CGRectGetHeight([UIScreen mainScreen].bounds)) {
        return;
    }
    
    self.frame = CGRectMake(self.curRect.origin.x,
                            self.curRect.origin.y + tmpOffsetY,
                            self.curRect.size.width,
                            self.curRect.size.height);
    self.curRect = self.frame;
}

- (void)show
{
    [self showWithDuration:2.0f animated:YES];
}

- (void)hide
{
    [self hideWithDuration:0.5f animated:YES];
}
@end

里氏代換原則處理動(dòng)畫類的繼承問題

SourceView *tmpView = [[ChildTwoView alloc] init];
[tmpView show];
  • 里氏代換原則的基本原理 (多態(tài))
  • 設(shè)計(jì)中要確保父類可以直接調(diào)用子類的方法
  • 將父類設(shè)計(jì)成虛類

什么是多態(tài)

1.定義

某一類事物的多種表現(xiàn)形態(tài)

舉例說明:

1)生活中:動(dòng)物:貓—波斯貓

2)程序中:父類指針指向子類對(duì)象

2.條件

  • 子類繼承父類
  • 子類有重寫父類的方法
  • 父類指針指向子類對(duì)象
動(dòng)物 a = [貓 alloc]init];
動(dòng)物 a = [狗 alloc]init];

表現(xiàn)形式:在父類指針指向不同的對(duì)象的時(shí)候,通過父類指針調(diào)用被重寫的方法,會(huì)執(zhí)行該指針?biāo)赶虻哪莻€(gè)對(duì)象的方法;

3.實(shí)現(xiàn)

@interface Computer : NSObject
- (void)system;
@end

@interface PC : Computer
// 重寫system方法
@end

@interface Mac : Computer
// 重寫system方法
@end

Computer *com = nil;

Computer = [PC alloc]init]; //實(shí)例化PC對(duì)象
[PC system];

Computer = [Mac alloc]init; //實(shí)例化Mac對(duì)象
[Mac system];

4.原理

  • id類型:通用對(duì)象指針類型,弱類型,編譯的時(shí)候不進(jìn)行具體的類型檢查。
  • 動(dòng)態(tài)綁定:動(dòng)態(tài)類型可以做到在程序直到執(zhí)行時(shí)才確定對(duì)象的真實(shí)類型,進(jìn)而確定需要調(diào)用那個(gè)對(duì)象方法。

ObjC 不同于其他程序設(shè)計(jì)語言,它可以在運(yùn)行的時(shí)候加入新的數(shù)據(jù)類型和新的程序模塊,動(dòng)態(tài)類型識(shí)別,動(dòng)態(tài)綁定,動(dòng)態(tài)加載。

5.優(yōu)點(diǎn):

  • 多態(tài)最重要的優(yōu)點(diǎn)在于簡(jiǎn)化了編程接口,何以理解?它允許在類和類之間重用一些習(xí)慣性的命名,而不是為每一個(gè)新加的函數(shù)命名一個(gè)新的名字。這樣,編程接口就是一些抽象行為的集合,從而和實(shí)現(xiàn)接口的類區(qū)分開。
  • 多態(tài)也使得代碼可以分散在不同的對(duì)象中,而不用視圖在一個(gè)函數(shù)中考慮到所有可能的對(duì)象。這樣可以優(yōu)化代碼的擴(kuò)展性和復(fù)用性。當(dāng)一個(gè)新的情景出現(xiàn)時(shí),無需對(duì)現(xiàn)有的代碼進(jìn)行改動(dòng),而只需要增加一個(gè)新的類和新的同名的方法。

詳細(xì)參考

Objective-C 繼承和多態(tài)

動(dòng)畫中的模塊化設(shè)計(jì)

  • 動(dòng)畫效果實(shí)現(xiàn)難度的判斷
  • 將看到的動(dòng)畫效果拆分成小模塊
  • 將寫好的小模塊組合成你所需要的動(dòng)畫效果
//ViewController.m

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    // 復(fù)雜的動(dòng)畫被寫進(jìn)了BaseAnimationView當(dāng)中,沒有暴露不必要的細(xì)節(jié)
    BaseAnimationView *baseView = [[BaseAnimationView alloc] initWithFrame:CGRectZero];
    [self.view addSubview:baseView];
    [baseView show];
}
//BaseAnimationView.m

- (void)show
{
    [self.translateView show];
}

- (void)hide
{
    [self.translateView hide];
}

- (void)buildView
{
    self.translateView = [[TranslateView alloc] initWithFrame:CGRectMake(50, 100, 2, 50)];
    [self addSubview:self.translateView];
}

- (void)percent:(CGFloat)percent
{
    [self.translateView percent:percent];
}

延時(shí)執(zhí)行某方法

[self performSelector:@selector(excuteAfterDelay) withObject:nil afterDelay:6];

初始化UIView

- (instancetype)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        // ......
    }
    return self;
}

Demo on Github

原文閱讀

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1.import static是Java 5增加的功能,就是將Import類中的靜態(tài)方法,可以作為本類的靜態(tài)方法來...
    XLsn0w閱讀 1,427評(píng)論 0 2
  • *面試心聲:其實(shí)這些題本人都沒怎么背,但是在上海 兩周半 面了大約10家 收到差不多3個(gè)offer,總結(jié)起來就是把...
    Dove_iOS閱讀 27,622評(píng)論 30 472
  • 面向?qū)ο笾饕槍?duì)面向過程。 面向過程的基本單元是函數(shù)。 什么是對(duì)象:EVERYTHING IS OBJECT(萬物...
    sinpi閱讀 1,220評(píng)論 0 4
  • 從小常常被父母教導(dǎo),人不可貌相,絕對(duì)不可以以貌取人。但是現(xiàn)在人可以貌相的說法,我也是十分認(rèn)可。一個(gè)人的穿著...
    黃小蟻閱讀 835評(píng)論 0 1
  • 親愛的夏優(yōu)同學(xué): 很久沒見了是吧,似乎沒想到我會(huì)在這個(gè)時(shí)候送你一份大禮吧,希望你能像我在去年收到你的禮物時(shí)有不一樣...
    徐徐思卿閱讀 361評(píng)論 0 4

友情鏈接更多精彩內(nèi)容