感觉好久没写会动的Demo了,前几天写了很久的Block源码分析,分析了几天整个人都不好了,都不知道block是什么了......,有需要的同学可以去看看,简直不要太简单Block是什么鬼毕竟也是做电商的,有时候会研究别人家的App实现,有写过Higo的页面,也看了小红书的push动画,之前一直不知道怎么做到的,偶然间看到了转场动画自定义,原来是这么玩的,OK,今天以非常简单的方式带大家入门转场动画

老规矩,没图说个毛线,先看会儿动画在决定是否继续看。。。。。。

    

TNND,为什么只能上传2M的图!!!!!!



转场其实就是下一场景的VC视图替换当前场景VC视图以及控制器的过程,我们看到的系统自带的就是当前视图消失了,下个视图出现,这是非常基本的动画了转场动画不言而喻,最重要的其实是动画,学习了几天,简单分析下两种情况

1.就是最常见的UINavigationController中的push和pop

2.Modal转场,present以及dissmiss,支持的模式不多,UIModalPresentationCustom和

UIModalPresentationFullScreen



根据上面说的两种,也是最常用的,我们来自定义一下,看看能搞成什么样子。

Apple分离了很多协议出来支持转场动画,我们不深入研究,就用最简单的两种来实现,而且我只介绍我用到的代理,不用的就不BB了,不然看起来很乱
1.转场代理

<UINavigationControllerDelegate>  对应UINavigationController delegate属性遵守该协议
<UIViewControllerTransitioningDelegate> 对应UIViewcontroller的transitioningDelegate属性遵守该协议

2.动画控制器(最核心的就是他了)
<UIViewControllerAnimatedTransitioning>
我们在这里通过转场上下文transitionContext来获取我们需要的View,然后可以添加视图以及执行动画

3。转场环境(这个是系统已经带上了,我们知道就好)
<UIViewControllerContextTransitioning>就是上面动画控制器获取的对象,提供了转场需要的数据和交互


知道有这三个代理,我们来简单举个例子,这样看起来舒服点

我们先拿UINavigationController举个例子
首先我们需要有个类来实现转场代理,有两个方法
第一:我们用一个一个UINavigationController的子类,来实现转场代理。在代理方法里面返回对应的动画对象
第二:我们可以弄一个NSObject的类型,实现转场代理,代码写的话可以在外部弄个属性强引用封装的代理对象,用storyBoard的话直接在IB页面拖个NSObject
类型的东东,和拖View一样拖进去就强引用了,然后把类型设置为刚才封装的代理对象就好了

然后我们看到这个方法是需要一个实现<UIViewControllerAnimatedTransitioning>协议的动画对象,就创建一个NSObject实现这个代理方法
这里有两个,一个是转场时间,一个就是真正的转场动画的实现方法,这里你就需要转场环境transitionContext进行布局以及动画交互,然后把你需要的动画写出来就好了

再拿UIViewcontroller的present来举个例子
首先一样,我们需要实现转场代理,这里也有很多方法
第一:让self成为transitioningDelegate的代理,然后实现转场代理,同样里面返回对应的动画对象
第二:用UIPresentationController系统提供的类来全权保管需要的两个转场代理和动画代理,这类能管理view的各种属性和时间点,我们只需要present的时候
把代理只想继承于UIPresentationController对象的子类就好了,然后我们需要实现的动画就出来了

针对上面举得两个例子,我们等下分别介绍第一种方法




根据图片来看,push和present还是有结构上的区别的,有个点要特别注意,不然你的页面就黑了

push和present其实不只是上面代理的区别,我们在实现UIViewControllerAnimatedTransitioning这个协议的动画控制器里面通过转场环境来操作的时候就需要注意了

push操作的转场动画在动画结束之后,presenting(当前页面)是跟着消失了,但是present就蛋疼了,有两种情况,当在这个模式下的时候UIModalPresentationFullScreen和push一样,会先移除视图,然后dismiss的时候后再重新进来,当在UIModalPresentationCustom的时候,看上面的图来说就是presneting未参与转场,所以动画结束之后它未移出视图结构,在dismiss的时候,就会有差异。

差异就是,当我们push还是pop的时候都会从转场环境

(id<UIViewControllerContextTransitioning>)transitionContext拿出toView来,然后add到containerView里面

虽然我也不知道这里containerView是怎么工作的......如果是UIModalPresentationCustom这个模式下dismiss的时候,你再add进去,就黑了,他本身就不会参与转场,而且也不会从视图结构中移除,所以这个点还是要小心


知识点:

1.小红书push转场动画以及图片上的小动画

