几句代码快速集成自定义转场效果+ 全手势驱动

原文出处:wazrxphp

写在前面

在简书写完第一篇的自定义转场文章后,已经好久没有碰过转场了,毕竟在公司,功能实现才是最重要的,这些转场的动效,只能是点睛之笔,不太容易被重视,不过个人第一篇文章仍是不少人的喜欢和讨论,不少人还提出些建议,很是感谢你们,这是我第一篇文章的地址自定义转场动画,里面包含了一些转场的基础知识,这篇文章我就再也不讨论这些基础知识了。git


为何会有这第二篇文章,主要缘由有以下几点:github

 

一、能不能更简单?当我好久没有使用转场的时候,再次来使用它,感受仍是比较烦琐,有一大堆记不住的长长的代理方法,都要去copy,长长的代理方法也把控制器弄得有点乱,虽然苹果已经将整个过程充分解耦了,我在想,要是能简单的一两句话就能集成转场效果多好,或者经过继承和复写一两个方法就能轻松实现本身的转场效果,无需关注转场逻辑,只需关注动画逻辑数组

 

二、闪烁和生硬?在第一篇文章中有人提到的部分的bug,好比小圆点扩散效果,若是手势在中途取消,不会有取消动画,很是生硬,并且会有闪烁的bug,我在想能不能解决这两个问题,强迫症接受不了o(╯□╰)o,我如今找到了一个比较好的方式来解决问题,原理和对比图会在后面给出网络

 

三、能不能多添加一些效果?因此我把本身写的效果封装,再参照网络一些效果,总过添加了将近20个效果ide

 

四、手势万岁!任何效果我都想可以手势驱动oop

效果图(图比较多,请手机用户慎重,可下载demo真机运行效果更好)

截图中,右上角的switch开关表明push和present,全部效果都支持手势,我就不一一演示了布局

 

一、CircleSpreadTransition 小圆点扩散测试

CircleSpreadTransition.gif优化

 

二、MagicMoveTransition 神奇移动

MagicMoveTransition1.gif

MagicMoveTransition2.gif

 

三、XWDrawerAnimator 抽屉效果,仿照QQ和淘宝

XWDrawerAnimator1.gif

 

XWDrawerAnimator2.gif

 

四、XWCoolAnimator 自定义一些效果

XWCoolAnimator2.gif

XWCoolAnimator1.gif

XWCoolAnimator3.gif

XWCoolAnimator4.gif

XWCoolAnimator5.gif

XWCoolAnimator6.gif

XWCoolAnimator7.gif

 

五、XWFilterAnimator 经过CIFilter滤镜自定义一些效果,请在真机上运行

XWFilterAnimator1.gif

 

XWFilterAnimator6.gif

 

XWFilterAnimator5.gif

XWFilterAnimator4.gif

 

XWFilterAnimator3.gif

XWFilterAnimator2.gif

XWFilterAnimator8.gif

XWFilterAnimator7.gif

如何使用

一、git地址:几句代码快速集成自定义转场效果+ 全手势驱动,clone后将整个XWTranstion文件夹导入工程

二、导入UINavigationController+XWTransition.h或者UIViewController+XWTransition.h两个分类

三、选择你须要的效果器进行根据初始化方法进行初始化,好比下面的小圆点扩散,初始化指定开始圆心和半径

1

XWCircleSpreadAnimator *animator = [XWCircleSpreadAnimator xw_animatorWithStartCenter:self.button.center radius:20];

四、经过初始化的效果器转场,根据分类提供的方法进行push或者present,就完成了!

1

2

3

[self.navigationController xw_pushViewController:toVC withAnimator:animator];

或者

[self xw_presentViewController:toVC withAnimator:animator];

手势驱动

一、在UIViewController+XWTransition.h分类中提供了两个方法,用来注册手势驱动,在viewDidLoad的时候调用注册手势就能够了,详见demo,注意避免循环引用,手势支持边缘属性

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

