轉(zhuǎn)場動畫(push)

iOS7 開始蘋果推出了自定義轉(zhuǎn)場的 API 衣赶。從此诊赊,任何可以用 CoreAnimation 實現(xiàn)的動畫,都可以出現(xiàn)在兩個 ViewController 的切換之間府瞄。并且實現(xiàn)方式高度解耦碧磅,這也意味著在保證代碼干凈的同時想要替換其他動畫方案時只需簡單改一個類名就可以了,真正體會了一把高顏值代碼帶來的愉悅感遵馆。

其實網(wǎng)上關(guān)于自定義轉(zhuǎn)場動畫的教程很多鲸郊,這里我是希望同學(xué)們能易懂,易上手货邓。

轉(zhuǎn)場分兩種Push和Modal,所以自定義轉(zhuǎn)場動畫也就肯定分兩種秆撮,今天我們講的是Push

自定義轉(zhuǎn)場動畫Push

首先搭建界面,添加4個按鈕:

- (void)addButton{

self.buttonArr = [NSMutableArray array];

CGFloat margin=50;

CGFloat width=(self.view.frame.size.width-margin*3)/2;

CGFloat height = width;

CGFloat x = 0;

CGFloat y = 0;

//列

NSInteger col = 2;

for (NSInteger i = 0; i < 4; i ++) {

x = margin + (i%col)*(margin+width);

y = margin + (i/col)*(margin+height) + 150;

UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];

button.frame = CGRectMake(x, y, width, height);

button.layer.cornerRadius = width * 0.5;

[button addTarget:self action:@selector(btnclick:) forControlEvents:UIControlEventTouchUpInside];

button.backgroundColor = [UIColor colorWithRed:arc4random()%255/255.0 green:arc4random()%255/255.0 blue:arc4random()%255/255.0 alpha:1.0];

button.tag = i+1;

[self.view addSubview:button];

[self.buttonArr addObject:button];

}

}

添加動畫:

- (void)setupButtonAnimation{

[self.buttonArr enumerateObjectsUsingBlock:^(UIButton * _Nonnull button, NSUInteger idx, BOOL * _Nonnull stop) {

// positionAnimation

CAKeyframeAnimation *positionAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"];

positionAnimation.calculationMode = kCAAnimationPaced;

positionAnimation.fillMode = kCAFillModeForwards;

positionAnimation.repeatCount = MAXFLOAT;

positionAnimation.autoreverses = YES;

positionAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];

positionAnimation.duration = (idx == self.buttonArr.count - 1) ? 4 : 5+idx;

UIBezierPath *positionPath = [UIBezierPath bezierPathWithOvalInRect:CGRectInset(button.frame, button.frame.size.width/2-5, button.frame.size.height/2-5)];

positionAnimation.path = positionPath.CGPath;

[button.layer addAnimation:positionAnimation forKey:nil];

// scaleXAniamtion

CAKeyframeAnimation *scaleXAniamtion = [CAKeyframeAnimation animationWithKeyPath:@"transform.scale.x"];

scaleXAniamtion.values = @[@1.0,@1.1,@1.0];

scaleXAniamtion.keyTimes = @[@0.0,@0.5,@1.0];

scaleXAniamtion.repeatCount = MAXFLOAT;

scaleXAniamtion.autoreverses = YES;

scaleXAniamtion.duration = 4+idx;

[button.layer addAnimation:scaleXAniamtion forKey:nil];

// scaleYAniamtion

CAKeyframeAnimation *scaleYAnimation = [CAKeyframeAnimation animationWithKeyPath:@"transform.scale.y"];

scaleYAnimation.values = @[@1,@1.1,@1.0];

scaleYAnimation.keyTimes = @[@0.0,@0.5,@1.0];

scaleYAnimation.autoreverses = YES;

scaleYAnimation.repeatCount = YES;

scaleYAnimation.duration = 4+idx;

[button.layer addAnimation:scaleYAnimation forKey:nil];

}];

}

界面搭建好了:

然后想在Push的時候?qū)崿F(xiàn)自定義轉(zhuǎn)場動畫首先要遵守一個協(xié)議UINavigationControllerDelegate

蘋果在 UINavigationControllerDelegate 中給出了幾個協(xié)議方法逻恐,通過返回類型就可以很清楚地知道各自的具體作用像吻。

//用來自定義轉(zhuǎn)場動畫

- (nullable id )navigationController:(UINavigationController *)navigationController

animationControllerForOperation:(UINavigationControllerOperation)operation

fromViewController:(UIViewController *)fromVC

toViewController:(UIViewController *)toVC ?NS_AVAILABLE_IOS(7_0);

//為這個動畫添加用戶交互

- (nullable id )navigationController:(UINavigationController *)navigationController

interactionControllerForAnimationController:(id ) animationController NS_AVAILABLE_IOS(7_0);

在第一個方法里只要返回一個準(zhǔn)守UIViewControllerInteractiveTransitioning協(xié)議的對象,并在里面實現(xiàn)動畫即可

