CoreAnimation | 核心動畫 | 自定義轉場動畫

效果展示

自定義轉場動畫效果

實現(xiàn)思路

  1. 實現(xiàn)UINavigationControllerDelegate,利用一個類去定義我們的轉場動畫
  2. 利用貝塞爾話兩個圓,一個小圓一個大圓,他們的中心都是一樣的,然后根據不同的界面做對應的放大和縮小動畫

總結 : 1.實現(xiàn)相關轉場動畫協(xié)議 2.就是添加轉場動畫

準備工作

#import "DNCircleTransition.h" 
@interface ViewController ()<UINavigationControllerDelegate>

需要實現(xiàn)的控制器需要實現(xiàn)的協(xié)議和注意點

  1. 需要創(chuàng)建一個類來實現(xiàn)一個UIViewControllerAnimatedTransitioning 這個協(xié)議
  2. 當前的控制器需要遵從UINavigationControllerDelegate 協(xié)議
  3. 需要在控制器將要顯示的時候設置代理,不然不會走代理方法

補充:DNCircleTransition 這個類是NSObject類 里面遵從了UIViewControllerAnimatedTransitioning這個協(xié)議

- (void)viewWillAppear:(BOOL)animated{
    self.navigationController.delegate = self;
}

實現(xiàn)的代理方法

#pragma mark - UINavigationControllerDelegate
//就是告訴nav,我不需要自帶的轉場動畫了,我想自定義轉場動畫
- (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC{
    if (operation == UINavigationControllerOperationPush) {
        DNCircleTransition *trans = [DNCircleTransition new];
        trans.isPush = YES;
        return trans;
    }else{
        return nil;
    }
}
  • 重點: 就是告訴nav,我不需要自帶的轉場動畫了,我想自定義轉場動畫

DNCircleTransition 需要實現(xiàn)的事情

  • Mark - UIViewControllerContextTransitioning

//轉場動畫的時間
- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext{
    return .8f;
}
  1. 這個是轉場動畫的時間設置
  • 核心代碼

//在此方法中實現(xiàn)具體的轉場動畫
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext{
    /*
     拆分:
     1.2個圓(重點:半徑,中心點)
     2.添加動畫
     */
    //1.持有上下文
    _context = transitionContext;
    //2.獲取一個view的容器
    UIView *containerView = [transitionContext containerView];
    //3.獲取tovc的view,然后添加到容器里面
    UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
//    [containerView addSubview:toVC.view];
    //4.添加動畫
    UIButton *btn;
    ViewController *VC1;
    DNBlackViewController *VC2;
    if (_isPush) {
        VC1 = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
        VC2 = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
        btn = VC1.blackBtn;

    }else{
        VC2 = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
        VC1 = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
        btn = VC2.redBtn;
    }
    [containerView addSubview:VC1.view];
    [containerView addSubview:VC2.view];
//    ViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
//    UIButton *btn = fromVC.blackBtn;
    //5.畫一個小圓(大小圓的中心點是一樣)
    UIBezierPath *smallPath = [UIBezierPath bezierPathWithOvalInRect:btn.frame];
    //6.中心點
    CGPoint centerP;
    centerP = btn.center;
    //7.求大圓的半徑
    CGFloat radius;
    //8.判斷按鈕在哪個象限
    CGFloat y = CGRectGetHeight(toVC.view.frame)-btn.center.y;
    CGFloat x = CGRectGetWidth(toVC.view.frame) - btn.center.x;
    if (btn.frame.origin.x > CGRectGetWidth(toVC.view.frame)/2) {
        if (btn.frame.origin.y < CGRectGetHeight(toVC.view.frame)/2) {
            //1
            radius = sqrtf(btn.center.x*btn.center.x + y*y);
        }else{
            //4
            radius = sqrtf(btn.center.x*btn.center.x + btn.center.y*btn.center.y);
        }
    }else{
        if (CGRectGetMaxY(btn.frame) < CGRectGetHeight(toVC.view.frame)/2) {
            //2
            radius = sqrtf(x*x + y*y);
        }else{
            //3
            radius = sqrtf(btn.center.y*btn.center.y + x*x);
        }
    }
    //9.用貝塞爾畫大圓
    UIBezierPath *bigPath = [UIBezierPath bezierPathWithArcCenter:centerP radius:radius startAngle:0 endAngle:M_PI*2 clockwise:YES];
    //10.把path添加到layer
    CAShapeLayer *shapeLayer = [CAShapeLayer layer];
    if (_isPush) {
        shapeLayer.path = bigPath.CGPath;

    }else{
        shapeLayer.path = smallPath.CGPath;

    }
//    [toVC.view.layer addSublayer:shapeLayer];
    //11.蒙板
    UIViewController *VC;
    if (_isPush) {
        VC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    }else{
        VC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];

    }
    VC.view.layer.mask = shapeLayer;
    //12.動畫
    CABasicAnimation *anim = [CABasicAnimation animation];
    anim.keyPath = @"path";
    if (_isPush) {
        anim.fromValue = (id)smallPath.CGPath;

    }else{
        anim.fromValue = (id)bigPath.CGPath;
    }
//    anim.toValue = bigPath;
    anim.delegate = self;
    [shapeLayer addAnimation:anim forKey:nil];
}
  • 動畫結束

#pragma mark - CAAnimationDelegate
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag{
    [_context completeTransition:YES];
    UIViewController *VC;
    if (_isPush) {
        VC = [_context viewControllerForKey:UITransitionContextToViewControllerKey];

    }else{
        VC = [_context viewControllerForKey:UITransitionContextFromViewControllerKey];
    }
    VC.view.layer.mask = nil;
}
  • 要點說明

  1. 這里的重點是需要判斷是push還是pop,來處理需要哪個控制器做動畫處理
  2. layer不是添加在視圖的layer上面,而是要添加在蒙版上面
  3. 蒙版的獲取和移除
  4. 如果已經指定了最后的路徑就沒必要在寫了,不然還要添加兩個方法,remove 最后動畫不移除
  5. 畫圓的時候需要注意一個象限的判斷,因為獲取大圓的半徑是不同計算的
  6. 動畫是在容器里面處理的
  7. 需要記錄是push還是pop類型,需要處理的控制器對象不同(_isPush)
  • 關于象限的演示和說明

利用勾股定律計算出大圓半徑
交互點在第一象限

注意不同象限計算的方法
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容