/**

 *  注册to手势(push或者Present手势)

 *

 *  @param direction       手势方向

 *  @param tansitionConfig 手势触发的block,block中须要包含你的push或者Present的逻辑代码,注意避免循环引用问题

 *  @param edgeSpacing     手势触发的边缘距离,该值为0,表示在整个控制器视图上都有效,否者这在边缘的edgeSpacing之类有效

 */

 

- (void)xw_registerToInteractiveTransitionWithDirection:(XWInteractiveTransitionGestureDirection)direction transitonBlock:(dispatch_block_t)tansitionConfig edgeSpacing:(CGFloat)edgeSpacing;

 

/**

 *  注册back手势(pop或者dismiss手势)

 *

 *  @param direction       手势方向

 *  @param tansitionConfig 手势触发的block,block中须要包含你的pop或者dismiss的逻辑代码,注意避免循环引用问题

 *  @param edgeSpacing     手势触发的边缘距离,该值为0,表示在整个控制器视图上都有效,否者这在边缘的edgeSpacing之类有效

 */

 

- (void)xw_registerBackInteractiveTransitionWithDirection:(XWInteractiveTransitionGestureDirection)direction transitonBlock:(dispatch_block_t)tansitionConfig edgeSpacing:(CGFloat)edgeSpacing;

 

二、事例代码

1

2

3

4

    __weak typeof(self)weakSelf = self;    //注册一个全屏的back转场

    [self xw_registerBackInteractiveTransitionWithDirection:XWInteractiveTransitionGestureDirectionDown transitonBlock:^{    //pop或者dismiss操做

        [weakSelf xw_transiton];

    } edgeSpacing:0];

关于神奇移动效果

一、在UIViewController+XWTransition.h分类中提供了三个关于神奇移动的方法,你须要在转场前和转场后的控制器中分别注册神奇移动先后的视图(用来告知神奇移动先后的frame),而后经过神奇移动效果器就能够触发神奇移动转场了

1

2

3

4

5

6

7

8

9

10

11

12

13

/**

 * 注册神奇移动起始视图

 *

 * @param group 神奇移动起始视图数组

 */- (void)xw_addMagicMoveStartViewGroup:(NSArray<UIView *> *)group;/**

 * 注册神奇移动终止视图

 *

 * @param group 神奇移动终止视图数组,注意起始视图数组和终止视图数组的视图须要一一对应才能有正确的效果

 */- (void)xw_addMagicMoveEndViewGroup:(NSArray<UIView *> *)group;/**

 * 改变神奇移动起始视图,由于在back的时候,有可能不须要再回到原来起始的位置,须要去一个新的视图位置,因此在back前须要调用该方法改变起始视图数组

 *

 * @param group 新的起始视图数组

 */- (void)xw_changeMagicMoveStartViewGroup:(NSArray<UIView *> *)group;

二、事例代码

1

2

3

4

5

    //fromVC转场前控制器中注册神奇移动前视图

    [self xw_addMagicMoveStartViewGroup:@[imgView, view1, view2]];    //toVC转场后控制器中注册神奇移动前视图

    [self xw_addMagicMoveEndViewGroup:@[imgView, view1, view2]];    //初始化神奇移动效果器转场

    XWMagicMoveToController *toVC = [XWMagicMoveToController new];

    [self xw_presentViewController:toVC withAnimator:animator];

 

三、转场中存在cell,因为在转场过程当中cell尚未加载,因此没法注册cell为神奇移动视图,这种状况须要生产一个零时视图注册为转场视图来使用,具体请参考demo中的九宫格例子

 

四、关于提供的imageMode属性:在神奇移动中,有个问题,就是移动中的临时视图通常都是用截图大法截图而来的,可是若是从从小图变成大图,因为截图为小图截图,变大过程当中会有模糊的现象,若是设置了该属性,我会对神奇移动视图中的包含了image的视图进行检测,若是能检测到image则直接取image,而不截图,就能解决模糊的问题,代码以下

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

