先看效果图:
上面就是要实现的Modal转场效果
首先,如果要自定义转场动画的话,需要设置
transitioningDelegate = [FFTransitionDelegateshareInstance];
modalPresentationStyle = UIModalPresentationCustom;
FFTransitionDelegate是实现了UIViewControllerTransitioningDelegate protocol,并实现它的代理方法,我在这里使用了单例模式。
staticFFTransitionDelegate* _instance;
+ (instancetype)shareInstance{
if(!_instance){
_instance= [[FFTransitionDelegatealloc]init];
}
return_instance;
}
+ (instancetype)allocWithZone:(struct_NSZone*)zone{
if(!_instance){staticdispatch_once_tonce;
dispatch_once(&once, ^{
_instance= [superallocWithZone:zone];
});
}return_instance;
}
dispatch_once(&once, ^{
_instance= [superinit];
});
return_instance;
}
- (UIPresentationController*)presentationControllerForPresentedViewController:(UIViewController*)presented presentingViewController:(UIViewController*)presenting sourceViewController:(UIViewController*)source{
FFPresentationController* presentController = [[FFPresentationControlleralloc]initWithPresentedViewController:presentedpresentingViewController:presenting];
returnpresentController;
}
- (id)animationControllerForDismissedController:(UIViewController*)dismissed{
FFAnimatedTransitioning* animated = [[FFAnimatedTransitioningalloc]init];
animated.type=Dismiss;
animated.duration=2;
returnanimated;
}
- (id)animationControllerForPresentedController:(UIViewController*)presented presentingController:(UIViewController*)presenting sourceController:(UIViewController*)source{
FFAnimatedTransitioning* animated = [[FFAnimatedTransitioningalloc]init];
animated.type=Presented;
animated.duration=2;
returnanimated;
}
继而在实现创建一个类来实现动画效果UIViewControllerAnimatedTransitioning,在这个方法里实现
- (void)animateTransition:(id)transitionContext
- (NSTimeInterval)transitionDuration:(id)transitionContext
还有个非常的重要的地方
创建一个继承自UIPresentationController的类后
要记得在
- (void)presentationTransitionWillBegin
加上
[self.containerViewaddSubview:self.presentedView];
然后
- (void)dismissalTransitionDidEnd:(BOOL)completed
[self.presentedViewremoveFromSuperview];
上面都是自定义转场动画的前置条件
现在开始分析页面,页面动画效果是由三部分构成,上半部分,中间部分,和下半部分
查看运动轨迹,可以发现上半圆是与上半部分一起运动的,所以这一块是Path描绘出来的
直接上代码
_UpPath= [UIBezierPathbezierPath];
[_UpPathmoveToPoint:LeftStartPoint];
[_UpPathaddLineToPoint:LeftendPoint];
UIBezierPath* UpCirclePath = [UIBezierPathbezierPath];
[UpCirclePathaddArcWithCenter:CircleCenterradius:RADIUSstartAngle:0endAngle:M_PIclockwise:NO];
[_UpPathappendPath:UpCirclePath];
[_UpPathmoveToPoint:RightStartPoint];
[_UpPathaddLineToPoint:RightEndPoint];
下半部分类似,只是描绘点不一样,中间部分这是由三角形构成
[_ToPathmoveToPoint:CGPointMake(RADIUS*2*cos(M_PI/6) -RectLength,0)];
[_ToPathaddLineToPoint:CGPointMake(RADIUS*2*cos(M_PI/6) -RectLength,RectLength)];
[_ToPathaddLineToPoint:CGPointMake(RectLength,RectLength/2)];
[_ToPathclosePath];
_ToPath.lineWidth=3;
closePath方法会将曲线连接起来,形成闭合
好啦,现在基础图形已经有了,要开始做动画效果了
上半部分的向上移动只需要设置position,y就可以啦,使用 CABasicAnimation
CABasicAnimation* UpAnimation = [CABasicAnimationanimationWithKeyPath:@"position.y"];
UpAnimation.fromValue= [NSNumbernumberWithFloat:self.UpLayer.position.y];
UpAnimation.toValue= [NSNumbernumberWithFloat:-self.UpLayer.position.y];
UpAnimation.removedOnCompletion=NO;
UpAnimation.repeatCount=1;
UpAnimation.duration=self.duration;
UpAnimation.autoreverses=NO;
UpAnimation.fillMode=kCAFillModeForwards;
效果如下:
向下的实现和上半部分差不多,只是值不一样
CABasicAnimation* DownAnimation = [CABasicAnimationanimationWithKeyPath:@"position.y"];
DownAnimation.toValue= [NSNumbernumberWithFloat:self.DownLayer.position.y+self.DownLayer.bounds.size.height];
DownAnimation.fromValue= [NSNumbernumberWithFloat:self.DownLayer.position.y];
DownAnimation.removedOnCompletion=NO;
DownAnimation.repeatCount=1;
DownAnimation.duration=self.duration;
DownAnimation.autoreverses=NO;
DownAnimation.fillMode=kCAFillModeForwards;
中间部分的旋转,需要设置ShapeLayer的Frame,否则会绕着圆心旋转,设置了Frame之后可以改变锚点(anchorPoint)的值,会影响旋转的轴,这个可以随意发挥想象让它按照想要的方向去转
CABasicAnimation* MiddleRotateAnimation = [CABasicAnimationanimationWithKeyPath:@"transform.rotation.z"];
MiddleRotateAnimation.fromValue= [NSNumbernumberWithFloat:0];
MiddleRotateAnimation.toValue= [NSNumbernumberWithFloat:-M_PI_2];
MiddleRotateAnimation.duration=self.duration/2;
MiddleRotateAnimation.repeatCount=1;
MiddleRotateAnimation.removedOnCompletion=NO;
MiddleRotateAnimation.fillMode=kCAFillModeForwards;
MiddleRotateAnimation.autoreverses=NO;
由于向上旋转,三角形会发生假上移现象
所以需要添加一个下移的动画啦
CABasicAnimation* MiddleDownAnimation = [CABasicAnimationanimationWithKeyPath:@"position.y"];
MiddleDownAnimation.fromValue= [NSNumbernumberWithFloat:_MiddleLayer.position.y];
MiddleDownAnimation.toValue= [NSNumbernumberWithFloat:_MiddleLayer.position.y+RectLength/2];
MiddleDownAnimation.duration=self.duration/2;
MiddleDownAnimation.repeatCount=1;
MiddleDownAnimation.removedOnCompletion=NO;
MiddleDownAnimation.fillMode=kCAFillModeForwards;
MiddleDownAnimation.autoreverses=NO;
创建一个动画组,组合旋转和下移的动画效果
CAAnimationGroup*MiddleAnimationGroup = [CAAnimationGroupanimation];
MiddleAnimationGroup.animations=@[MiddleRotateAnimation];
MiddleAnimationGroup.duration=self.duration/2;
MiddleAnimationGroup.repeatCount=MAXFLOAT;
MiddleAnimationGroup.removedOnCompletion=NO;
MiddleAnimationGroup.fillMode=kCAFillModeForwards;
MiddleAnimationGroup.autoreverses=NO;
上移就没有啦
最后在这些动画做完之后,整个view还有一个淡化的效果,这个只需要给
其中一个动画 设置 CAAnimationDelegate
delegate = self;
并实现
- (void)animationDidStop:(CAAnimation*)anim finished:(BOOL)flag
{[UIViewanimateWithDuration:1animations:^{
self.AnimationView.alpha=0;
}completion:^(BOOLfinished) {
[self.contextcompleteTransition:YES];
[self.AnimationViewremoveFromSuperview];
}];
}
一定要记住,在结束动画之后要 调用
[self.contextcompleteTransition:YES];
否则页面会卡住,不会再跳转了。
Dismiss回来的动画相差不多,在方向上进行改变就行
完整项目地址,使用的话只需要设置代理的时候设置为
ViewController.transitionDelegate = [FFTransitionDelegate shareInstance];
就能使用这个效果啦,大家喜欢的可以点一下star