2.present自定义小窗口模式的动画

3.离子发射器做下雪动画

4.碎屏美女

5.还有一个非常简单的cell切换以及瀑布流


第一步:瀑布流搭建以及下雪动画

瀑布流的效果这里不详细介绍了需要的同学请戳我另一篇点击打开链接

下雪动画这个东西我用的是CAEmitterLayer和CAEmitterCell这套东西做的,本来这东西我是做成点赞之后爆炸的效果

但是改着改着感觉能发射特效,还是蛮好玩的,就改了下属性,就能下雪了,只要记住cell是小颗粒,layer是容器,类似于tableView的cell,这样想去改属性就能得到自己想要的效果了

self.clipsToBounds = NO; // 让例子能弹出固定的框框,不然只有在框框内而已self.userInteractionEnabled = NO; // 取消交互,不然点不到CAEmitterCell *cell = [CAEmitterCell emitterCell];cell.contents = (id)[UIImage imageNamed:@"favorite_hl"].CGImage; // 这里要的是CGImagecell.name = @"snow";cell.lifetime = 10.0f;cell.lifetimeRange = 100.0;// 隐掉的时间// 初始化右多少个cell.birthRate = 10;cell.velocity = 10.0f; // 越大越往外 速度越快cell.velocityRange = 10.0f; // 越小就越接近圆形,越大越不规则cell.emissionRange = M_PI_2; // 发散的范围cell.yAcceleration = 2;cell.scale = 0.5f; // 栗子倍数cell.scaleRange = 0.02;_emitterLayer = [CAEmitterLayer layer];_emitterLayer.emitterShape = kCAEmitterLayerLine; // layer的形状_emitterLayer.emitterMode = kCAEmitterLayerSurface; // layer的弹出方式_emitterLayer.emitterSize = CGSizeMake(self.bounds.size.width * 2, 100);_emitterLayer.renderMode = kCAEmitterLayerOldestFirst;_emitterLayer.masksToBounds = NO;_emitterLayer.emitterCells = @[cell];_emitterLayer.frame = [UIScreen mainScreen].bounds;_emitterLayer.emitterPosition = CGPointMake(100, -40);[self.layer addSublayer:_emitterLayer];

第二步:实现push自定义转场动画前代理设置

1.自定义UINavigationController的子类,实现UINavigationDelegate

- (void)pushViewController:(UIViewController *)viewControllerimageView:(UIImageView *)imageViewdesRec:(CGRect)desRecoriginal:(CGRect)originalRecdeleagte:(id<MKJAnimatorDelegate>)delegate isRight:(BOOL)isRight;

根据属性也能看出,我们自定义的方法把原来的位置,目标位置,图片以及presentedVC传进去了,VC作为参数是之后设置代理用的

2.实现NavigationDelegate的方法 返回自定义动画控制器