- (UIView *)_xw_snapshotView:(UIView *)view{    CALayer *layer = view.layer;    UIView *snapView = [UIView new];

    snapView.frame = view.frame;    BOOL imgMode = [objc_getAssociatedObject(view, &kXWMagicMovePropertyInViewKey) boolValue] || _imageMode;    UIImage *img = nil;    if (imgMode) {//若是开启imgMode,优先直接获取图片,避免截图时时从小到大形成的模糊

        if ([view isKindOfClass:[UIImageView class]]) {//取imageView中的image

            img = [(UIImageView *)view image];

        }else if ([view isKindOfClass:[UIButton class]]){//取button中的image

            img = [(UIButton *)view currentImage];

        }        if (!img && [view isKindOfClass:[UIView class]]) {//没取到尝试取content

            img = [UIImage imageWithCGImage:(__bridge CGImageRef)view.layer.contents];

        }

    }    //若都没有取到,则截图

    if (!img) {        UIGraphicsBeginImageContextWithOptions(layer.bounds.size, layer.opaque, 0);        CGContextRef context = UIGraphicsGetCurrentContext();

        [layer renderInContext:context];

        img = UIGraphicsGetImageFromCurrentImageContext();        UIGraphicsEndImageContext();

    }

    snapView.layer.contents = (__bridge id)img.CGImage;    return snapView;

}

关于抽屉效果的全屏拖动

一、抽屉效果因为注册的手势都是在控制器的的视图上,若是作QQ设置界面的效果,不可能在toVC以外点击和拖动可以back,个人思路是会在toVC没有覆盖的区域添加一个透明视图,给透明视图加上点击和拖动手势,具体代码以下

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

//首先须要设置点击和拖动的back操做,block中应该包含你的dismiss或者pop逻辑/**

 *  开启边缘(就是屏幕除开toView所占用的部分)back手势和边缘点击返回效果,相似于QQ设置界面的返回效果

 *

 *  @param backConfig 返回操做,您的dismiss或者pop操做

 */- (void)xw_enableEdgeGestureAndBackTapWithConfig:(dispatch_block_t)backConfig;//添加全屏手势代码以下/**

 *  添加全局手势和点击视图

 */- (void)_xw_addFullGestureAndTapBackViewInContainerView:(UIView *)containerView toView:(UIView *)toView distance:(CGFloat)distance{    CGFloat width = _vertical ? containerView.frame.size.width : containerView.frame.size.width - fabs(distance);    CGFloat height = _vertical ? containerView.frame.size.height - fabs(distance) : containerView.frame.size.height;    //若是toVC是全屏铺满则无需添加全局手势,直接使用toVC的view的手势就行了

    if (width == 0 || height == 0)return;    if (!_backConfig) return;    //若是toView注册过手势,咱们直接获取这个手势

    NSArray<UIGestureRecognizer *> *gestures = toView.gestureRecognizers;

    __block id target = nil;

    [gestures enumerateObjectsUsingBlock:^(UIGestureRecognizer * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {        NSString *panType = objc_getAssociatedObject(obj, "xw_interactivePanKey");        if ([panType isEqualToString:@"xw_interactiveBackPan"] && obj.delegate) {

            target = obj.delegate;

            *stop = YES;

        }

    }];    CGFloat x = _vertical || _direction == XWDrawerAnimatorDirectionRight ? 0 : -distance;    CGFloat y = !_vertical || _direction == XWDrawerAnimatorDirectionBottom ? 0 : -distance;    UIControl *gestureView = [UIControl new];    //添加点击事件

    [gestureView addTarget:self action:@selector(_xw_backConfig) forControlEvents:UIControlEventTouchUpInside];

    gestureView.frame = CGRectMake(x, y, width, height);

    gestureView.backgroundColor = [UIColor clearColor];    //第一种状况,toView已经添加了返回手势,咱们直接拿到该手势的target和action

    if (target) {        //给containerView添加全局手势

        UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:target action:NSSelectorFromString(@"_xw_handleGesture:")];

        [containerView addGestureRecognizer:pan];

 

    }else{        //第二种状况,toView没有添加手势,咱们须要建立一个

        __weak typeof(self)weakSelf = self;

        XWInteractiveTransition *backTransition = [XWInteractiveTransition xw_interactiveTransitionWithDirection:(XWInteractiveTransitionGestureDirection)_direction config:^{

            weakSelf.backConfig();

        } edgeSpacing:0];

        backTransition.panRatioBaseValue = _vertical ? containerView.frame.size.height : containerView.frame.size.width;

        [backTransition xw_addPanGestureForView:gestureView to:NO];//        [self xw_setBackInteractiveTransition:backTransition];

        [self setValue:backTransition forKey:@"backTransition"];

    }

    [containerView addSubview:gestureView];

}

