先看看系統(tǒng)日歷的Transition效果
這里面包含了好幾個動畫
- 在push的時候错邦,前面一個Controller從中間截斷店煞;后面的Conntroller則從中間位置上升蘑志。
- 在pop的時候手蝎,以相反的動畫顯示
- navigationBar包含了一個淡入淡出的效果
得益于iOS 7新增UIViewControllerAnimatedTransitioning趁餐,用現(xiàn)有的Core Animation就可用輕松實現(xiàn)散怖。值得一提的是菇绵,《iOS 7 by Tutorials》對這方面點講解非常透徹肄渗;還有VCTransitionsLibrary這個庫,實現(xiàn)了很多特效,我很多代碼就是參考他里面的實現(xiàn)。更深層次疤苹,WWDC 2013 #218——Custom Transitions Using View Controllers腺逛,然而對初學(xué)者并不適用。
要實現(xiàn)一個自定義的Transition,概括起來有3個步驟:
- 創(chuàng)建一個animation controller。這個controller只是一個普通NSObject對象,但要實現(xiàn)
UIViewControllerAnimatedTransitioning
協(xié)議硝桩,這個協(xié)議有一個重要的方法- (void)animateTransition:(id<UIViewControllerContextTransitioning> _Nonnull)transitionContext
,參數(shù)transitionContext對象包含了所有動畫需要的UIView - 設(shè)置self.navigationController.delegate枚荣。它的作用就是碗脊,在push、pop橄妆、present Controller時衙伶,返回上面的animation controller,替換系統(tǒng)默認(rèn)的效果
- 最后害碾,在animation controller實現(xiàn)動畫矢劲。
animation controller是transition的核心』潘妫可以理解它是一個臨時controller芬沉,在這個里面有一個containerView,以及前阁猜、后controller的view丸逸。把前、后的view放在containerView里做動畫剃袍,這就是Transition黄刚。
下面從0開始,一步一步完成這個自定義Transition民效。
首先建一個Single View的工程calenderAnimator隘击。我不太習(xí)慣用storyboard,一般我會刪掉它研铆,然后在AppDelegate.m加上下面代碼
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
ViewController *mainViewController = [[ViewController alloc] init];
UINavigationController *navigationController = [[UINavigationController alloc] initWithRootViewController:mainViewController];
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
[self.window setRootViewController:navigationController];
[self.window setBackgroundColor:[UIColor whiteColor]];
[self.window makeKeyAndVisible];
return YES;
}
增加一個UINavigationController,方便實現(xiàn)push和pop動作州叠。然后在ViewConrtoller.m里添加代碼
- (void)viewDidLoad {
[super viewDidLoad];
CGFloat r = RND_COLOR;
CGFloat g = RND_COLOR;
CGFloat b = RND_COLOR;
self.view.backgroundColor = [UIColor colorWithRed:r green:g blue:b alpha:1];
self.title = [NSString stringWithFormat:@"#%0X%0X%0X", (int)(r*255), (int)(g*255), (int)(b*255)];
UIImageView *imageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"CatInBin"]];
imageView.center = self.view.center;
[self.view addSubview:imageView];
}
- (void)viewDidAppear:(BOOL)animated
{
NSUInteger count = self.navigationController.viewControllers.count;
if (count > 1) {
self.navigationController.navigationBar.tintColor = self.navigationController.viewControllers[count - 2].view.backgroundColor;
}
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[self.navigationController pushViewController:[ViewController new] animated:YES];
}
給ViewController加上隨機背景色和一張圖片棵红。任意點擊一次就push一個新的ViewController。設(shè)置navigationController的tintColor咧栗,方便觀察上一個ViewController的背景色逆甜。
到目前為止虱肄,這些代碼都是我們熟悉的操作。Transition效果也是默認(rèn)的左右滑入交煞,下面就來實現(xiàn)這3個步驟咏窿。
步驟1:創(chuàng)建CalenderAnimationController
@interface CalenderAnimationController : NSObject <UIViewControllerAnimatedTransitioning>
@property (nonatomic, assign) BOOL reverse;
@end
@implementation CalenderAnimationController
- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext
{
return 1.0;
}
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIView *toView = toVC.view;
UIView *fromView = fromVC.view;
if(self.reverse){
[self executeReverseAnimation:transitionContext fromVC:fromVC toVC:toVC fromView:fromView toView:toView];
} else {
[self executeForwardsAnimation:transitionContext fromVC:fromVC toVC:toVC fromView:fromView toView:toView];
}
}
@end
reverse屬性用于區(qū)分push還是pop,對應(yīng)不同的動畫實現(xiàn)素征。CalenderAnimationController實現(xiàn)了兩個方法集嵌。transitionDuration:
返回的時動畫時長;animateTransition:
是動畫的回調(diào)方法御毅,這里我們通過2個key已經(jīng)拿到了fromView和toView根欧。
步驟2:設(shè)置delegate
在ViewController加上代碼
@interface ViewController () <UIViewControllerTransitioningDelegate, UINavigationControllerDelegate>
@property CalenderAnimationController *animationController;
@end
@implementation ViewController
- (void)viewDidLoad {
//保持原樣
self.animationController = [[CalenderAnimationController alloc] init];
}
- (void)viewDidAppear:(BOOL)animated
{
self.navigationController.delegate = self;
//保持原樣
}
- (id<UIViewControllerAnimatedTransitioning>) navigationController:(UINavigationController *)navigationController
animationControllerForOperation:(UINavigationControllerOperation)operation
fromViewController:(UIViewController *)fromVC
toViewController:(UIViewController *)toVC
{
self.animationController.reverse = (operation == UINavigationControllerOperationPop);
return self.animationController;
}
@end
在viewDidAppear:
設(shè)置navigationController.delegate = self。不能在viewDidLoad
設(shè)置端蛆,因為所有的ViewController共享一個navigationController凤粗,當(dāng)pop出去的controller被回收時,navigationController.delegate就會被置為nil今豆,也就沒有了動畫嫌拣。
ViewController新加了一個方法
- (nullable id <UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController
animationControllerForOperation:(UINavigationControllerOperation)operation
fromViewController:(UIViewController *)fromVC
toViewController:(UIViewController *)toVC NS_AVAILABLE_IOS(7_0);
這個方法返回前面創(chuàng)建出來的animationController,并通過operation得到當(dāng)前的操作類型呆躲。步驟2的主要目的异逐,就是讓系統(tǒng)回調(diào)這個方法。
步驟3:實現(xiàn)動畫
前面的所有工作只是鋪墊歼秽,為的是讓系統(tǒng)使用我們自己實現(xiàn)的動畫应役,而非默認(rèn)動畫。我們要的動畫有3個:1燥筷、前面的View分為兩半對開移動箩祥;2、后面的View從中間開始向上移動肆氓;3袍祖、導(dǎo)航條的Fade效果。
動畫3其實不需要寫任何代碼谢揪,當(dāng)我們接管了系統(tǒng)的transition蕉陋,導(dǎo)航條就自動有這個效果。動畫2用簡單的隱式動畫就能完成拨扶。動畫1沒有對應(yīng)的隱式動畫凳鬓,麻煩一點。如果把前面的View拆分為2個單獨的View患民,各自完成一個移動的動畫就簡單很多了缩举。而拆分的過程,用UIView的snapshot來截屏即可。
- (void)executeForwardsAnimation:(id<UIViewControllerContextTransitioning>)transitionContext fromVC:(UIViewController *)fromVC toVC:(UIViewController *)toVC fromView:(UIView *)fromView toView:(UIView *)toView
{
UIView *containerView = [transitionContext containerView];
[containerView addSubview:fromView];
fromView.frame = CGRectOffset(toView.frame, toView.frame.size.width, 0);
[containerView addSubview:toView];
CGRect upRegion = CGRectMake(0, 0, fromView.frame.size.width, fromView.frame.size.height/2);
UIView *upView = [fromView resizableSnapshotViewFromRect:upRegion afterScreenUpdates:NO withCapInsets:UIEdgeInsetsZero];
upView.frame = upRegion;
[containerView addSubview:upView];
CGRect downRegion = CGRectMake(0, fromView.frame.size.height/2, fromView.frame.size.width, fromView.frame.size.height/2);
UIView *downView = [fromView resizableSnapshotViewFromRect:downRegion afterScreenUpdates:NO withCapInsets:UIEdgeInsetsZero];
downView.frame = downRegion;
[containerView addSubview:downView];
toView.frame = CGRectOffset(toView.frame, 0, toView.frame.size.height/2);
NSTimeInterval duration = [self transitionDuration:transitionContext];
[UIView animateWithDuration:duration
delay:0.0
options:UIViewAnimationOptionCurveEaseOut
animations:^{
upView.frame = CGRectOffset(upView.frame, 0, -upView.frame.size.height);
downView.frame = CGRectOffset(downView.frame, 0, downView.frame.size.height);
toView.frame = CGRectOffset(toView.frame, 0, -toView.frame.size.height/2);
}
completion:^(BOOL finished) {
[upView removeFromSuperview];
[downView removeFromSuperview];
fromView.frame = containerView.frame;
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
}];
}
第一步仅孩,先獲得containerView托猩,再把fromView和toView添加進來。這一步是需要手動完成的辽慕,containerView默認(rèn)是沒有subview京腥。fromView我們先隱藏起來,等動畫完成了再顯示溅蛉。
接下來創(chuàng)建upView和downView公浪,利用iOS 7新加的- (UIView *)resizableSnapshotViewFromRect:(CGRect)rect afterScreenUpdates:(BOOL)afterUpdates withCapInsets:(UIEdgeInsets)capInsets
,它可以截取UIView任意的一部分温艇,拷貝到另一個UIView因悲。
真正的動畫部分就只有upView、downView和toView的frame操作勺爱,3行代碼晃琳。等動畫完成后,upView和downView就沒有存在的意義琐鲁,從contrainerView移除卫旱。再調(diào)用[transitionContext completeTransition:![transitionContext transitionWasCancelled]]
,通知系統(tǒng)transition全部完成围段。至此顾翼,animation controller就完成了它的使命。
- (void)executeReverseAnimation:(id<UIViewControllerContextTransitioning>)transitionContext fromVC:(UIViewController *)fromVC toVC:(UIViewController *)toVC fromView:(UIView *)fromView toView:(UIView *)toView
只是上一個動畫的反轉(zhuǎn)奈泪,在此就不貼代碼了适贸。