如果你在开发中遇到需求,需要实现各种各样的转场动画,那么你可以看看这篇文章。当然,本文并没有实现各种各样的花式转场动画,而是实现了一种思路,抛砖引玉,希望你在看了本文之后能举一反三,随心所欲的定制自己喜欢的转场动画!(注意这里讲的实现仅仅支持iOS7 or later)
好了,效果也看到了(好像很丑~~)废话不多说,开撸!
自定义转场动画
我们都知道,苹果在转场动画给了我们自己实现的机会,那么在自己实现的时候就得按着苹果的要求来,也就是遵守相应的协议,下面看看这些协议:
- UIViewControllerContextTransitioning
- UIViewControllerAnimatedTransitioning
- UIViewControllerTransitioningDelegate
- UINavigationControllerDelegate
UIViewControllerContextTransitioning这个就是一个关于转场上下文对象的协议,它里面提供很多的方法,我们可以通过上下文获取到包括容器视图、要呈现的视图,以及当前的视图(源视图)等诸多有用信息。
UIViewControllerAnimatedTransitioning这个协议里面呢,就是提供了用户自己写转场动画相关的方法:
- (NSTimeInterval)transitionDuration:(nullable id )transitionContext;
这个方法可以提供转场动画的时间
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext;
这个方法就是主角了,动画核心就在这里面,该方法会去细节实现到底执行什么样的动画,待会再好好分析一下里面的实现。其实想想转场动画实现思路挺简单嘛,就这两个方法。然后再遵守协议(UIViewControllerAnimatedTransitioning)就完了(这仅仅是实现了自定义动画对象~~(姑且叫做动画对象),使用还有其他的步骤)
UIViewControllerTransitioningDelegate 这个协议呢,主要是管理转场动画的, 比如present 和dismiss这种转场,该协议里面的方法就是需要返回一个自定义的遵守了UIViewControllerAnimatedTransitioning协议的对象来作为present或者dismiss的动画对象。那么我们这里主要实现push、pop、present、dismiss这四种动画。那么在实现present 和dismiss这个自定义动画的时候呢就必须要用到这个协议,因为这里面提供了返回present、dismiss动画的动画对象的方法:
- (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source;
- (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed
那么我们在这两个方法分别返回之前自定义的不同类型(present、dismiss)的动画对象就可以了。而push、pop这种动画都是要依赖UINavigationController的,那么就得说说UINavigationControllerDelegate协议,该协议里面的方法多数和转场相关,其他还有屏幕方向相关的(这里不讨论)
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated;
- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated;
这两个方法都是转场时机方法,有需要可以在这合适的时机干点别的(我可是没干过~~)
- (nullable id <UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController
animationControllerForOperation:(UINavigationControllerOperation)operation
fromViewController:(UIViewController *)fromVC
toViewController:(UIViewController *)toVC NS_AVAILABLE_IOS(7_0);
好了push、pop动画主角上场了,看看该方法的返回值类型就知道之前实现的核心动画对象会成为这里的返回值,看看UINavigationControllerOperation,这是个枚举:
- UINavigationControllerOperationNone,
- UINavigationControllerOperationPush,
- UINavigationControllerOperationPop,
一目了然,那么我们在UINavigationControllerOperationPush的时候就返回‘push’类型的自定义动画对象,在UINavigationControllerOperationPop时就返回‘pop’对象不就可以了,是的,就这么简单,但是好像还有什么不完美的,系统的push、pop是支持边缘手势返回的,我们自定义了动画也得把这个加上啊(追求完美嘛~)苹果也是想到的哦,看看下面这个方法:
- (nullable id <UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController *)navigationController
interactionControllerForAnimationController:(id <UIViewControllerAnimatedTransitioning>) animationController NS_AVAILABLE_IOS(7_0)
{
if(((Animator *)animationController).animationType == AnimationTypeDismiss){
return self.percentTransition;
}
return nil;
}
这里先说一下,苹果是为我们准备好了这个类型UIPercentDrivenInteractiveTransition,这个类型的对象是遵守了UIViewControllerInteractiveTransitioning协议的,所以我们只需创建一个UIPercentDrivenInteractiveTransition类型的对象,在这个方法返回就可以了,但是我们还需要自己整一个边缘手势和这个对象关联起来
UIScreenEdgePanGestureRecognizer *pan = [[UIScreenEdgePanGestureRecognizer alloc] initWithTarget:self action:@selector(pan:)];
pan.edges = UIRectEdgeLeft;
[self.view addGestureRecognizer:pan];
- (void)pan:(UIScreenEdgePanGestureRecognizer *)sender
{
CGFloat progress = [sender translationInView:self.view].x / self.view.frame.size.width;//手指在屏幕滑动的进度百分百
if (sender.state == UIGestureRecognizerStateBegan) {
self.percentTransition = [[UIPercentDrivenInteractiveTransition alloc] init]; //开始滑动就初始化这个对象
[self.navigationController popViewControllerAnimated:YES];
}else if (sender.state == UIGestureRecognizerStateChanged){
[self.percentTransition updateInteractiveTransition:progress];
}else if (sender.state == UIGestureRecognizerStateCancelled || sender.state == UIGestureRecognizerStateEnded){
if (progress > 0.5) {
[self.percentTransition finishInteractiveTransition];
}else{
[self.percentTransition cancelInteractiveTransition];
}
self.percentTransition = nil;
[sender setTranslation:CGPointZero inView:self.view];
}
}
这样的话边缘手势返回也搞定了,有木有很简单~ 下面我们再来看看动画核心,这一块就非常的灵活了,动画炫不炫就看这里了,这里的话就提供如图的动画效果:
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
UIView *container = transitionContext.containerView;
self.transitionContext = transitionContext;
//为了兼容iOS7
UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
self.fromVC = fromVC;
UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
CGPoint startPoint = CGPointMake([UIApplication sharedApplication].keyWindow.center.x, [UIApplication sharedApplication].keyWindow.center.y); //这里用keyWindow和container都可以。感觉应该用container好些~~(尽量用container大小来做参照)
UIBezierPath *fromPath = [UIBezierPath bezierPathWithArcCenter:startPoint radius:1.0 startAngle:0.0 endAngle:M_PI * 2 clockwise:YES];
UIBezierPath *toPath = [UIBezierPath bezierPathWithArcCenter:startPoint radius:sqrt([UIScreen mainScreen].bounds.size.width *[UIScreen mainScreen].bounds.size.width + [UIScreen mainScreen].bounds.size.height * [UIScreen mainScreen].bounds.size.height) * 0.5 startAngle:0.0 endAngle:M_PI * 2 clockwise:YES];
if (self.animationType == AnimationTypePresent) {
//======================1=====================//
[container addSubview:toVC.view];
self.revealAnimation = [CABasicAnimation animationWithKeyPath:@"path"];
self.shapeLayer.path = toPath.CGPath;
self.revealAnimation.fromValue = (__bridge id )(fromPath.CGPath);
self.revealAnimation.toValue = (__bridge id )(toPath.CGPath);
self.revealAnimation.duration = [self transitionDuration:transitionContext];
self.revealAnimation.delegate = self;
[self.shapeLayer addAnimation:self.revealAnimation forKey:@"animation"];
toVC.view.layer.mask = self.shapeLayer;
// [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
// toVC.view.frame = container.bounds;
// } completion:^(BOOL finished) {
// [self.transitionContext completeTransition:finished]; //一定要加上转场结束
// }];
}else if(self.animationType == AnimationTypeDismiss){
//======================2=====================//
[container addSubview:toVC.view];
toVC.view.center = CGPointMake([UIApplication sharedApplication].keyWindow.center.x, [UIApplication sharedApplication].keyWindow.center.y - [UIApplication sharedApplication].keyWindow.bounds.size.height);
[UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
toVC.view.frame = container.bounds;
} completion:^(BOOL finished) {
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
}];
}
}
//动画结束
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
{
if (flag) {
self.fromVC.view.layer.mask = nil;
[self.transitionContext completeTransition:flag];
self.revealAnimation = nil;
self.shapeLayer = nil;
}else{
}
}
好了,要做自己的动画只需要把“1”、“2”部分替换自己的动画代码就ok了。
结束语
本文动画并不炫,只是将自定义转场动画理了一遍,希望起到抛砖引玉的作用,但愿对你有帮助~。如发现任何知识错误,请下方留言纠正,期待共同进步!!