解决动画生硬

一、先看小圆点效果的例子,前面是解决前写的,后面是如今的

未解决

小圆点未开启timer.gif

解决后

小圆开启timer.gif


二、问题缘由:在手势结束后该效果不会动画的过渡到成功或者失败,而是整个转场进度会直接update到0或者1,就木有动画了

三、解决:在手指松开的时候,我会开启一个CADisplayLink来不断的刷新整个转场进度到1或者0,来达到动画的效果,具体代码以下

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

case UIGestureRecognizerStateEnded:{//转场结束后

            //判断是否须要timer

            if (!_timerEable) {

                _percent >= 0.5 ? [self _xw_finish] : [self _xw_cancle];                return;

            }            //判断此时是否已经转场完成,大于1或者小于0

            BOOL canEnd = [self _xw_canEndInteractiveTransitionWithPercent:_percent];            if (canEnd) return;            //开启timer

            [self _xw_setEndAnimationTimerWithPercent:_percent];//设置开启timer- (void)_xw_setEndAnimationTimerWithPercent:(CGFloat)percent{

    _percent = percent;    //根据失败仍是成功设置刷新间隔

    if (percent > 0.5) {

        _timeDis = (1 - percent) / ((1 - percent) * 60);

    }else{

        _timeDis = percent / (percent * 60);

    }    //开启timer

    [self _xw_startTimer];

}//开启timer- (void)_xw_startTimer{    if (_timer) {        return;

    }

    _timer = [CADisplayLink displayLinkWithTarget:self selector:@selector(_xw_timerEvent)];

    [_timer addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];

}//timer 事件- (void)_xw_timerEvent{    if (_percent > 0.5) {

        _percent += _timeDis;

    }else{

        _percent -= _timeDis;

    }    //经过timer不断刷新转场进度,达到动画效果

    [self _xw_updatingWithPercent:_percent];    //判断进度是否达到0和1,达到则结束timer,结束转场

    BOOL canEnd = [self _xw_canEndInteractiveTransitionWithPercent:_percent];    if (canEnd) {

        [self _xw_stopTimer];

    }

}

解决闪烁问题

一、闪烁缘由:在不使用UIView的动画block时,咱们直接为layer添加一个CAAnimtion,此时会先设置modelLayer为转场成功的状态,好比小圆点效果会设置path为大圆的path,可是若是转场失败,presentLayer依然会先变为modelLayer设置的成功值,而后动画才结束,走咱们的转场失败逻辑,因此就会闪烁

二、解决:我把手势改变的一些关键状态经过代理传出来,在手势结束前,咱们若是检查到失败,能够先将modelLayer的值标记为失败时候的值,也就是初始值,就解决了该问题

三、事例代码

1

2

3

4

