什么是转场动画呢?顾名思义,就是切换界面所用的动画效果。本文主要介绍的是modal的效果。当我们使用modal的时候,只需要使用 presentViewController:animated:completion
和 dismissViewControllerAnimated:completion
即可实现界面的弹出和消失,但是这样的动画过于单调,只是简单的弹出消失,没有’艺术性’,嘿嘿嘿,如果我们想要炫酷复杂的动画,那就只有自定义动画了。
当我们使用普通的modal进行转场的时候,是这样的
//创建要跳转的控制器
CLHPhotoBrowserViewController *photoVC = [[CLHPhotoBrowserViewController alloc] init];
//跳转控制器
[self.window.rootViewController presentViewController:photoVC animated:YES completion:nil];
但是如果我们要自定义modal转场动画,那么需要设置ViewController的modalPresentationStyle属性为UIModalPresentationCustom.即自定义状态,因为系统默认的属性为UIModalPresentationFullScreen,即充满屏幕.
photoVC.modalPresentationStyle = UIModalPresentationCustom;
从iOS7之后,苹果推出了真正可以实现转场动画的API,这才让我们有了那么多酷炫的界面效果和动画,转场呢,又分为交互式转场和非交互式转场,本文主要讲解非交互式转场,因为这种转场可以让我们完全控制转场所利用的界面和动画。
自定义转场其实也很简单,只是实现一个协议而已,如果我们使用modal进行转场,那么我们必须设置我们要跳转控制器的transitioningDelegate
,而要成为跳转控制器的代理,需要遵守UIViewControllerTransitioningDelegate
协议.为了防止代码量太多而导致不好管理,所以说我一般自定义一个类,来管理转场动画,这里我们使用一个CLHPhotoBrowserAnimator
类来管理转场动画.
//将自定义类设置为转场代理
photoVC.transitioningDelegate = self.animator;
在UIViewControllerTransitioningDelegate
中,我们主要使用两个方法.
-(id)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source{
return self;
}
- (id)animationControllerForDismissedController:(UIViewController *)dismissed{
return self;
}
从上面的回调方法中我们发现,返回的对象必须遵守UIViewControllerAnimatedTransitioning
协议,因为上面的协议UIViewControllerTransitioningDelegate
是来设置动画的执行者的,而UIViewControllerAnimatedTransitioning
才是设置动画的,所以说,我们自定义的CLHPhotoBrowserAnimator必须遵守此协议,并实现其代理方法.
- (NSTimeInterval)transitionDuration:(id)transitionContext{
return 0.5;
}
@property(nonatomic, assign, getter=isPresented) BOOL presented;
- (void)animateTransition:(id)transitionContext{
if(self.isPresented){
//调用自定义弹出动画
[self animationForPresentView:transitionContext];
} else{
//调用自定义消失动画
[self animationForDismissView:transitionContext];
}
}
实现效果
我们发现如果在微信点击查看图片,那个图片是从小到大,然后充满屏幕的(如果可以充满屏幕的话),如果再次点击图片,图片由全屏又变回它应该在的位置,所以说关键点就在于在进行转场的时候我们要拿到图片放大之前的位置和放大之后的位置还有当前图片,在消失的时候我们要拿到图片的信息和当前所浏览图片缩小后应该处于的位置。
分析之后,我们一步一步来
总体设计我们使用代理的设计模式,所以在自定义类CLHPhotoBrowserAnimator
中设置两个协议,一个是转场协议photoBrowserAnimatorDelegate
一个是消失协议photoBrowserAnimatorDismissDelegate
首先我们先看一下转场协议。
@protocol photoBrowserAnimatorDelegate <NSObject>
//获取开始位置
- (CGRect)startRect:(NSInteger)index;
//获取放大后的位置
- (CGRect)endRect:(NSInteger)index;
//获取当前图片
- (UIImageView *)locImageView:(NSInteger)index;
@end
@protocol photoBrowserAnimatorDismissDelegate <NSObject>
//获取当前图片的下标
- (NSInteger)indexForDismissView;
//获取当前图片
- (UIImageView *)imageViewForDismissView;
@end
当写好协议之后,当我们设置自定义转场的时候,就可以将给自定义类CLHPhotoBrowserAnimator
设置两种代理。
如何来拿到图片放大之前的位置呢?从哪里拿到呢?下面我们一一解答
整个朋友圈我是用一个UITableView来实现的,tableView中一条动态又是一个cell,在cell中我设置了一个占位View(CLHWerChatPhotoView)来放置图片(请忽略我当时手速过快导致拼写错误 = =,然后在这个View中添加图片,当我们点击了图片的时候,我们也是在这个View中作出反应,所以说我们转场所需要的信息可以从这个View中拿,但是首先这个类必须先遵守转场协议photoBrowserAnimatorDelegate
//设置转场协议
self.animator.animationDelegate = self;
- (CGRect)startRect:(NSInteger)index{
UIImageView *imageView = nil;
for(NSInteger i = 0; i < self.subviews.count; i++){
if([self.subviews[i] isKindOfClass:[UIImageView class]]){//检验是否为图片
UIImageView *view = self.subviews[i];
if(view.tag == index){//查找点击的图片
imageView = view;
}
}
}
//返回当前的图片在窗口的位置
return [self convertRect:imageView.frame toView:[UIApplication sharedApplication].keyWindow];
}
- (CGRect)endRect:(NSInteger)index{
//拿到当前下标所对应的图片
UIImage *image = [UIImage imageNamed:self.photoArray[index]];
//计算imageView的frame
CGFloat x = 0;
CGFloat width = screenW;
CGFloat height = width / image.size.width * image.size.height;
CGFloat y = 0;
if(height < screenH){
y = (screenH - height) * 0.5;
}
return CGRectMake(x, y, width, height);
}
- (UIImageView *)locImageView:(NSInteger)index{
UIImageView *imageView = [[UIImageView alloc] init];
UIImage *image = [UIImage imageNamed:self.photoArray[index]];
imageView.image = image;
imageView.contentMode = UIViewContentModeScaleToFill;
imageView.clipsToBounds = YES;
return imageView;
}
消失动画同样需要当前图片所处位置和下标,还有缩小后的位置,注意两个问题,一个是从哪里拿?二是怎么拿?
我们自定义一个图片查看器的控制器CLHPhotoBrowserViewController
来显示放大后的图片,而且放大后的滚动等一系列动作都是在其中完成的,所以说动画的消失代理设置为CLHPhotoBrowserViewController
再合适不过了.
self.animator.animationDismissDelegate = photoVC;
- (NSInteger)indexForDismissView{
//获取当前显示在屏幕上的cell
CLHPhotoCell *cell = [_collectionView visibleCells].firstObject;
return [_collectionView indexPathForCell:cell].row;
}
- (UIImageView *)imageViewForDismissView{
UIImageView *imageView = [[UIImageView alloc] init];
CLHPhotoCell *cell = [_collectionView visibleCells].firstObject;
imageView.image = cell.imageView.image;
imageView.frame = cell.imageView.frame;
imageView.contentMode = UIViewContentModeScaleToFill;
imageView.clipsToBounds = YES;
return imageView;
}
我们可以通过转场上下文的viewForKey来获取到转场的源视图和目标视图
//自定义弹出动画
- (void)animationForPresentView:(id)transitionContext{
//获取要弹出的View
UIView *presentView = [transitionContext viewForKey:UITransitionContextToViewKey];
//将执行的View添加到上下文的containerView
[transitionContext.containerView addSubview:presentView];
//获取开始尺寸和结束尺寸
CGRect startRect = [self.animationDelegate startRect:self.index];
CGRect endRect = [self.animationDelegate endRect:self.index];
UIImageView *imageView = [self.animationDelegate locImageView:self.index];
//将图片添加到上下文的containerView
[transitionContext.containerView addSubview:imageView];
//设置初始frame
imageView.frame = startRect;
presentView.alpha = 0;
transitionContext.containerView.backgroundColor = [UIColor blackColor];
[UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
//设置图片的结束frame
imageView.frame = endRect;
}completion:^(BOOL finished) {
presentView.alpha = 1.0;
[imageView removeFromSuperview];
transitionContext.containerView.backgroundColor = [UIColor clearColor];
//一定要告诉转场上下文动画完成了
[transitionContext completeTransition:YES];
}];
}
//自定义消失动画
- (void)animationForDismissView:(id)transitionContext{
//获取要消失的view
UIView *dismissView = [transitionContext viewForKey:UITransitionContextFromViewKey];
[dismissView removeFromSuperview];
//获取要消失的图片
UIImageView *imageView = [self.animationDismissDelegate imageViewForDismissView];
[transitionContext.containerView addSubview:imageView];
//获取要消失的图片的下标
NSInteger index = [self.animationDismissDelegate indexForDismissView];
[UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
//以刚开始弹出的初始位置为动画后图片的frame
imageView.frame = [self.animationDelegate startRect:index];
} completion:^(BOOL finished) {
//告诉转场上下文动画完成
[transitionContext completeTransition:YES];
}];
}
modal自定义转场动画之微信朋友圈图片查看器的实战关键思路就是这些啦。
在学习了转场动画之后,是不是感觉很简单呢?以后我们就可以发挥自己神奇的脑洞来实现各种各样酷炫的动画效果了,有什么好的动画可以通知我哦,我会mark下来慢慢欣赏,谢谢。
那么就先说到这里啦,有疑问和不足请务必要通知我!!!同时也欢迎大家关注和顶一下!嘿嘿嘿!
Demo地址:GitHub
欢迎来到我的博客,我是AnICoo1,我喂自己袋盐
有错误请评论指出或联系我,不胜感激!
个人邮箱:[email protected]
转载请注明出处,毕竟写了这么多也不容易,谢谢!