iOS實戰(zhàn):動畫實戰(zhàn)-自定義轉(zhuǎn)場動畫實現(xiàn)

前言.png

前言

(呃呃呃,其實本文不算是動畫實戰(zhàn)懦铺,只是用到了一點動畫捉貌,算了沒差~)
在平時使用的app中,部分app的部分轉(zhuǎn)場動畫與傳統(tǒng)的動畫不一樣冬念,其實他們使用的是自定義轉(zhuǎn)場動畫趁窃。本文記錄的是自定義轉(zhuǎn)場動畫的實現(xiàn)。

效果圖

效果圖.gif

主要思路

最重要的是需要創(chuàng)建一個繼承NSObject的類急前,并且遵守UIViewControllerAnimatedTransitioning協(xié)議醒陆。我暫時給這個類命名為YQAnimatedTransition。這個協(xié)議就是用來自定義轉(zhuǎn)場動畫的裆针。點進去看看:

@protocol UIViewControllerAnimatedTransitioning <NSObject>
// This is used for percent driven interactive transitions, as well as for
// container controllers that have companion animations that might need to
// synchronize with the main animation.
- (NSTimeInterval)transitionDuration:(nullable id <UIViewControllerContextTransitioning>)transitionContext;
// This method can only  be a nop if the transition is interactive and not a percentDriven interactive transition.
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext;
@end

發(fā)現(xiàn)這個協(xié)議有兩個必須實現(xiàn)的方法统求。第一個方法是設(shè)置動畫的時間。第二個方法是設(shè)置動畫据块。
好了码邻,當這個YQAnimatedTransition類設(shè)置好后,在控制器需要用它的時候調(diào)用它另假。這個下面會具體說像屋。

開始吃鍵盤

1.YQAnimatedTransition創(chuàng)建

首先創(chuàng)建一個繼承NSObject,并且遵守UIViewControllerAnimatedTransitioning協(xié)議的類YQAnimatedTransition边篮。
其次考慮到轉(zhuǎn)場一共有四種方式:push己莺,pop,present戈轿,dismiss凌受。所以我加了一個枚舉,用來設(shè)置轉(zhuǎn)場的類型思杯。

typedef enum {
    YQAnimatedTransitionTypePush,
    YQAnimatedTransitionTypePop,
    YQAnimatedTransitionTypePresent,
    YQAnimatedTransitionTypeDismiss
}YQAnimatedTransitionType;

為了方便這個類的使用胜蛉,我加了一個類方法挠进,在類方法中進行初始化且設(shè)置轉(zhuǎn)場類型:

//.h
+ (YQAnimatedTransition *)animatedTransitionWithType:(YQAnimatedTransitionType)type;

//.m
+ (YQAnimatedTransition *)animatedTransitionWithType:(YQAnimatedTransitionType)type
{
    YQAnimatedTransition *animatedTransition = [[YQAnimatedTransition alloc] init];
    animatedTransition.type = type;
    return animatedTransition;
}

2.協(xié)議方法實現(xiàn)

下面是重點了!既然這個類遵循UIViewControllerAnimatedTransitioning協(xié)議誊册,就需要實現(xiàn)協(xié)議方法领突。
直接上代碼了。

- (NSTimeInterval)transitionDuration:(nullable id <UIViewControllerContextTransitioning>)transitionContext
{
    return 0.5;
}

- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
    self.transitionContext = transitionContext;
    
    if (self.type == YQAnimatedTransitionTypePush) {
    } else if (self.type == YQAnimatedTransitionTypePresent) {
    } else if (self.type == YQAnimatedTransitionTypeDismiss) {
    } else {
    }
}

解釋一下案怯。第一個方法的意思是我設(shè)置轉(zhuǎn)場動畫為0.5秒君旦。第二個方法是在設(shè)置動畫過程。由于篇幅過長嘲碱,我暫時先省略啦~
重點說說上面的第二方法:動畫設(shè)置金砍。
不管是pop或者dismiss等等,只要控制器轉(zhuǎn)場都會執(zhí)行這第二個方法麦锯。所以首先在這個方法中進行判斷恕稠,是屬于哪種轉(zhuǎn)場方式。然后再自定義動畫离咐。
以push為例子:

if (self.type == YQAnimatedTransitionTypePush) {
        
        // 獲得即將消失的vc的v
        UIView *fromeView = [transitionContext viewForKey:UITransitionContextFromViewKey];
        // 獲得即將出現(xiàn)的vc的v
        UIView *toView = [transitionContext viewForKey:UITransitionContextToViewKey];
        // 獲得容器view
        UIView *containerView = [transitionContext containerView];
        
        [containerView addSubview:fromeView];
        [containerView addSubview:toView];
        
        UIBezierPath *startBP = [UIBezierPath bezierPathWithOvalInRect:CGRectMake((containerView.frame.size.width-100)/2, 100, 100, 100)];
        CGFloat radius = 1000;
        UIBezierPath *finalBP = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(150 - radius, 150 -radius, radius*2, radius*2)];
        
        CAShapeLayer *maskLayer = [CAShapeLayer layer];
        maskLayer.path = finalBP.CGPath;
        toView.layer.mask = maskLayer;
        
        //執(zhí)行動畫
        CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"path"];
        animation.fromValue = (__bridge id _Nullable)(startBP.CGPath);
        animation.toValue = (__bridge id _Nullable)(finalBP.CGPath);
        animation.duration = [self transitionDuration:transitionContext];
        animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
        [maskLayer addAnimation:animation forKey:@"path"];
    }