//手势转场时的代理事件,animator默认为为其手势的代理,复写对应的代理事件可处理一些手势失败闪烁的状况@protocol XWInteractiveTransitionDelegate <NSObject>@optional/**手势转场即将开始时调用*/- (void)xw_interactiveTransitionWillBegin:(XWInteractiveTransition *)interactiveTransition;/**手势转场中调用*/- (void)xw_interactiveTransition:(XWInteractiveTransition *)interactiveTransition isUpdating:(CGFloat)percent;/**若是开始了转场手势timer,会在松开手指,timer开始的时候调用*/- (void)xw_interactiveTransitionWillBeginTimerAnimation:(XWInteractiveTransition *)interactiveTransition;/**手势转场结束的时候调用*/- (void)xw_interactiveTransition:(XWInteractiveTransition *)interactiveTransition willEndWithSuccessFlag:(BOOL)flag percent:(CGFloat)percent;@end//我在小圆点扩散效果中处理的以下- (void)xw_interactiveTransition:(XWInteractiveTransition *)interactiveTransition willEndWithSuccessFlag:(BOOL)flag percent:(CGFloat)percent{    if (!flag) {        //防止失败后的闪烁,若是失败将遮罩的path设置为其实的小圆path

        _maskLayer.path = _startPath.CGPath;

    }

    _containerView.userInteractionEnabled = YES;

关于coolTransiton

一、直接经过枚举初始化就有已经集成的部分效果,具体以下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

typedef NS_ENUM(NSUInteger, XWCoolTransitionAnimatorType){    //全屏翻页

    XWCoolTransitionAnimatorTypePageFlip,    //中间翻页

    XWCoolTransitionAnimatorTypePageMiddleFlipFromLeft,

    XWCoolTransitionAnimatorTypePageMiddleFlipFromRight,

    XWCoolTransitionAnimatorTypePageMiddleFlipFromTop,

    XWCoolTransitionAnimatorTypePageMiddleFlipFromBottom,    //开窗

    XWCoolTransitionAnimatorTypePortal,    //折叠

    XWCoolTransitionAnimatorTypeFoldFromLeft,

    XWCoolTransitionAnimatorTypeFoldFromRight,    //爆炸

    XWCoolTransitionAnimatorTypeExplode,    //酷炫线条效果

    XWCoolTransitionAnimatorTypeHorizontalLines,

    XWCoolTransitionAnimatorTypeVerticalLines,    //扫描效果

    XWCoolTransitionAnimatorTypeScanningFromLeft,

    XWCoolTransitionAnimatorTypeScanningFromRight,

    XWCoolTransitionAnimatorTypeScanningFromTop,

    XWCoolTransitionAnimatorTypeScanningFromBottom,

};

二、 cool转场效果中的Portal、Fold、Explode效果的部分代码逻辑来源于ColinEberhardt/VCTransitionsLibrary,很是感谢做者,我只是将其进行了部分改动,以便对手势的支持更加完善,里面还有许多其余效果,本人经历有限就没有再集成进来了,你们能够自行查看;cool转场效果的Lines的想法来自于cinkster/HUAnimator, 很是感谢做者,可是因为做者在对toVC截图采用了延迟的方式来处理,致使了很差处理的bug和一些手势上的bug,对此我采用了另外一种方式来解决截图的问题,使用了layer的contentRect属性,解决了发现的问题,相关代码请自行查看

关于FilterTransition

一、XWFilterAnimator 全都是基于不一样的CIFilter产生的一些滤镜效果,貌似在模拟器没法运行这些效果,请在真机上测试,直接经过枚举初始化就有已经集成的部分效果,具体以下:

1

2

3

4

5

6

7

8

9

10