創(chuàng)建繼承自 NSObject 并且聲明 UIViewControllerAnimatedTransitioning 的的動畫類。

重載 UIViewControllerAnimatedTransitioning 中的協(xié)議方法复隆。

//返回動畫時間

- (NSTimeInterval)transitionDuration:(nullable id )transitionContext;

//將動畫的代碼寫到里面即可

- (void)animateTransition:(id )transitionContext;

首先我自定義一個名為LRTransitionPushController的類繼承于NSObject準(zhǔn)守了UIViewControllerAnimatedTransitioning協(xié)議

- (void)animateTransition:(id)transitionContext{

self.transitionContext = transitionContext;

//獲取源控制器 注意不要寫成 UITransitionContextFromViewKey

LRTransitionPushController *fromVc = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];

//獲取目標(biāo)控制器 注意不要寫成 UITransitionContextToViewKey

LRTransitionPopController *toVc = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];

//獲得容器視圖

UIView *containView = [transitionContext containerView];

// 都添加到container中。注意順序 目標(biāo)控制器的view需要后面添加

[containView addSubview:fromVc.view];

[containView addSubview:toVc.view];

UIButton *button = fromVc.button;

//繪制圓形

UIBezierPath *startPath = [UIBezierPath bezierPathWithOvalInRect:button.frame];

//創(chuàng)建兩個圓形的 UIBezierPath 實例姆涩;一個是 button 的 size 挽拂,另外一個則擁有足夠覆蓋屏幕的半徑。最終的動畫則是在這兩個貝塞爾路徑之間進行的

//按鈕中心離屏幕最遠的那個角的點

CGPoint finalPoint;

//判斷觸發(fā)點在那個象限

if(button.frame.origin.x > (toVc.view.bounds.size.width / 2)){

if (button.frame.origin.y < (toVc.view.bounds.size.height / 2)) {

//第一象限

finalPoint = CGPointMake(0, CGRectGetMaxY(toVc.view.frame));

}else{

//第四象限

finalPoint = CGPointMake(0, 0);

}

}else{

if (button.frame.origin.y < (toVc.view.bounds.size.height / 2)) {

//第二象限

finalPoint = CGPointMake(CGRectGetMaxX(toVc.view.frame), CGRectGetMaxY(toVc.view.frame));

}else{

//第三象限

finalPoint = CGPointMake(CGRectGetMaxX(toVc.view.frame), 0);

}

}

CGPoint startPoint = CGPointMake(button.center.x, button.center.y);

//計算向外擴散的半徑 = 按鈕中心離屏幕最遠的那個角距離 - 按鈕半徑

CGFloat radius = sqrt((finalPoint.x-startPoint.x) * (finalPoint.x-startPoint.x) + (finalPoint.y-startPoint.y) * (finalPoint.y-startPoint.y)) - sqrt(button.frame.size.width/2 * button.frame.size.width/2 + button.frame.size.height/2 * button.frame.size.height/2);

UIBezierPath *endPath = [UIBezierPath bezierPathWithOvalInRect:CGRectInset(button.frame, -radius, -radius)];

//賦值給toVc視圖layer的mask

CAShapeLayer *maskLayer = [CAShapeLayer layer];

maskLayer.path = endPath.CGPath;

toVc.view.layer.mask = maskLayer;

CABasicAnimation *maskAnimation =[CABasicAnimation animationWithKeyPath:@"path"];

maskAnimation.fromValue = (__bridge id)startPath.CGPath;

maskAnimation.toValue = (__bridge id)endPath.CGPath;

maskAnimation.duration = [self transitionDuration:transitionContext];

maskAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];

maskAnimation.delegate = self;

[maskLayer addAnimation:maskAnimation forKey:@"path"];

}

在控制器里面用來自定義轉(zhuǎn)場動畫的方法里返回剛才自定義的動畫類

- (id)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC{

if (operation == UINavigationControllerOperationPush) {

return [LRTranstionAnimationPush new];

}else{

return nil;

}

}

到此為止自定義轉(zhuǎn)場動畫就完成了

pop的動畫只是把push動畫反過來做一遍這里就不細講了骨饿,有疑問的可以去看代碼

添加滑動返回手勢

上面說到這個方法是為這個動畫添加用戶交互的所以我們要在pop時實現(xiàn)滑動返回

最簡單的方式應(yīng)該就是利用UIKit提供的UIPercentDrivenInteractiveTransition類了亏栈,這個類已經(jīng)實現(xiàn)了UIViewControllerInteractiveTransitioning協(xié)議,同學(xué)men可以通過這個類的對象指定轉(zhuǎn)場動畫的完成百分比宏赘。

//為這個動畫添加用戶交互

- (nullable id )navigationController:(UINavigationController *)navigationController

interactionControllerForAnimationController:(id ) animationController NS_AVAILABLE_IOS(7_0);

第一步 添加手勢

UIPanGestureRecognizer *gestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePan:)];

[self.view addGestureRecognizer:gestureRecognizer];