- (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationControlleranimationControllerForOperation:(UINavigationControllerOperation)operationfromViewController:(UIViewController *)fromVCtoViewController:(UIViewController *)toVC
{// 普通push的时候就是小红书类型的MKJAnimator *animator = [[MKJAnimator alloc] init];animator.imageView = self.imageView;animator.destinationRec = self.destinationRec;animator.originalRec = self.originalRec;animator.isPush = self.isPush;animator.delegate = self.animationDelegate;// 左侧push的时候是屏幕碎裂类型的MKJSuperAnimation *superAnimator = [[MKJSuperAnimation alloc] init];if (self.isRight) {return animator;}else{return superAnimator;}
}

第三步:实现Push真正的自定义转场控制组件

1.创建一个对象遵循<UIViewcontrollAnimatedTransioning>代理,并生成一个动画的结束代理,让presented(push过去的VC)控制器来实现,让动画结束的时候做一系列操作

@protocol MKJAnimatorDelegate <NSObject>- (void)animationFinish;@end@interface MKJAnimator : NSObject <UIViewControllerAnimatedTransitioning>@property (nonatomic,strong) UIImageView *imageView;
@property (nonatomic,assign) CGRect destinationRec;
@property (nonatomic,assign) CGRect originalRec;
@property (nonatomic,assign) BOOL isPush;
@property (nonatomic,assign) id<MKJAnimatorDelegate>delegate;@end

2.实现动画的核心代理方法持续时间和动画效果(核心代码)

- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{// 获取上面那张图的containerVIewUIView* contentView = [transitionContext containerView];contentView.backgroundColor = [UIColor whiteColor];// toView就是push过去的ViewUIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];// 加到容器View里面[contentView addSubview:toViewController.view];// 先设置为透明toViewController.view.alpha = 0;// 弄一个做动画的UIImageView__block UIImageView *imageView = [[UIImageView alloc] initWithImage:self.imageView.image];imageView.frame = self.isPush ? self.originalRec : self.destinationRec;[contentView addSubview:imageView];if (!self.isPush) {self.imageView.alpha = 0;}// 让临时的ImageView根据坐标动起来 然后把toView也渐渐显示[UIView animateWithDuration:0.5 animations:^{imageView.frame = self.isPush ? self.destinationRec : self.originalRec;toViewController.view.alpha = 1.0f;} completion:^(BOOL finished) {// 动画结束必定要告知这个对象结束了,不然问题[transitionContext completeTransition:![transitionContext transitionWasCancelled]];[imageView removeFromSuperview];if (!self.isPush) {self.imageView.alpha = 1;}else{// 告诉代理结束了if (self.delegate) {[self.delegate animationFinish];}}imageView = nil;}];
}


第四步:实现Present Modal效果的自定义动画小弹窗

1.这里接着刚才的效果,动画结束之后先给个图片,然后实现小小的动画

// <MKJAnimatorDelegate> 代理的实现,通知动画结束
- (void)animationFinish
{// 图片往上平铺[self.mainImageView sd_setImageWithURL:[NSURL URLWithString:self.redBookModel.img]];// 然后做个简单的小动画显示圆点,直线和文字[self showAnimationTag];
}

2.点击右上角实现present自定义小弹窗

还是两步走,先实现UIViewControllerTransioningDelegate,让自身成为代理,而且也可以看到,和刚才一样返回一个自定义的动画组件即可

- (void)click1:(UIButton *)button
{ThirdViewController *thirdVC = [[ThirdViewController alloc] init];thirdVC.modalPresentationStyle = UIModalPresentationCustom; // 貌似只支持几种模式,自己写还是直接用custom好了thirdVC.transitioningDelegate =  self; // 自己成为代理,返回动画[self presentViewController:thirdVC animated:YES completion:nil];
}#pragma - present的时候需要的代理方法
#pragma mark - UIViewControllerTransitioningDelegate
- (id<UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source
{return [MKJPresentAnimator new];
}- (id<UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed
{return [MKJPresentAnimator new];
}

然后再进行关键动画的组装,这里就是刚才提到了push和present的区别,当modal属性是custom的时候,present 之后的动画是不会把presentingVC给移出的,那么当dismiss的时候千万别再把toView通过AddSubview到

ContainerView里面去了

- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext
{return 0.45f;
}- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{// 这个是present的动画UIView *containerView = transitionContext.containerView;UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];UIView *toView = toViewController.view;UIView *fromView = fromViewController.view;// 这里present不和push一样需要自己标记,这里直接有跟踪标记if (toViewController.beingPresented){// toView加进来,设置居中的小框,宽度为1[containerView addSubview:toView];toView.bounds = CGRectMake(0, 0, 1, kScreenHeight *2/3);toView.center = containerView.center;// 然后再加个蒙层 大小也是很小UIView *dimmingView = [[UIView alloc] init];dimmingView.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.3];dimmingView.center = containerView.center;dimmingView.bounds = CGRectMake(0, 0, kScreenWidth * 3 / 5, kScreenHeight * 3 / 5);[containerView insertSubview:dimmingView belowSubview:toView];// 动画toView开始变宽  蒙层开始变大[UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{toView.bounds = CGRectMake(0, 0, kScreenWidth * 3 / 5, kScreenHeight *1/2);dimmingView.bounds = containerView.bounds;} completion:^(BOOL finished) {[transitionContext completeTransition:![transitionContext transitionWasCancelled]];}];}else{// 返回的时候直接放fromView动画缩小就行了,提交之后自动消失了,不需要在进行Add了,不然就黑了[UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{fromView.bounds = CGRectMake(0, 0, 1, kScreenHeight * 1 / 2);} completion:^(BOOL finished) {[transitionContext completeTransition:![transitionContext transitionWasCancelled]];}];}}

3.小弹窗里面的用Masonry实现的小动画(按钮旋转缩小以及View扩大)

- (void)viewDidAppear:(BOOL)animated
{[super viewDidAppear:animated];// 给背景蒙层加手势for (UIView *dimmingView in self.view.superview.subviews) {if (![dimmingView isEqual:self.view]){[dimmingView addGestureRecognizer:self.tap];}}self.TFWidthConstraint.constant = self.view.bounds.size.width * 2 /3;self.labelWidthConstraint.constant = self.view.bounds.size.width * 2 / 3;[UIView animateWithDuration:0.3 animations:^{self.closeButton.alpha = 1.0f;[self.view layoutIfNeeded];} completion:^(BOOL finished) {}];
}
- (IBAction)close:(id)sender {self.TFWidthConstraint.constant = 0;self.labelWidthConstraint.constant = 0;CGAffineTransform form = CGAffineTransformMakeRotation(M_PI);form = CGAffineTransformScale(form, 0.1, 0.1);[UIView animateWithDuration:0.8 animations:^{self.closeButton.transform = form;[self.view layoutIfNeeded];} completion:^(BOOL finished) {[self dismissViewControllerAnimated:YES completion:nil];}];
}


第五步:动画组件实现Push自定义的碎屏动画

依旧是两步走,实现UINavitgationController代理刚才已经介绍了,无非就是弄个代理方法,看了那么多,这个效果其实最关键的还是动画的实现,你脑洞大,那么动画必然就很酷炫,前提是你代码能力比脑洞还大,伪代码

- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{// 获取两个VC和对应的View1.拿出需要的UIView// push是要判断的,present是有属性跟踪的2.BOOL isPush 是否push?// 截屏3.截屏UIGraphicsBeginImageContextWithOptions(containerView.bounds.size, YES, containerView.window.screen.scale);[xxx drawViewHierarchyInRect:containerView.bounds afterScreenUpdates:NO];// 加一层动画的View,这里会有layer一个个铺在上面,是原始切割数量的两倍,正面是from,背面是to,事先摆好位置4.[containerView addSubview:transitionContainerView];5.计算二位数组切割后CGFloat sliceSize = round(CGRectGetWidth(containerView.bounds) / 8.f);NSUInteger xSlices = ceil(CGRectGetWidth(containerView.bounds) / sliceSize);NSUInteger ySlices = ceil(CGRectGetHeight(containerView.bounds) / sliceSize);// 防止layer,layer是加到小格子View上面的,但是layer是全屏的,所以每个小格子要对应上必须进行负数偏移,不然图像都不完整了6.布局for (NSUInteger y = 0 ; y < ySlices; y++){for (NSUInteger x = 0; x < xSlices; x++){CALayer1CALayer2UIView *toCheckboardSquareView[toCheckboardSquareView.layer addSublayer:toContentLayer];UIView *fromCheckboardSquareView[fromCheckboardSquareView.layer addSublayer:fromContentLayer];[transitionContainerView addSubview:toCheckboardSquareView];[transitionContainerView addSubview:fromCheckboardSquareView];}}// 上面是to和from的View交错放置,所以这个时候需要*2来拿出各自的View__block NSUInteger sliceAnimationsPending = 0;for (NSUInteger y = 0 ; y < ySlices; y++){for (NSUInteger x = 0; x < xSlices; x++){7.计算时间........// 通过上面定的时间计算持续时间以及延迟时间NSTimeInterval startTime = projectionLength/(transitionVectorLength + transitionSpacing) * transitionDuration;NSTimeInterval duration = ( (projectionLength + transitionSpacing)/(transitionVectorLength + transitionSpacing) * transitionDuration ) - startTime;sliceAnimationsPending++;[UIView animateWithDuration:duration delay:startTime options:0 animations:^{8.开始动画。。。。。。} completion:^(BOOL finished) {// Finish the transition once the final animation completes.if (--sliceAnimationsPending == 0)9. 结束动画。。。。。。}
}

总结下:

1.present和push动画是有所区别的,结构可以看上面的图,有的时候不区分是会出问题

2.自定义无论哪种,第一步就是实现该容易控制器代理的方法返回自定义动画组件(该组件必须实现指定代理)

第二步就是实现自定义动画控制器

3.CAEmitterLayer可以实现很多离子特效,这里简单介绍了两个

这个只是非常简单的两步走实现自定义转场动画,先有个基本了解,在进行更高层次的自定义效果就会更好,这些动画对性能来讲肯定会有所影响,有这样的想法必然就回去用GPUImage和OpenGL来实现图形动画,这些实现的效果等以后有空了在去研究分析下

简单的介绍了下流程,反正我看东西还是喜欢看Demo,需要的同学点下面的链接直接下载看吧

文章Demo链接:点击打开本文Demo链接

参考唐巧大神的理论知识文章:点击打开链接




iOS 自定义转场动画实现小红书的push效果思路以及下雪碎屏等动画的实现相关推荐

  1. iOS 自定义转场动画浅谈

    代码地址如下: http://www.demodashi.com/demo/11612.html 路漫漫其修远兮,吾将上下而求索 前记 想研究自定义转场动画很久了,时间就像海绵,挤一挤还是有的,花了差 ...

  2. 反编译简书app和小红书app滑动效果sticky粘性头布局的实现CoordinatorLayout+behavior

    反编译简书app和小红书app滑动效果sticky粘性头布局的实现CoordinatorLayout+behavior 小红书效果: 简书效果: demo效果图: github地址:https://g ...

  3. android 模拟滑动app,反编译简书app和小红书app滑动效果sticky粘性头布局的实现CoordinatorLayout+behavior...

    反编译简书app和小红书app滑动效果sticky粘性头布局的实现CoordinatorLayout+behavior 小红书效果: xiaohongshuu.gif 简书效果: jianshug.g ...

  4. 小红书运营模式和思路解析,新手账号运营实操必看

    随着互联网消费能力不断增强,随着数字消费实力的不断叠加,很多商家或者品牌也看到了小红书等网络社交平台所带来的巨量流量.今天就来和大家分享一下小红书运营模式和思路,小红书的流量入口是什么? 要想弄清楚小 ...

  5. iOS 自定义转场动画初探

    最近项目刚迭代,正好闲下来捣鼓了一下iOS的自定义转场的效果.闲话不多说,直接开始上代码吧.(ps:请忽略实际的转场效果,关注技术本身呢哦.pps:主要是转场的动画做的比较low啦!) 1.首先定义一 ...

  6. iOS 自定义转场动画, nav的push/pop自定义动画

    本文记录分享下自定义转场动画的实现方法,具体到动画效果:新浪微博图集浏览转场效果.手势过渡动画.网易音乐启动屏转场动画.开关门动画.全屏侧滑返回效果 的代码可以到Github WSLTransferA ...

  7. iOS自定义转场动画实战讲解

    转场动画这事,说简单也简单,可以通过presentViewController:animated:completion:和dismissViewControllerAnimated:completio ...

  8. iOS 自定义转场动画篇

    前言: 自定义转场动画其实并不难, 关键在于能够明白思路, 也就是操作步骤. 本篇博客主要以present转场动画为例, 进行分析, 操作, 如有错误欢迎简信与我交流. 不进行修改的话, presen ...

  9. 3d翻转 ios_iOS自定义转场详解04——实现3D翻转效果

    这是自定义转场系列的第四篇.由于具有一定的连续性,我会忽略一些基础,所以如果你是第一次看这个系列,可以先过目之前的几篇 --- UIViewControllerTransitioning的用法 .实现 ...

最新文章

  1. 孙庆新:做产品,感觉从何而来
  2. 关于 MSDTC 分布式事务两个常见错误
  3. python中select用法_Python select及selectors模块概念用法详解
  4. vue v-Model
  5. html5与css3都要学吗,前端要学css3吗?
  6. [html] websocket可以携带cookie吗?为什么?如果可以,怎样做到呢?
  7. 第五天--表单与页面
  8. nginx nodejs环境配置_Linux 环境变量配置(Nodejs/MongoDB/JDK/Nginx)
  9. Golang 之协程详解
  10. ubuntu下安装程序的三种方法
  11. [转载] 3 idiots
  12. java研磨设计模式_研磨设计模式之单例模式(内部类)
  13. Golang + selenium 设置无头浏览器模式
  14. 使用pdfobject.js实现在线浏览PDF--前端显示PDF
  15. 手把手教你快速搭建Struts2框架【详细教程,建议收藏】
  16. MATLAB数据导入汇总
  17. React Native布局实践:开发京东客户端首页(三)——轮播图的实现
  18. 自定義 ForkJoinPool 線程池,并消除classLoader加载失败的问题
  19. Keras_examples
  20. UltraEdit 21.30.1006.0 繁体中文破解版(功能最强的文本编辑器)

热门文章

  1. 今日学习在线编程题:数字游戏
  2. 看得懂这篇关于虹膜识别的文章
  3. 天线巴伦制作和原理_最新传输线巴伦的原理设计、制作及测试
  4. vuejs使用vux出错Module build failed: Error: Cannot find module 'less'
  5. mysql 存电话号码应该用哪个字段_2021-01-06:mysql中,我存十亿个手机号码,考虑存储空间和查询效率,用什么类型的字段去存?...
  6. HTML5的政治斗争:还要闹十年?
  7. 3d建模真的很累吗?前景怎么样
  8. 最详细的python案例学习与实践(含详细教程)
  9. NAS入门之——My Cloud EX2 Ultra初始化设置
  10. APP商城上架应用商店的注意事项