這里用到了動畫的知識谱俭,改變的是layer的path屬性奉件,讓layer從小圓變成了大圓宵蛀。
一直看代碼和文字也累了吧,先看看現(xiàn)在push的效果好了县貌。(注意哈术陶,這里為了看效果,我已經(jīng)在控制器寫了調(diào)用該類的代碼了煤痕,至于怎么調(diào)用梧宫,下面會說,先看效果吧~)

步驟2-1.gif

首先可以發(fā)現(xiàn)一個問題摆碉,就是返回不了了塘匣。解決辦法是:在動畫完成后加一行代碼[transitionContext completeTransition:YES];。但是巷帝,問題又來了忌卤,這行代碼加在哪里呢。
直接加在動畫設(shè)置后面效果:

步驟2-2.gif

好像沒問題楞泼,但是仔細觀察發(fā)現(xiàn)navBar存在push的太早問題驰徊。如果你和我一樣覺得這個很丑,那就換一個方法堕阔。
給animation設(shè)置代理棍厂,然后該類監(jiān)聽動畫,當動畫結(jié)束的時候再調(diào)用這行代碼超陆,這樣就沒問題啦牺弹。當然別忘了遵循動畫協(xié)議CAAnimationDelegate。

- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
{
    //告訴系統(tǒng)轉(zhuǎn)場動畫完成
    [self.transitionContext completeTransition:YES];
}

這樣動畫就寫好了,至于present例驹,dismiss等捐韩,也類似,就不再說啦鹃锈。

上面動畫實現(xiàn)中有一個layer.mask屬性荤胁,我在本文最后會解釋。

3.控制器調(diào)用

最后一步就是控制器調(diào)用剛寫的類了屎债。

a.push/pop方式如下:

在控制器中遵循UINavigationControllerDelegate協(xié)議仅政,并實現(xiàn)協(xié)議方法:

