轉(zhuǎn)場(chǎng)動(dòng)畫
轉(zhuǎn)場(chǎng)動(dòng)畫就是從一個(gè)場(chǎng)景以動(dòng)畫的形式過(guò)渡到另一個(gè)場(chǎng)景。自定義轉(zhuǎn)場(chǎng)動(dòng)畫的意義是脫離系統(tǒng)固定的轉(zhuǎn)場(chǎng),實(shí)現(xiàn)UI交互設(shè)計(jì)師設(shè)計(jì)的視覺(jué)效果強(qiáng)的轉(zhuǎn)場(chǎng)動(dòng)畫。
下圖是整個(gè)案例的Demo菜單截圖,為了方便大家一步一步掌握自定義轉(zhuǎn)場(chǎng)動(dòng)畫,每個(gè)效果我都寫了非常詳細(xì)的Demo(包括導(dǎo)航push的轉(zhuǎn)場(chǎng)和模態(tài)modal的轉(zhuǎn)場(chǎng)),建議大家先下載下來(lái)跟著文章一個(gè)案例一個(gè)案例自己去實(shí)現(xiàn)一下,會(huì)對(duì)理解十分有幫助。github地址:https://github.com/yangli-dev/LYCustomTransition



目錄
0、CATransition(系統(tǒng)轉(zhuǎn)場(chǎng)動(dòng)畫)
一、基礎(chǔ)轉(zhuǎn)場(chǎng)-非交互式(初步認(rèn)識(shí)自定義轉(zhuǎn)場(chǎng)動(dòng)畫)
二、仿酷狗轉(zhuǎn)場(chǎng)-非交互式(鞏固對(duì)轉(zhuǎn)場(chǎng)的認(rèn)識(shí))
三、仿微信轉(zhuǎn)場(chǎng)-非交互式(加強(qiáng)對(duì)轉(zhuǎn)場(chǎng)的認(rèn)識(shí))
四、基礎(chǔ)轉(zhuǎn)場(chǎng)-交互式
五、實(shí)戰(zhàn) - 網(wǎng)友提問(wèn) - Question One
六、圖片瀏覽器-PictureBrowse
說(shuō)明:本文章目前只講解了demo中的部分案例,但其它的跟這幾個(gè)類似,擴(kuò)展一下就好,如有問(wèn)題請(qǐng)留言
0、CATransition(系統(tǒng)轉(zhuǎn)場(chǎng)動(dòng)畫)
首先來(lái)個(gè)最簡(jiǎn)單的改變轉(zhuǎn)場(chǎng)效果的方法(不是自定義,是通過(guò)官方提供的動(dòng)畫效果實(shí)現(xiàn)),提供動(dòng)畫效果的這個(gè)類就是CATransition
CATransition
CATransition 是CAAnimation的子類,用于頁(yè)面之間的過(guò)度動(dòng)畫,官方提供了四個(gè)公有的API動(dòng)畫效果,但是私有API的效果更加炫酷(謹(jǐn)慎使用私有的API)
(1)Nav導(dǎo)航轉(zhuǎn)場(chǎng):要改變轉(zhuǎn)場(chǎng)動(dòng)畫,其實(shí)方法只有一個(gè),非常容易理解:
[self.navigationController.view.layer addAnimation:[self pushAnimation] forKey:nil],
(2)modal模態(tài)轉(zhuǎn)場(chǎng):和nav類似,
[self.view.window.layer addAnimation:[self presentAnimation] forKey:nil];
意思是在視圖的圖層上添加一個(gè)CAAnimation類動(dòng)畫,然后圖層執(zhí)行這個(gè)類提供的動(dòng)畫效果,故轉(zhuǎn)場(chǎng)動(dòng)畫也就改變了。
通過(guò)CAAnimation 的子類CATransition可以快速創(chuàng)建動(dòng)畫效果,以下代碼是改變系統(tǒng)轉(zhuǎn)場(chǎng)動(dòng)畫的具體實(shí)現(xiàn)
- (void)pushSecond{
LYCATransitionSecondVC *second = [[LYCATransitionSecondVC alloc] init];
[self.navigationController.view.layer addAnimation:[self pushAnimation] forKey:nil];//添加Animation
[self.navigationController pushViewController:second animated:NO]; //記得這里的animated要設(shè)為NO,不然會(huì)重復(fù)
/* modal模態(tài)
LYModalCATransitionSecondVC *second = [[LYModalCATransitionSecondVC alloc] init];
[self.view.window.layer addAnimation:[self presentAnimation] forKey:nil];//添加Animation
[self presentViewController:second animated:NO completion:nil]; //記得這里的animated要設(shè)為NO,不然會(huì)重復(fù)
*/
}
- (CATransition *)pushAnimation{
CATransition* transition = [CATransition animation];
transition.duration = 0.8;
transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault];
/*私有API
cube 立方體效果
pageCurl 向上翻一頁(yè)
pageUnCurl 向下翻一頁(yè)
rippleEffect 水滴波動(dòng)效果
suckEffect 變成小布?jí)K飛走的感覺(jué)
oglFlip 上下翻轉(zhuǎn)
cameraIrisHollowClose 相機(jī)鏡頭關(guān)閉效果
cameraIrisHollowOpen 相機(jī)鏡頭打開(kāi)效果
*/
transition.type = @"cube";
//transition.type = kCATransitionMoveIn;
//下面四個(gè)是系統(tǒng)公有的API
//kCATransitionMoveIn, kCATransitionPush, kCATransitionReveal, kCATransitionFade
transition.subtype = kCATransitionFromRight;
//kCATransitionFromLeft, kCATransitionFromRight, kCATransitionFromTop, kCATransitionFromBottom
return transition;
}
看完是不是很簡(jiǎn)單,趕緊自己嘗試一下吧。接下來(lái)就是真正的自定義轉(zhuǎn)場(chǎng)動(dòng)畫的學(xué)習(xí)了。
真正的自定義轉(zhuǎn)場(chǎng)從這里開(kāi)始
從iOS7開(kāi)始,蘋果提供了真正能自定義轉(zhuǎn)場(chǎng)動(dòng)畫的API,這才使得我們可以為APP定義自己特有的轉(zhuǎn)場(chǎng)效果。轉(zhuǎn)場(chǎng)有非交互式和交互式轉(zhuǎn)場(chǎng),這里當(dāng)然是從基本的非交互式的轉(zhuǎn)場(chǎng)開(kāi)始說(shuō)起。
其實(shí)導(dǎo)航push和模態(tài)modal自定義轉(zhuǎn)場(chǎng)的實(shí)現(xiàn),只是一個(gè)協(xié)議的區(qū)別,
實(shí)現(xiàn)push的類去遵循UINavigationControllerDelegate協(xié)議;
實(shí)現(xiàn)modal的類去遵循UIViewControllerTransitioningDelegate協(xié)議。
兩個(gè)協(xié)議里面的方法都大同小異,所以此系列文章就講push轉(zhuǎn)場(chǎng)中的案例實(shí)現(xiàn)。
具體看Demo就知道了_(可能在這里你并不知道這兩個(gè)協(xié)議是干嘛的,不要擔(dān)心,下面馬上就一一道來(lái))
一、基礎(chǔ)轉(zhuǎn)場(chǎng)-非交互式(初步認(rèn)識(shí)自定義轉(zhuǎn)場(chǎng)動(dòng)畫)
完成這個(gè)案例只需要簡(jiǎn)單的兩步就可實(shí)現(xiàn),耐心并仔細(xì)看下去,你會(huì)發(fā)現(xiàn)自定義轉(zhuǎn)場(chǎng)其實(shí)也很簡(jiǎn)單!
1. 遵循UINavigationControllerDelegate協(xié)議,設(shè)置代理。
比如在Demo中的Nav-BaseTransition案例,在LYNavBaseVC本個(gè)類中自己遵循UINavigationControllerDelegate此協(xié)議,在push轉(zhuǎn)場(chǎng)之前設(shè)置代理self.navigationController.delegate = self,然后再實(shí)現(xiàn)其協(xié)議特有方法,當(dāng)push操作執(zhí)行時(shí),就會(huì)回調(diào)實(shí)現(xiàn)的代理方法,代理方法會(huì)要求返回遵循了UIViewControllerAnimatedTransitioning協(xié)議的代理對(duì)象,從而去執(zhí)行所對(duì)應(yīng)的動(dòng)畫。(代碼中的LYNavBaseCustomAnimator是遵循了UIViewControllerAnimatedTransitioning協(xié)議的類,這個(gè)協(xié)議是專門在轉(zhuǎn)場(chǎng)中提供并執(zhí)行轉(zhuǎn)場(chǎng)動(dòng)畫,稍后會(huì)在第2小節(jié)詳細(xì)介紹)
- (nullable id <UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController
animationControllerForOperation:(UINavigationControllerOperation)operation
fromViewController:(UIViewController *)fromVC
toViewController:(UIViewController *)toVC;
具體代碼實(shí)現(xiàn)
- (nullable id <UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController
animationControllerForOperation:(UINavigationControllerOperation)operation
fromViewController:(UIViewController *)fromVC
toViewController:(UIViewController *)toVC{
if (operation == UINavigationControllerOperationPush) {
return self.customAnimator;
}else if (operation == UINavigationControllerOperationPop){
return self.customAnimator;
}
return nil;
}
- (LYNavBaseCustomAnimator *)customAnimator
{
if (_customAnimator == nil) {
_customAnimator = [[LYNavBaseCustomAnimator alloc]init];
}
return _customAnimator;
}
2.創(chuàng)建提供動(dòng)畫效果的執(zhí)行者
上述代碼中的
LYNavBaseCustomAnimator就是動(dòng)畫效果的執(zhí)行者。它是遵循了UIViewControllerAnimatedTransitioning協(xié)議的類。
協(xié)議 UIViewControllerAnimatedTransitioning,這個(gè)協(xié)議是轉(zhuǎn)場(chǎng)動(dòng)畫中,動(dòng)畫效果的執(zhí)行者,實(shí)現(xiàn)這個(gè)協(xié)議的類具有負(fù)責(zé)給轉(zhuǎn)場(chǎng)提供各種復(fù)雜動(dòng)畫效果的能力。協(xié)議里有兩個(gè)必須要實(shí)現(xiàn)的方法
//這個(gè)方法控制轉(zhuǎn)場(chǎng)動(dòng)畫的時(shí)間長(zhǎng)度
- (NSTimeInterval)transitionDuration:(nullable id <UIViewControllerContextTransitioning>)transitionContext;
//這個(gè)是轉(zhuǎn)場(chǎng)上下文,提供轉(zhuǎn)場(chǎng)過(guò)程中兩個(gè)控制器的具體信息。
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext;`
具體代碼實(shí)現(xiàn)
- (NSTimeInterval)transitionDuration:(nullable id <UIViewControllerContextTransitioning>)transitionContext{
return 0.5;
}
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext{
//轉(zhuǎn)場(chǎng)過(guò)渡的容器view
UIView *containerView = [transitionContext containerView];
//FromVC
UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIView *fromView = fromViewController.view;
fromView.frame = CGRectMake(0, 0, kScreenWidth, kScreenHeight);
//ToVC
UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIView *toView = toViewController.view;
toView.frame = CGRectMake(0, 0, kScreenWidth, kScreenHeight);
//此處判斷是push,還是pop 操作
BOOL isPush = ([toViewController.navigationController.viewControllers indexOfObject:toViewController] > [fromViewController.navigationController.viewControllers indexOfObject:fromViewController]);
if (isPush) {
[containerView addSubview:fromView];
[containerView addSubview:toView];//push,這里的toView 相當(dāng)于secondVC的view
toView.frame = CGRectMake(kScreenWidth, kScreenHeight, kScreenWidth, kScreenHeight);
}else{
[containerView addSubview:toView];
[containerView addSubview:fromView];//pop,這里的fromView 也是相當(dāng)于secondVC的view
fromView.frame = CGRectMake(0, 0, kScreenWidth, kScreenHeight);
}
//因?yàn)閟econdVC的view在firstVC的view之上,所以要后添加到containerView中
//動(dòng)畫
[UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
if (isPush) {
toView.frame = CGRectMake(0, 0, kScreenWidth, kScreenHeight);
}else{
fromView.frame = CGRectMake(kScreenWidth, kScreenHeight, kScreenWidth, kScreenHeight);
}
} completion:^(BOOL finished) {
BOOL wasCancelled = [transitionContext transitionWasCancelled];
//設(shè)置transitionContext通知系統(tǒng)動(dòng)畫執(zhí)行完畢
[transitionContext completeTransition:!wasCancelled];
}];
}
這里簡(jiǎn)單介紹下fromView和toView,不然大家可能會(huì)有點(diǎn)繞
A push ---> B
B pop ---> A
|| ||
fromView toView
誰(shuí)在轉(zhuǎn)場(chǎng)中主動(dòng)發(fā)起轉(zhuǎn)場(chǎng),誰(shuí)就是fromVC、fromView
A主動(dòng)push到B,A就是fromVC
B主動(dòng)pop到A,B就是fromVC
從代碼中可以看出,轉(zhuǎn)場(chǎng)動(dòng)畫的自定義,就是對(duì)fromView和toView的操作,而這兩個(gè)view都是可以在這個(gè)協(xié)議的上下文中獲取,所以,
我們不難實(shí)現(xiàn)一些簡(jiǎn)單的自定義轉(zhuǎn)場(chǎng)。
案例一小結(jié)
對(duì)于非交互式的轉(zhuǎn)場(chǎng)來(lái)說(shuō),其實(shí)就只需要實(shí)現(xiàn)兩個(gè)協(xié)議的相關(guān)方法:
第一個(gè)是UINavigationControllerDelegate,作用好比是告訴系統(tǒng)我有自己的轉(zhuǎn)場(chǎng)動(dòng)畫了,我要去調(diào)我自定義的。
第二個(gè)是UIViewControllerAnimatedTransitioning,作用好比是我制作好了動(dòng)畫了,需要的你直接調(diào)用就好了。
二、仿酷狗轉(zhuǎn)場(chǎng)-非交互式(鞏固對(duì)轉(zhuǎn)場(chǎng)的認(rèn)識(shí))
動(dòng)畫解析:
首先可以了解到這個(gè)動(dòng)畫其實(shí)也是一個(gè)線性動(dòng)畫,只不過(guò)是弧線形的,那么給定起始和終止?fàn)顟B(tài)的位置就可以了。跟案例一類似,只不過(guò)這里多了一個(gè)旋轉(zhuǎn),這個(gè)動(dòng)畫可以用組動(dòng)畫CAAnimationGroup實(shí)現(xiàn),但是鑒于效果不是太流暢,這里我采用的是仿射變換CGAffineTransform實(shí)現(xiàn)的。代碼如下
@implementation LYNavKuGouPushAnimator
- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext{
return 0.4;
}
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext{
//轉(zhuǎn)場(chǎng)過(guò)渡的容器view
UIView *containerView = [transitionContext containerView];
//ToVC
UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIView *toView = toViewController.view;
[containerView addSubview:toView];
//動(dòng)畫 仿射變換動(dòng)畫
float centerX = toView.bounds.size.width * 0.5;
float centerY = toView.bounds.size.height * 0.5;
float x = toView.bounds.size.width * 0.5;
float y = toView.bounds.size.height * 1.8;
//起始狀態(tài): 原始狀態(tài)繞x,y旋轉(zhuǎn)45o后的狀態(tài)
CGAffineTransform trans = [self GetCGAffineTransformRotateAroundCenterX:centerX centerY:centerY x:x y:y angle:45.0/180.0*M_PI];
toView.transform = trans;
[UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
//終止?fàn)顟B(tài): 原始狀態(tài)
toView.transform = CGAffineTransformIdentity;
} completion:^(BOOL finished) {
BOOL wasCancelled = [transitionContext transitionWasCancelled];
//設(shè)置transitionContext通知系統(tǒng)動(dòng)畫執(zhí)行完畢
[transitionContext completeTransition:!wasCancelled];
}];
}
/**
仿射變換
@param centerX view的中心點(diǎn)X坐標(biāo)
@param centerY view的中心點(diǎn)Y坐標(biāo)
@param x 旋轉(zhuǎn)中心x坐標(biāo)
@param y 旋轉(zhuǎn)中心y坐標(biāo)
@param angle 旋轉(zhuǎn)的角度
@return CGAffineTransform對(duì)象
*/
- (CGAffineTransform)GetCGAffineTransformRotateAroundCenterX:(float)centerX centerY:(float)centerY x:(float)x y:(float)y angle:(float)angle{
CGFloat l = y - centerY;
CGFloat h = l * sin(angle);
CGFloat b = l * cos(angle);
CGFloat a = l - b;
CGFloat x1 = h;
CGFloat y1 = a;
CGAffineTransform trans = CGAffineTransformMakeTranslation(x1, y1);
trans = CGAffineTransformRotate(trans,angle);
return trans;
}
@end
看到這個(gè)類的代碼是不是更清爽了,那是因?yàn)閺倪@個(gè)案例開(kāi)始起我就把push和pop的Animator分別用一個(gè)類實(shí)現(xiàn)(LYNavKuGouPushAnimator 和 LYNavKuGouPopAnimator),這樣大家理解起來(lái)思路也會(huì)更清晰啦~
(還有從這個(gè)案例開(kāi)始UINavigationControllerDelegate協(xié)議也用一個(gè)單獨(dú)的類實(shí)現(xiàn),比如這個(gè)案例中的LYNavKuGouAnimationTransition就是遵循并實(shí)現(xiàn)了該協(xié)議方法的類,在控制器中設(shè)置這個(gè)類的對(duì)象為代理即可)
案例二小結(jié)
該轉(zhuǎn)場(chǎng)動(dòng)畫的精髓也就是
GetCGAffineTransformRotateAroundCenterX: centerY: x: y: angle:方法,這個(gè)方法使得可以根據(jù)傳入的參數(shù)計(jì)算出view的變換后的位置狀態(tài)。(關(guān)于CGAffineTransform更多知識(shí),請(qǐng)自行Google,這里就不贅述了)
有了變換前后的狀態(tài),動(dòng)畫效果用一個(gè)簡(jiǎn)單的UIView動(dòng)畫也就可以實(shí)現(xiàn)了。
三、仿微信轉(zhuǎn)場(chǎng)-非交互式(加強(qiáng)對(duì)轉(zhuǎn)場(chǎng)的認(rèn)識(shí))
動(dòng)畫分析:首先看下示例圖

這個(gè)動(dòng)畫和前兩個(gè)動(dòng)畫就有點(diǎn)不同了,前兩個(gè)動(dòng)畫是對(duì)整個(gè)界面進(jìn)行的動(dòng)畫操作,而這個(gè)動(dòng)畫只是對(duì)縮放的圖片進(jìn)行動(dòng)畫操作,背景顏色僅做了漸變效果。
既然知道了只是對(duì)圖片進(jìn)行動(dòng)畫操作,那就不難想到,在containerView上加上一個(gè)UIImageView,然后對(duì)此做動(dòng)畫操作,即可完成需求。
看下代碼:
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext{
//轉(zhuǎn)場(chǎng)過(guò)渡的容器view
UIView *containerView = [transitionContext containerView];
//FromVC
UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIView *fromView = fromViewController.view;
[containerView addSubview:fromView];
//ToVC
UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIView *toView = toViewController.view;
[containerView addSubview:toView];
toView.hidden = YES;
//圖片背景的空白view (設(shè)置和控制器的背景顏色一樣,給人一種圖片被調(diào)走的假象 [可以換種顏色看看效果])
UIView *imgBgWhiteView = [[UIView alloc] initWithFrame:self.transitionBeforeImgFrame];
imgBgWhiteView.backgroundColor = bgColor;
[containerView addSubview:imgBgWhiteView];
//有漸變的黑色背景
UIView *bgView = [[UIView alloc] initWithFrame:containerView.bounds];
bgView.backgroundColor = [UIColor blackColor];
bgView.alpha = 0;
[containerView addSubview:bgView];
//過(guò)渡的圖片
UIImageView *transitionImgView = [[UIImageView alloc] initWithImage:self.transitionImgView.image];
transitionImgView.frame = self.transitionBeforeImgFrame;
[transitionContext.containerView addSubview:transitionImgView];
[UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0.0 usingSpringWithDamping:0.7 initialSpringVelocity:0.3 options:UIViewAnimationOptionCurveLinear animations:^{
transitionImgView.frame = self.transitionAfterImgFrame;
bgView.alpha = 1;
} completion:^(BOOL finished) {
toView.hidden = NO;
[imgBgWhiteView removeFromSuperview];
[bgView removeFromSuperview];
[transitionImgView removeFromSuperview];
BOOL wasCancelled = [transitionContext transitionWasCancelled];
//設(shè)置transitionContext通知系統(tǒng)動(dòng)畫執(zhí)行完畢
[transitionContext completeTransition:!wasCancelled];
}];
}
代碼中,空白view和漸變的黑色背景都是扮演輔助角色的,而過(guò)渡圖片才是核心。要實(shí)現(xiàn)動(dòng)畫效果,必須得有三個(gè)數(shù)據(jù):image圖像、轉(zhuǎn)場(chǎng)前imageView的frame和轉(zhuǎn)場(chǎng)后imageView的frame。這三個(gè)數(shù)據(jù)都是從第一個(gè)VC里面計(jì)算得來(lái)的,只需按邏輯步驟一步步傳過(guò)來(lái)即可。
其中要注意的點(diǎn):
(1) toView加到containerView上時(shí),需要先隱藏,等到動(dòng)畫結(jié)束時(shí)再顯示,不然toView 會(huì)蓋住整個(gè)fromView。
(2) 其中除了系統(tǒng)的fromView和toView,其它所有的view在動(dòng)畫結(jié)束時(shí)必須移除,不然會(huì)一直在containerView上存在。
(3) popAnimator 中的fromView 不用加到containerView中了,因?yàn)榇宿D(zhuǎn)場(chǎng)在pop時(shí)不需要fromView的參與了,加上會(huì)出現(xiàn)整個(gè)界面沒(méi)有變化的bug。
案例三小結(jié):
1.在VC中計(jì)算好Animator中必要的三個(gè)參數(shù),然后依次傳遞到Animator中。
2.獲取得到傳入的數(shù)據(jù),對(duì)過(guò)渡圖片根據(jù)做動(dòng)畫處理
四、基礎(chǔ)轉(zhuǎn)場(chǎng)-交互式
交互式轉(zhuǎn)場(chǎng):人為控制轉(zhuǎn)場(chǎng)過(guò)渡,最常見(jiàn)的交互轉(zhuǎn)場(chǎng)動(dòng)畫就是系統(tǒng)自帶的側(cè)滑返回。
此案例請(qǐng)對(duì)照demo
1.實(shí)現(xiàn)代理方法
在LYNavBaseInteractiveAnimatedTransition類里,相較于案例一中的UINavigationControllerDelegate協(xié)議,要多實(shí)現(xiàn)一個(gè)代理方法, 即:
- (nullable id <UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController *)navigationController
interactionControllerForAnimationController:(id <UIViewControllerAnimatedTransitioning>) animationController;
此方法會(huì)返回一個(gè)遵循了UIViewControllerInteractiveTransitioning協(xié)議的代理對(duì)象,實(shí)現(xiàn)這個(gè)方法,系統(tǒng)轉(zhuǎn)場(chǎng)時(shí),就會(huì)知道當(dāng)前是否有交互式的轉(zhuǎn)場(chǎng),有便執(zhí)行交互轉(zhuǎn)場(chǎng),無(wú)則執(zhí)行普通自定義的轉(zhuǎn)場(chǎng)動(dòng)畫。
具體代碼實(shí)現(xiàn):
- (nullable id <UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController
animationControllerForOperation:(UINavigationControllerOperation)operation
fromViewController:(UIViewController *)fromVC
toViewController:(UIViewController *)toVC{
if (operation == UINavigationControllerOperationPush) {
return self.customAnimator;
}else if (operation == UINavigationControllerOperationPop){
return self.customAnimator;
}
return nil;
}
- (LYNavBaseCustomAnimator *)customAnimator
{
if (_customAnimator == nil) {
_customAnimator = [[LYNavBaseCustomAnimator alloc]init];
}
return _customAnimator;
}
- (nullable id <UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController *)navigationController
interactionControllerForAnimationController:(id <UIViewControllerAnimatedTransitioning>) animationController{
if (self.gestureRecognizer)
return self.percentIntractive;
else
return nil;
}
- (void)setGestureRecognizer:(UIPanGestureRecognizer *)gestureRecognizer{
_gestureRecognizer = gestureRecognizer;
}
- (LYNavBasePercentDerivenInteractive *)percentIntractive{
if (!_percentIntractive) {
_percentIntractive = [[LYNavBasePercentDerivenInteractive alloc] initWithGestureRecognizer:self.gestureRecognizer];
}
return _percentIntractive;
}
其中,
(1)gestureRecognizer 是在secondVC加入的一個(gè)交互手勢(shì),在pop時(shí)是需要傳遞過(guò)來(lái)的,后面會(huì)講到。
(2)percentIntractive 是LYNavBasePercentDerivenInteractive 類對(duì)象,這個(gè)類繼承于 UIPercentDrivenInteractiveTransition類,UIPercentDrivenInteractiveTransition類是交互轉(zhuǎn)場(chǎng)中的核心類,后面會(huì)講到。
2.新建一個(gè)繼承于 UIPercentDrivenInteractiveTransition類的類 LYNavBasePercentDerivenInteractive
UIPercentDrivenInteractiveTransition類是系統(tǒng)定義的,它遵循了 UIViewControllerInteractiveTransitioning協(xié)議,故可做為第一節(jié)中的代理對(duì)象。
此類又定義了三個(gè)方法供交互轉(zhuǎn)場(chǎng)時(shí)調(diào)用:
//更新轉(zhuǎn)場(chǎng)過(guò)程的百分比
- (void)updateInteractiveTransition:(CGFloat)percentComplete;
//取消轉(zhuǎn)場(chǎng)
- (void)cancelInteractiveTransition;
//完成轉(zhuǎn)場(chǎng)
- (void)finishInteractiveTransition;
具體代碼實(shí)現(xiàn):
- (void)gestureRecognizeDidUpdate:(UIPanGestureRecognizer *)gestureRecognizer
{
CGFloat scale = 1 - [self percentForGesture:gestureRecognizer];
switch (gestureRecognizer.state)
{
case UIGestureRecognizerStateBegan:
//沒(méi)用
break;
case UIGestureRecognizerStateChanged:
//更新百分比
[self updateInteractiveTransition:scale];
break;
case UIGestureRecognizerStateEnded:
if (scale < 0.3){
//取消轉(zhuǎn)場(chǎng)
[self cancelInteractiveTransition];
}
else{
//完成轉(zhuǎn)場(chǎng)
[self finishInteractiveTransition];
}
break;
default:
//取消轉(zhuǎn)場(chǎng)
[self cancelInteractiveTransition];
break;
}
}
在此類中,根據(jù)pop時(shí)傳遞過(guò)來(lái)的手勢(shì)信息,計(jì)算獲得滑動(dòng)距離所占屏幕的百分比,從而根據(jù)百分比來(lái)處理轉(zhuǎn)場(chǎng)的取消與完成。
3.傳值
此處傳值跟之前都有不同的地方,我們這里的交互是在pop時(shí)做交互動(dòng)畫,故傳值是在SecondVC中傳入的。
具體代碼:
- (void)interactiveTransitionRecognizerAction:(UIPanGestureRecognizer *)gestureRecognizer
{
CGPoint translation = [gestureRecognizer translationInView:gestureRecognizer.view];
CGFloat scale = 1 - fabs(translation.x / kScreenWidth);
scale = scale < 0 ? 0 : scale;
NSLog(@"second = %f", scale);
switch (gestureRecognizer.state) {
case UIGestureRecognizerStatePossible:
break;
case UIGestureRecognizerStateBegan:{
//1. 設(shè)置代理
self.animatedTransition = nil;
self.navigationController.delegate = self.animatedTransition;
//2. 傳值
self.animatedTransition.gestureRecognizer = gestureRecognizer;
//3. push跳轉(zhuǎn)
[self.navigationController popViewControllerAnimated:YES];
}
break;
case UIGestureRecognizerStateChanged: {
break;
}
case UIGestureRecognizerStateFailed:
case UIGestureRecognizerStateCancelled:
case UIGestureRecognizerStateEnded: {
self.animatedTransition.gestureRecognizer = nil;
}
}
}
此案例注意的點(diǎn):
(1)LYNavBaseInteractiveAnimatedTransition類中的customAnimator是直接用的案例一中的
五、實(shí)戰(zhàn) - 網(wǎng)友提問(wèn) - Question One
此案例為本文章2樓網(wǎng)友的提問(wèn)解答
此案例其實(shí)和案例三的實(shí)現(xiàn)方式基本一致,單從轉(zhuǎn)場(chǎng)角度來(lái)說(shuō),不同點(diǎn)可以有兩個(gè):(1)轉(zhuǎn)場(chǎng)前ImageView的frame不定,案例三中ImageView的frame就一個(gè)(2)轉(zhuǎn)場(chǎng)后的位置跟案例三不同
而這兩個(gè)不同點(diǎn)都是我們可以計(jì)算得到的,所以要實(shí)現(xiàn)這個(gè)動(dòng)畫不難。
先看代碼
// 獲取指定視圖在window中的位置
- (CGRect)getFrameInWindow:(UIView *)view
{
return [view.superview convertRect:view.frame toView:nil];
}
此方法即可解決(1)中的問(wèn)題,點(diǎn)擊cell時(shí),傳入cell上的UIImageView對(duì)象,即可返回此View在window上的frame,這樣在轉(zhuǎn)場(chǎng)中的過(guò)渡ImageView就可根據(jù)此frame設(shè)置轉(zhuǎn)場(chǎng)前的位置了
- (CGRect)backScreenImageViewRectWithImage:(UIImage *)image{
CGSize size = image.size;
CGSize newSize;
newSize.height = kScreenWidth * 0.6;
newSize.width = newSize.height / size.height * size.width;
CGFloat imageY = 0;
CGFloat imageX = (kScreenWidth - newSize.width) * 0.5;
CGRect rect = CGRectMake(imageX, imageY, newSize.width, newSize.height);
return rect;
}
此方法可解決(2)中的問(wèn)題,傳入image,即可根據(jù)自己的需求,計(jì)算得出轉(zhuǎn)場(chǎng)后圖片的位置。
六、圖片瀏覽器-PictureBrowse
封裝了圖片瀏覽器,demo中是封裝的模態(tài)方式跳轉(zhuǎn),如有需求導(dǎo)航方式push的,請(qǐng)將
LYPictureBrowseInteractiveAnimatedTransition類中遵循的協(xié)議修改為UINavigationControllerDelegate,并修改相應(yīng)的代理方法(請(qǐng)仿照上面幾個(gè)案例),別忘了跳轉(zhuǎn)中的present、dismiss修改為push、pop方法。
使用方法:
(1)在你的工程中導(dǎo)入LYPictureBrowse 文件夾,并引入LYPictureBrowse.h 頭文件
(2)構(gòu)造四個(gè)必須的參數(shù)transitionImage、firstVCImgFrames、transitionImgIndex、dataSouceArray。具體構(gòu)造方法請(qǐng)對(duì)照demo編寫
-------------------- 完結(jié) --------------------
更新日志:
2017.07.05
(1) 更新案例0 - CATransition
(2) 更新案例一 - 基礎(chǔ)轉(zhuǎn)場(chǎng)-非交互
(3) 新增案例二 - 仿酷狗轉(zhuǎn)場(chǎng)-非交互
(4) 新增案例三 - 仿微信轉(zhuǎn)場(chǎng)-非交互
(5) 新增案例四 - 基礎(chǔ)轉(zhuǎn)場(chǎng)-交互式
(6) 新增案例五 - 實(shí)戰(zhàn) - 網(wǎng)友提問(wèn) - Question One
更新日志:
2017.12.11
(1)新增圖片瀏覽器框架
如果看了本篇文章能對(duì)你有所幫助的小伙伴們,就來(lái)個(gè)贊給個(gè)鼓勵(lì)吧!畢竟碼字不易 O(∩_∩)O哈哈~