深入学习iOS7自定义导航转场动画
这篇文章主要为大家详细介绍了iOS7自定义导航转场动画的相关资料,感兴趣的小伙伴们可以参考一下
在iOS7以前,开发者如果希望定制导航控制器推入推出视图时的转场动画,一般都只能通过子类化UINavigationController或者自己编写动画代码去覆盖相应的方法,现在iOS7为开发者带来了福音,苹果公司引入了大量新API,给予了开发者很高的自由度,在处理由UIViewController管理的UIView动画时,这些API使用方便,可扩展性也很强,定制起来非常轻松:
全新的针对UIView的动画block方法
全新的UIViewControllerAnimatedTransitioning协议以及动画控制器的概念
InteractionControllers以及TransitionCoordinators
全新的针对动画的助手API(简便方法)
全新的针对UIView的动画block方法iOS4的发布带来了强大的block方法,在编写UIView动画时使用block可以轻松地得到满意的效果,然而有些情况下,我们还是不得不直接使用CoreAnimation。幸运的是,苹果公司在iOS7中增加了2个新的基于block的方法,这样我们就很少再需要直接使用CoreAnimation了。1、关键帧动画iOS7为UIView封装了一组API,让我们很容易的得到与CoreAnimation框架中的CAKeyframeAnimation一样的效果。
?
1
2
3
4
5
6
7
8
9
10
11
12
13 [UIViewanimateKeyframesWithDuration:durationdelay:delay
options:optionsanimations:^{
[UIViewaddKeyframeWithRelativeStartTime:0.0
relativeDuration:0.5animations:^{
//第一帧要执行的动画
}];
[UIViewaddKeyframeWithRelativeStartTime:0.5
relativeDuration:0.5animations:^{
//第二帧要执行的动画
}];
}completion:^(BOOLfinished){
//动画结束后执行的代码块
}]; 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
34
35
36 [UIViewaddKeyframeWithRelativeStartTime:0.0
relativeDuration:0.15animations:^{
//顺时针旋转90度
snapshot.transform=CGAffineTransformMakeRotation(M_PI
-1.5);
}];
[UIViewaddKeyframeWithRelativeStartTime:0.15
relativeDuration:0.10animations:^{
//180度
snapshot.transform=CGAffineTransformMakeRotation(M_PI
1.0);
}];
[UIViewaddKeyframeWithRelativeStartTime:0.25
relativeDuration:0.20animations:^{
//摆过中点,225度
snapshot.transform=CGAffineTransformMakeRotation(M_PI
1.3);
}];
[UIViewaddKeyframeWithRelativeStartTime:0.45
relativeDuration:0.20animations:^{
//再摆回来,140度
snapshot.transform=CGAffineTransformMakeRotation(M_PI
0.8);
}];
[UIViewaddKeyframeWithwww.visa158.comRelativeStartTime:0.65
relativeDuration:0.35animations:^{
//旋转后掉落
//最后一步,视图淡出并消失
CGAffineTransformshift=
CGAffineTransformMakeTranslation(180.0,0.0);
CGAffineTransformrotate=
CGAffineTransformMakeRotation(M_PI0.3);
snapshot.transform=CGAffineTransformConcat(shift,
rotate);
_coverView.alpha=0.0;
}]; 视图仿佛在重力的牵引下绕左下角顺时针旋转,并在最低点摆动了一下,最后脱落。
2、弹簧动画iOS7新引入的另一个block方法可以让你轻松将真实物理世界中的弹性效果集成进视图动画中。苹果公司一直建议开发者尽可能将动画效果做的跟真实物理世界一样——在视图滑动时,可以像弹簧一样,稍微拉伸一些,再弹回正确位置。使用新的弹簧动画API来实现此效果相较以往要简单很多。
1
2
3
4
5
6
7 [UIViewanimateWithDuration:durationdelay:delay
usingSpringWithDamping:dampinginitialSpringVelocity:velocity
options:optionsanimations:^{
//这里书写动画相关代码
}completion:^(BOOLfinished){
//动画结束后执行的代码块
}]; 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
34
35
36
37
38
39
40
41
42 -(void)animateTransition:
(id)transitionContext{
//获取容器视图引用
UIViewcontainerView=[transitionContext
containerView];
UIViewControllerfromViewController=[transitionContext
viewControllerForKey:UITransitionContextFromViewControllerKey
];
UIViewControllertoViewController=[transitionContext
viewControllerForKey:UITransitionContextToViewControllerKey];
if(self.type==AnimationTypePresent){
//插入“to”视图,初始缩放值为0.0
toViewController.view.transform=
CGAffineTransformMakeScale(0.0,0.0);
[containerViewinsertSubview:toViewController.view
aboveSubview:fromViewController.view];
//缩放“to”视图为想要的效果
[UIViewanimateWithDuration:[self
transitionDuration:transitionContext]animations:^{
toViewController.view.transform=
CGAffineTransformMakeScale(1.0,1.0);
}completion:^(BOOLfinished){
[transitionContextcompleteTransition:YES];
}];
}elseif(self.type==AnimationTypeDismiss){
//插入“to”视图
[containerViewinsertSubview:toViewController.view
belowSubview:fromViewController.view];
//缩小“from”视图,直到其消失
[UIViewanimateWithDuration:[self
transitionDuration:transitionContext]animations:^{
fromViewController.view.transform=
CGAffineTransformMakeScale(0.0,0.0);
}completion:^(BOOLfinished){
[transitionContextcompleteTransition:YES];
}];
}
}
-(NSTimeInterval)transitionDuration:
(id)transitionContext{
return0.4;
} 符合UIViewControllerAnimatedTransitioning协议的任何对象都需要实现animateTransition:和transitionDuration:两个方法。你也可以选择实现@optional方法animationEnded:,它在动画完成后由系统自动调用,相当于completionblock,非常方便。在animateTransition:中你需要处理以下过程:1.将“to”视图插入容器视图2.将“to”和“from”视图分别移动到自己想要的位置3.最后,在动画完成时千万别忘了调用completeTransition:方法UIViewControllerAnimatedTransitioning协议中的方法都带有一个参数:transitionContext,这是一个系统级的对象,它符合UIView-ControllerContextTransitioning协议,我们可以从该对象中获取用于控制转场动画的必要信息,主要包括以下内容:
显然,苹果公司帮助开发者完成了大部分让人讨厌的细节工作,仅仅需要我们自己完成的工作就是定义动画的初始状态和终止状态,并调整到自己满意的效果。最后我再啰嗦两句有关transitionContext的重要注意事项:
1.获取frame的方法可能会返回CGRectZero——如果系统无法确定该frame的值具体是什么。例如,如果你使用自定义的模态视图控制器推出动画,在结束时系统无法确定其finalFrame。2.如果视图控制器已经从屏幕上移除了,那么获取frame的方法也会返回CGRectZero。例如在导航控制器的转场动画结束后,试图获取“from”视图的finalFrame。你不用手动去移除“from”视图,transitionContext将自动帮你完成。3.如果你在应用的其他地方需要使用transitionContext,你可以放心地使用动画控制器保留一个transitionContext的引用。将动画控制器应用到转场动画中。
现在,我们已经开发好了动画控制器,那么最后需要做的就是,将它们应用到转场动画中:我们需要对管理转场动画的UIViewController做一些操作。
一般来说,我们只需要让UIViewController符合UIViewController-TransitioningDelegate协议,编写animationController-ForPresentedController和animationControllerForDismissedController方法。在我的示例应用程序中,我设置了一个属性,用来让动画控制器知道目前正在推入还是推出视图:
1
2
3
4
5
6
7
8
9
10
11
12
13 -(id)
animationControllerForPresentedController:(UIViewController
)presentedpresentingController:(UIViewController
)presentingsourceController:(UIViewController)source{
modalAnimationController.type=AnimationTypePresent;
returnmodalAnimationController;
}
-(id)
animationControllerForDismissedController:(UIViewController
)dismissed{
modalAnimationController.type=AnimationTypeDismiss;
returnmodalAnimationController;
} 然后,在推入模态视图控制器时,我们设置modalPresentationStyle为UIModalPresentationFullScreen或UIModalPresentationCustom。我们还必须将一个符合UIViewControllerTransitioningDelegate协议的对象设置为它的transitioningDelegate,一般来说都是推入该模态视图控制器的UIViewController。
1
2
3
4
5
6
7 OptionsViewControllermodal=[[OptionsViewControlleralloc]
initWithNibName:@"OptionsViewController"bundle:[NSBundle
mainBundle]];
modal.transitioningDelegate=self;
modal.modalPresentationStyle=UIModalPresentationCustom;
[selfpresentViewController:modalwww.hunanwang.netanimated:YES
completion:nil]; 如果需要将动画控制器应用到UINavigationController的转场动画中,我们需要使用UINavigationControllerDelegate协议中的一个新方法:animationControllerForOperation。对于任何自定义的导航转场动画,导航栏都会有一个淡入淡出的动画过程。同样,对于UITabBarController,使用UITabBarControllerDelegate协议的新方法——animationController-ForTransitionFromViewController。
为转场动画定义交互方式在iOS7中,苹果到处都在使用交互式弹出手势,同时,苹果也给开发者们提供了一系列工具,只需简单几步就能将交互手势应用在视图切换过程中。我们可以通过相应的委托方法返回一个交互控制器:
UINavigationController
interactionControllerForAnimationController
UITabBarController
interactionControllerForAnimationController
UIViewController
interactionControllerForPresentation
interactionControllerForDismissal
这里唯一需要注意的是,如果没有自定义转场动画,这些方法就不会起作用。例如,你必须从animationControllerForOperation得到一个有效的动画控制器,UINavigationController才会调用interactionController-ForAnimationController——即使你在转场交互中没有使用动画控制器。
其次,交互控制器非常灵活,有很强的可扩展性。虽然在示例应用程序中我使用手势检测来控制交互,但是你也可以用手势以外的其他方式来实现。你可以设计任意你想要的效果用以转场交互。
交互控制器:最简单的实现方式有两种方式可以创建交互控制器。第一个也是最简单的一个,就是使用UIPercentDrivenInteractiveTransition。
1
2
3
4
5
6
7
8
9
10 @interfaceUIPercentDrivenInteractiveTransition:NSObject
@property(readonly)CGFloatduration;
@property(readonly)CGFloatpercentComplete;
@property(nonatomic,assign)CGFloatcompletionSpeed;
@property(nonatomic,assign)UIViewAnimationCurve
completionCurve;
-(void)updateInteractiveTransition:(CGFloat)percentComplete;
-(void)cancelInteractiveTransition;
-(void)finishInteractiveTransition; 这个类具体实现了UIViewControllerInteractiveTransitioning协议,我们可以使用它轻松为动画控制器添加自定义的交互方式。只要为目标视图加入手势(或者其他交互方式)并调用updateInteractiveTransition:,传入动画时间占整个过程的百分比即可。同时,记住在交互完成后调用finishInteractiveTransition:,交互被取消时调用cancel-InteractiveTransition:。下面的例子展示了如何将捏合手势应用到转场动画中:
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
34 -(void)handlePinch:(UIPinchGestureRecognizer)pinch{
CGFloatscale=pinch.scale;
switch(pinch.state){
caseUIGestureRecognizerStateBegan:{
_startScale=scale;
self.interactive=YES;
[self.navigationController
popViewControllerAnimated:YES];
break;
}
caseUIGestureRecognizerStateChanged:{
CGFloatpercent=(1.0-scale/_startScale);
[selfupdateInteractiveTransition:(percent<0.0)?
0.0:percent];
break;
}
caseUIGestureRecognizerStateEnded:{
CGFloatpercent=(1.0-scale/_startScale);
BOOLcancelled=([pinchvelocity]<5.0&&percent
<=0.3);
if(cancelled)[selfcancelInteractiveTransition];
else[selffinishInteractiveTransition];
break;
}
caseUIGestureRecognizerStateCancelled:{
CGFloatpercent=(1.0-scale/_startScale);
BOOLcancelled=([pinchvelocity]<5.0&&percent
<=0.3);
if(cancelled)[selfcancelInteractiveTransition];
else[selffinishInteractiveTransition];
break;
}
}
} 当你继承了UIPercentDrivenInteractiveTransition类,交互过程中系统会自动调用动画控制器的animateTransition:方法,按照你传递的percentComplete参数实时地展现动画效果。在交互完成后,它还自动调用animateTransition:方法恢复到正常状态,一旦交互完成,我们就可以改变completionSpeed和completionCurve属性来修改其他的一些样式。
交互控制器:通过自定义的方式如果你需要深入控制UIPercentDrivenInteractiveTransition处理转场动画的细节,那么就不用去继承该类,而是使用UIViewController-InteractiveTransitioning协议。此协议与UIViewController-AnimatedTransitioning类似,我们可以通过该协议控制所有关于转场动画的细节。在该协议中我们需要完成以下步骤:
1.实现startInteractiveTransition:方法,用于初始化专场动画。2.获取transitionContext对象的引用(如果继承了UIPercentDrivenInteractiveTransition,可以看到它自动帮我们完成了这一步骤,因此这里我们必须手动获取该对象)。3.和之前一样,在适当的情况下调用updateInteractiveTransition:,cancelInteractiveTransition和finishInteractiveTransition(对于导航控制器来说,完成方法中还需要显示或隐藏导航栏)。4.完成后仍然请记住调用transitionCompleted:。
下面是我通过自定义的交互控制器来实现与之前相同的动画,仍然是使用捏合手势控制转场动画。
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
34
35
36
37
38
39
40
41
42
43
44
45
46
47 -(void)startInteractiveTransition:
(id)transitionContext{
//获取transitionContext对象的引用
_context=transitionContext;
//获取容器视图引用
UIViewcontainerView=[transitionContext
containerView];
UIViewControllerfromViewController=[transitionContext
viewControllerForKey:UITransitionContextFromViewControllerKey
];
UIViewControllertoViewController=[transitionContext
viewControllerForKey:UITransitionContextToViewControllerKey];
//插入“to”视图
toViewController.view.frame=[transitionContext
finalFrameForViewController:toViewController];
[containerViewinsertSubview:toViewController.view
belowSubview:fromViewController.view];
//保留需要缩?小的视图的引用
_transitioningView=fromViewController.view;
}
-(void)updateWithPercent:(CGFloat)percent{
CGFloatscale=fabsf(percent-1.0);
_transitioningView.transform=
CGAffineTransformMakeScale(scale,scale);
[_contextupdateInteractiveTransition:percent];
}
-(void)end:(BOOL)cancelled{
if(cancelled){
[UIViewanimateWithDuration:_completionSpeed
animations:^{
_transitioningView.transform=
CGAffineTransformMakeScale(1.0,1.0);
}completion:^(BOOLfinished){
[_contextcancelInteractiveTransition];
[_contextcompleteTransition:NO];
}];
}else{
[UIViewanimateWithDuration:_completionSpeed
animations:^{
_transitioningView.transform=
CGAffineTransformMakeScale(0.0,0.0);
}completion:^(BOOLfinished){
[_contextfinishInteractiveTransition];
[_contextcompleteTransition:YES];
}];
}
} 你可以让动画控制器同时实现UIViewControllerInteractive-Transitioning和UIViewControllerAnimatedTransitioning(像示例程序中那样),从而把所有代码都放在一个类中。你也可以将交互控制器和动画控制器分成两个类——协议这一语法特性的妙处在于,你可以轻松实现符合需求的最佳解决方案。
更多小技巧在block中选择是否进行动画开发者或许会遇到这样一种情况:在一串精美的动画效果中,我们需要让某些视图不进行动画,从而营造一种动静相宜的效果。在动画block方法推出之前,我们可以在[UIViewbeginAnimations]和[UIViewcommitAnimations]之间使用setAnimationsEnabled方法来设置哪些动画不需要执行。而在iOS7SDK中,苹果公司为开发者提供了新方法,只要把不需要执行的动画写在block中即可:
1
2
3 [UIViewperformWithoutAnimation:^{
//确保不执行动画
}]; 1
2
3
4
5
6 CollectionViewControllerVC=[[CollectionViewController
alloc]initWithCollectionViewLayout:flowLayout];
VC.title=@"MiniApples";
VC.useLayoutToLayoutNavigationTransitions=YES;
[self.navigationControllerpushViewController:VC
animated:YES]; 转场动画调度器还有一个非常有用的API,它可以帮助视图控制器管理转场动画:UIViewControllerTransitionCoordinator协议。在iOS7中,每一个视图控制器(当然也包括UINavigationController和UITabBarController)都有一个transitionCoordinator属性,该属性提供了一系列用于转场动画的强大工具,首先我们来看看animateAlongsideTransition:方法。
1
2
3
4
5
6
7
8
9 [self.transitionCoordinator
animateAlongsideTransition:^(id
rdinatorContext>context){
//要执行的动画
}
completion:^(id
context){
//动画结束后执行的代码块
}]; 1
2
3
4
5
6 [self.transitionCoordinator
notifyWhenInteractionEndsUsingBlock:^(id
sitionCoordinatorContext>context){
//动画结束后执?行的代码块
}]; 1 [viewsnapshotViewAfterScreenUpdates:NO]; 这个方法制作了一个UIView的副本,如果我们希望视图在执行动画之前保存现在的外观,以备之后使用(动画中视图可能会被子视图遮盖或者发生其他一些变化),该方法就特别方便。
afterUpdates参数表示是否在所有效果应用在视图上了以后再获取快照。例如,如果该参数为NO,则立马获取该视图现在状态的快照,反之,以下代码只能得到一个空白快照:
1
2 [viewsnapshotViewAfterScreenUpdates:YES];
[viewsetAlpha:0.0]; 由于我们设置afterUpdates参数为YES,而视图的透明度值被设置成了0,所以方法将在该设置应用在视图上了之后才进行快照,于是乎屏幕空空如也。另外就是……你可以对快照再进行快照……继续快照……苹果公司在iOS7中为开发者添加了新的用于创建和自定义动画的API。iOS7SDK不仅引入了强大的动画block和许多易于使用的方法,而且彻底改变了为视图自定义动画的方式。
|
|