第二步 通過用戶滑動的變化確定動畫執(zhí)行的比例

- (void)handlePan:(UIPanGestureRecognizer *)gestureRecognizer {

/*調(diào)用UIPercentDrivenInteractiveTransition的updateInteractiveTransition:方法可以控制轉(zhuǎn)場動畫進行到哪了绒北,

當(dāng)用戶的下拉手勢完成時,調(diào)用finishInteractiveTransition或者cancelInteractiveTransition察署,UIKit會自動執(zhí)行剩下的一半動畫闷游,

或者讓動畫回到最開始的狀態(tài)。*/

if ([gestureRecognizer translationInView:self.view].x>=0) {

//手勢滑動的比例

CGFloat per = [gestureRecognizer translationInView:self.view].x / (self.view.bounds.size.width);

per = MIN(1.0,(MAX(0.0, per)));

if (gestureRecognizer.state == UIGestureRecognizerStateBegan) {

self.interactiveTransition = [UIPercentDrivenInteractiveTransition new];

[self.navigationController popViewControllerAnimated:YES];

} else if (gestureRecognizer.state == UIGestureRecognizerStateChanged){

if([gestureRecognizer translationInView:self.view].x ==0){

[self.interactiveTransition updateInteractiveTransition:0.01];

}else{

[self.interactiveTransition updateInteractiveTransition:per];

}

} else if (gestureRecognizer.state == UIGestureRecognizerStateEnded || gestureRecognizer.state == UIGestureRecognizerStateCancelled){

if([gestureRecognizer translationInView:self.view].x == 0){

[self.interactiveTransition cancelInteractiveTransition];

self.interactiveTransition = nil;

}else if (per > 0.5) {

[ self.interactiveTransition finishInteractiveTransition];

}else{

[ self.interactiveTransition cancelInteractiveTransition];

}

self.interactiveTransition = nil;

}

} else if (gestureRecognizer.state == UIGestureRecognizerStateChanged){

[self.interactiveTransition updateInteractiveTransition:0.01];

[self.interactiveTransition cancelInteractiveTransition];

} else if ((gestureRecognizer.state == UIGestureRecognizerStateEnded || gestureRecognizer.state == UIGestureRecognizerStateCancelled)){

self.interactiveTransition = nil;

}

}

第三步 在為動畫添加用戶交互的代理方法里返回UIPercentDrivenInteractiveTransition的實例

- (id )navigationController:(UINavigationController *)navigationController

interactionControllerForAnimationController:(id ) animationController {

return self.interactiveTransition;

}

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末贴汪,一起剝皮案震驚了整個濱河市脐往,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌扳埂,老刑警劉巖业簿,帶你破解...
    沈念sama閱讀 219,366評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異阳懂,居然都是意外死亡梅尤,警方通過查閱死者的電腦和手機柜思,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,521評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來巷燥,“玉大人酝蜒,你說我怎么就攤上這事》龋” “怎么了亡脑?”我有些...
    開封第一講書人閱讀 165,689評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長邀跃。 經(jīng)常有香客問我霉咨,道長,這世上最難降的妖魔是什么拍屑? 我笑而不...
    開封第一講書人閱讀 58,925評論 1 295
  • 正文 為了忘掉前任途戒,我火速辦了婚禮,結(jié)果婚禮上僵驰,老公的妹妹穿的比我還像新娘喷斋。我一直安慰自己,他們只是感情好蒜茴,可當(dāng)我...
    茶點故事閱讀 67,942評論 6 392
  • 文/花漫 我一把揭開白布星爪。 她就那樣靜靜地躺著,像睡著了一般粉私。 火紅的嫁衣襯著肌膚如雪顽腾。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,727評論 1 305
  • 那天诺核,我揣著相機與錄音抄肖,去河邊找鬼。 笑死窖杀,一個胖子當(dāng)著我的面吹牛漓摩,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播入客,決...
    沈念sama閱讀 40,447評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼管毙,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了痊项?” 一聲冷哼從身側(cè)響起锅风,我...
    開封第一講書人閱讀 39,349評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎鞍泉,沒想到半個月后皱埠,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,820評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡咖驮,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,990評論 3 337
  • 正文 我和宋清朗相戀三年边器,在試婚紗的時候發(fā)現(xiàn)自己被綠了训枢。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,127評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡忘巧,死狀恐怖恒界,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情砚嘴,我是刑警寧澤十酣,帶...
    沈念sama閱讀 35,812評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站际长,受9級特大地震影響耸采,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜工育,卻給世界環(huán)境...
    茶點故事閱讀 41,471評論 3 331
  • 文/蒙蒙 一虾宇、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧如绸,春花似錦嘱朽、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,017評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至蜕提,卻和暖如春森书,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背谎势。 一陣腳步聲響...
    開封第一講書人閱讀 33,142評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留杨名,地道東北人脏榆。 一個月前我還...
    沈念sama閱讀 48,388評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像台谍,于是被迫代替她去往敵國和親须喂。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,066評論 2 355

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