前言
什么是转场动画呢?顾名思义,就是切换界面所用的动画效果。本文主要介绍的是modal的效果。当我们使用modal的时候,只需要使用 presentViewController:animated:completion
和 dismissViewControllerAnimated:completion
即可实现界面的弹出和消失,但是这样的动画过于单调,只是简单的弹出消失,没有’艺术性’,嘿嘿嘿,如果我们想要炫酷复杂的动画,那就只有自定义动画了。
modal设置自定义转场步骤
1.设置属性和代理
属性
当我们使用普通的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;
2.实现代理方法
UIViewControllerTransitioningDelegate
在UIViewControllerTransitioningDelegate
中,我们主要使用两个方法.
- 弹出界面的回调方法
-(id<UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source{
return self;
}
- 消失界面的回调方法
- (id<UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed{
return self;
}
UIViewControllerAnimatedTransitioning
从上面的回调方法中我们发现,返回的对象必须遵守UIViewControllerAnimatedTransitioning
协议,因为上面的协议UIViewControllerTransitioningDelegate
是来设置动画的执行者的,而UIViewControllerAnimatedTransitioning
才是设置动画的,所以说,我们自定义的CLHPhotoBrowserAnimator必须遵守此协议,并实现其代理方法.
- 设置转场动画的执行时间
- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext{
return 0.5;
}
- 转场上下文,这里可以用来提供转场动画过程中两个控制器的信息
因为我们自定义了动画的执行,所以说系统无法自动调用这些方法,所以说我们可以设置一个BOOL属性presented来表示控制器的弹出和消失
@property(nonatomic, assign, getter=isPresented) BOOL presented;
我们要设置的转场动画有可能很复杂,所以说,我们可以将弹出和消失动画抽出两个方法,并将转场上下文传过去,这样代码看起来更好看,而且增加维护性.然后我们就可以在自定义的弹出动画方法中设置动画了,这个时候就需要你奇妙的脑洞了.
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)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
设置两种代理。
转场协议
如何来拿到图片放大之前的位置呢?从哪里拿到呢?下面我们一一解答
1.从哪里拿?
整个朋友圈我是用一个UITableView来实现的,tableView中一条动态又是一个cell,在cell中我设置了一个占位View(CLHWerChatPhotoView)来放置图片(请忽略我当时手速过快导致拼写错误 = =,然后在这个View中添加图片,当我们点击了图片的时候,我们也是在这个View中作出反应,所以说我们转场所需要的信息可以从这个View中拿,但是首先这个类必须先遵守转场协议photoBrowserAnimatorDelegate
//设置转场协议
self.animator.animationDelegate = self;
2.如何获得
- 获取开始位置
在占位的View中添加图片的时候,我给每一个图片加了一个tag,然后增加了点击手势,所以说,每当点击了图片,我可以将tag当做下标传过去,然后根据下标找到图片,得到图片在整个窗口的位置。
- (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即可
- (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;
}
消失协议
消失动画同样需要当前图片所处位置和下标,还有缩小后的位置,注意两个问题,一个是从哪里拿?二是怎么拿?
1.从哪里拿
我们自定义一个图片查看器的控制器CLHPhotoBrowserViewController
来显示放大后的图片,而且放大后的滚动等一系列动作都是在其中完成的,所以说动画的消失代理设置为CLHPhotoBrowserViewController
再合适不过了.
self.animator.animationDismissDelegate = photoVC;
2.怎么拿?
- 获取当前图片的下标
这个比较简单了,因为我实现朋友圈查看器是使用UICollectionView实现的,所以说每个cell的大小都是整个屏幕,所以说当前显示的图片的cell的下标即为图片的下标
- (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<UIViewControllerContextTransitioning>)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<UIViewControllerContextTransitioning>)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,我喂自己袋盐
有错误请评论指出或联系我,不胜感激!
个人邮箱:helloiamclh@gmail.com
转载请注明出处,毕竟写了这么多也不容易,谢谢!