iOS自定義交互式轉(zhuǎn)場(chǎng)動(dòng)畫-仿微信圖片轉(zhuǎn)場(chǎng)、酷狗轉(zhuǎn)場(chǎng) (從零到一 + 交互式圖片瀏覽器)

轉(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

homemenu.png
效果展示.gif
圖片瀏覽器.gif

目錄

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)(LYNavKuGouPushAnimatorLYNavKuGouPopAnimator),這樣大家理解起來(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)畫分析:首先看下示例圖

微信轉(zhuǎn)場(chǎng)示例.png

這個(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哈哈~

github:https://github.com/yangli-dev/LYCustomTransition

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容