typedef NS_ENUM(NSUInteger, XWFilterAnimatorType) {

    XWFilterAnimatorTypeBoxBlur,//模糊转场,对应CIBoxBlur

    XWFilterAnimatorTypeSwipe,//滑动过渡转场,对应CISwipeTranstion

    XWFilterAnimatorTypeBarSwipe,//对应CIBarSwipeTranstion

    XWFilterAnimatorTypeMask,//按指定遮罩图片转场,对应CIDisintegrateWithMaskTransition

    XWFilterAnimatorTypeFlash,//闪烁转场,对应CIFlashTransition

    XWFilterAnimatorTypeMod,//条纹转场 对应CIModTransition

    XWFilterAnimatorTypePageCurl,//翻页转场 对应CIPageCurlWithShadowTransition

    XWFilterAnimatorTypeRipple,//波纹转场,对应CIRippleTransition

    XWFilterAnimatorTypeCopyMachine, //效果和XWCoolAnimator中的Scanning效果相似,对应CICopyMachineTransition};

 

二、若是想要添加其余滤镜转场,能够尝试个人FilterTransition中书写分类的方式,只须要指定CIFilter和相关逻辑便可

关于自定义转场效果

一、你只须要继承于XWTransitionAnimator,就像我上面全部的效果器同样,而后复写须要的属性和两个必须的方法便可,而后你就可使用你自定义的效果器转场,XWTransitionAnimator头文件以下:

1

2

3

4

5

@interface XWTransitionAnimator : NSObject<UIViewControllerTransitioningDelegateUINavigationControllerDelegateUITabBarControllerDelegate, XWInteractiveTransitionDelegate>//to转场时间 默认0.5@property (nonatomic, assign) NSTimeInterval toDuration;//back转场时间 默认0.5@property (nonatomic, assign) NSTimeInterval backDuration;//是否须要开启手势timer,某些转场若是在转成过程当中所开手指,不会有动画过渡,显得很生硬,开启timer后,松开手指,会用timer不断的刷新转场百分比,消除生硬的缺点@property (nonatomic, assign) BOOL needInteractiveTimer;/**

 *  配置To过程动画(push, present),自定义转场动画应该复写该方法

 */- (void)xw_setToAnimation:(id<UIViewControllerContextTransitioning>)transitionContext;/**

 *  配置back过程动画(pop, dismiss),自定义转场动画应该复写该方法

 */- (void)xw_setBackAnimation:(id<UIViewControllerContextTransitioning>)transitionContext;@end

二、这样就只须要关心动画的逻辑,其他的事情就不用管了,不过若是遇到闪烁问题,你只须要复写相关的手势代理方法,就像我在小圆点转场中同样,由于XWTransitionAnimator默认是手势管理者的代理,因此直接实现代理方法就行了

写在最后

陆陆续续的就这些了,东西比较多,可能个人叙述也还有必定问题,某些内容可能描述的不太清楚,请你们多多参考demo,但愿本文能让你们之后再设计到自定义转场的时候可以迅速解决问题,再次复习一下地址几句代码快速集成自定义转场效果+ 全手势驱动 ,若是对您有帮助欢迎给予star支持!

更新 2016-06-24

今天早上思考了一下,优化了一下DrawerAnimator,以前的toVC的frame不会随着设置的distance改变,默认通常都是屏幕的宽和高,也就是说显示以后,toVC的有一部分实际是在屏幕外面的,这对于后续的布局是不太方便的,因此我修改了一下,如今toVC的frame是和设置的distance相关的,所看见的toVC的部分就是toVC的所有

更新 2016-07-05

一、今天发现了一个问题,就是在进行不一样的效果屡次push的时候,在pop的时候,以前的效果会失效,我修复了这个问题,请看截图,上面是修复前,下面是修复后

修正以前.gif

修正以后.gif

 

能够看见,修复前,在最后一次back的时候,那个爆炸的效果已经失效了,
二、问题缘由:在每次push时我会切换navigationController的delegate为当前效果器,从而能完成转场效果的逻辑,因此屡次push后,代理始终是最后一个效果器,而在pop的时候那个效果器随着对应的pop操做已经被销毁了,而代理并无切换为以前的爆炸效果器,因此自定义转场就没法触发了
三、解决:因为我每个效果器是和被push出的VC绑定的,因此当被pushVC被销毁的时候,效果器就会销毁,此刻,应该去检测一下代理,若是上一个VC存在效果器,则须要切换回该效果器,因此须要在pushVC的dealloc方法中须要对代理进行检测和切换,为了达到目的,须要对VC的dealloc方法进行调剂,调剂的方法稍微有点复杂,具体请看我另外一篇简书文章:一句代码,更加优雅的调用KVO和通知中关于调剂dealloc方法的相关代码,在dealloc中添加了代理检测和切换的方法来达到目的~

 

 

原文:http://bbs.520it.com/forum.php?mod=viewthread&tid=2906

相关文章
相关标签/搜索