- (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC{
    
    if (operation == UINavigationControllerOperationPush) {
        YQAnimatedTransition *animatedTransition = [YQAnimatedTransition animatedTransitionWithType:YQAnimatedTransitionTypePush];
        return animatedTransition;
    }
    return nil;
}

b.present/dismiss方式如下:

在控制器中遵循UIViewControllerTransitioningDelegate協(xié)議,并實現(xiàn)方法:

- (id<UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source
{
    return [YQAnimatedTransition animatedTransitionWithType:YQAnimatedTransitionTypePresent];
}


- (id<UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed
{
    return [YQAnimatedTransition animatedTransitionWithType:YQAnimatedTransitionTypeDismiss];
}

到這里盆驹,轉(zhuǎn)場動畫就實現(xiàn)了圆丹。

步驟3-1.gif

4.細節(jié)補充

上圖和效果圖比較還是有差別的,少了一個過渡動畫躯喇。當用戶點擊cell的時候辫封,頭像會移動且放大到詳細頁面那個頭像那個位置。實現(xiàn)代碼:

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    // 獲得點擊的cell
    UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
    CGRect rectInTableView = [tableView rectForRowAtIndexPath:indexPath];
    // 獲得點擊cell的frame
    CGRect rect = [tableView convertRect:rectInTableView toView:[tableView superview]];
    
   // 設(shè)置selectImageView的位置和圖片
    self.selectImageView.image = cell.imageView.image;
    self.selectImageView.frame = CGRectMake(cell.imageView.frame.origin.x, rect.origin.y, cell.imageView.frame.size.width, cell.imageView.frame.size.height);
    // 動畫
    [UIView animateWithDuration:0.5 animations:^{
        self.selectImageView.frame = CGRectMake(0, 64, self.view.bounds.size.width, self.view.bounds.size.width);
    } completion:^(BOOL finished) {
        [self.navigationController pushViewController:detail animated:YES];
    }];
}

獲取當前cell方法以及cell相對屏幕的位置兩個方法每次都忘記廉丽,所以加粗倦微,方便以后找。

上面代碼的效果圖:

步驟4-1.gif

現(xiàn)在的問題是返回的時候 self.selectImageView還在那里正压,所以需要在轉(zhuǎn)場結(jié)束后使 self.selectImageView消失欣福。
解決方法:

- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated
{
    if (viewController != self) {
        self.selectImageView.frame = CGRectNull;
    }
}

轉(zhuǎn)場后,設(shè)置frame為CGRectNull焦履,這樣就消失啦~

layer.mask屬性

其實這個mask屬性用到的地方還是蠻多的拓劝。比如新手引導(雖然現(xiàn)在都是圖片),還有微信的照片紅包嘉裤。下面說說這個屬性郑临。
mask是一個layer層,并且作為背景層和組成層之間的一個遮罩層通道屑宠,默認是nil厢洞。
還是在這個項目中,在列表控制器的- (void)viewDidLoad方法中加如下代碼

    CAShapeLayer *shapeLayer = [CAShapeLayer layer];
    shapeLayer.path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(100, 100, 200, 200)].CGPath;
    self.view.layer.mask = shapeLayer;

效果圖:

mask屬性-1.png

發(fā)現(xiàn)就只有l(wèi)ayer那一塊顯示出來侨把,其余全部白色了犀变。至于其余部分的顏色 是由 window.backgroundColor控制。
改成黑色:

mask屬性-2.png

當我代碼改成這樣:(一條線的時候)

    UIBezierPath *path = [UIBezierPath bezierPath];
    [path moveToPoint:CGPointMake(100, 100)];
    [path addLineToPoint:CGPointMake(100, 500)];
    
    CAShapeLayer *shapeLayer = [CAShapeLayer layer];
    shapeLayer.path = path.CGPath;
    shapeLayer.lineWidth = 20;
    self.view.layer.mask = shapeLayer;
mask屬性-3.png

發(fā)現(xiàn)不起作用秋柄,即使線寬為20获枝。
當代碼為三角形:

    UIBezierPath *path = [UIBezierPath bezierPath];
    [path moveToPoint:CGPointMake(100, 100)];
    [path addLineToPoint:CGPointMake(100, 500)];
    [path addLineToPoint:CGPointMake(200, 500)];
    [path closePath];
    
    CAShapeLayer *shapeLayer = [CAShapeLayer layer];
    shapeLayer.path = path.CGPath;
    shapeLayer.lineWidth = 20;
    self.view.layer.mask = shapeLayer;
mask屬性-4.png

綜上可以說明:layer的路徑必須要封閉才能起作用。

最后

本文github地址:https://github.com/JabberYQ/animatedTransitionDemo

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末骇笔,一起剝皮案震驚了整個濱河市省店,隨后出現(xiàn)的幾起案子嚣崭,更是在濱河造成了極大的恐慌,老刑警劉巖懦傍,帶你破解...
    沈念sama閱讀 222,000評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件雹舀,死亡現(xiàn)場離奇詭異,居然都是意外死亡粗俱,警方通過查閱死者的電腦和手機说榆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,745評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來寸认,“玉大人签财,你說我怎么就攤上這事∑” “怎么了唱蒸?”我有些...
    開封第一講書人閱讀 168,561評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長灸叼。 經(jīng)常有香客問我神汹,道長,這世上最難降的妖魔是什么古今? 我笑而不...
    開封第一講書人閱讀 59,782評論 1 298
  • 正文 為了忘掉前任屁魏,我火速辦了婚禮,結(jié)果婚禮上沧卢,老公的妹妹穿的比我還像新娘蚁堤。我一直安慰自己醉者,他們只是感情好但狭,可當我...
    茶點故事閱讀 68,798評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著撬即,像睡著了一般立磁。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上剥槐,一...
    開封第一講書人閱讀 52,394評論 1 310
  • 那天唱歧,我揣著相機與錄音,去河邊找鬼粒竖。 笑死颅崩,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的蕊苗。 我是一名探鬼主播沿后,決...
    沈念sama閱讀 40,952評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼朽砰!你這毒婦竟也來了尖滚?” 一聲冷哼從身側(cè)響起喉刘,我...
    開封第一講書人閱讀 39,852評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎漆弄,沒想到半個月后睦裳,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,409評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡撼唾,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,483評論 3 341
  • 正文 我和宋清朗相戀三年廉邑,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片倒谷。...
    茶點故事閱讀 40,615評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡鬓催,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出恨锚,到底是詐尸還是另有隱情宇驾,我是刑警寧澤,帶...
    沈念sama閱讀 36,303評論 5 350
  • 正文 年R本政府宣布猴伶,位于F島的核電站课舍,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏他挎。R本人自食惡果不足惜筝尾,卻給世界環(huán)境...
    茶點故事閱讀 41,979評論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望办桨。 院中可真熱鬧筹淫,春花似錦、人聲如沸呢撞。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,470評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽殊霞。三九已至摧阅,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間绷蹲,已是汗流浹背棒卷。 一陣腳步聲響...
    開封第一講書人閱讀 33,571評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留祝钢,地道東北人比规。 一個月前我還...
    沈念sama閱讀 49,041評論 3 377
  • 正文 我出身青樓,卻偏偏與公主長得像拦英,于是被迫代替她去往敵國和親蜒什。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,630評論 2 359

推薦閱讀更多精彩內(nèi)容