動畫特效十九:自定義過度動畫2

本人錄製技術視頻地址:https://edu.csdn.net/lecturer/1899 歡迎觀看。

在前面的章節中,我已經介紹了自定義過度動畫的基本使用,大家可以參照這裏:動畫特效十三:自定義過度動畫之基本使用。這一節我使用更詳細的例子來闡述它的使用及引申出一些其他知識點。效果圖如下:




注意到,上面兩個Demo中均是自定義導航控制器切換的效果。由於實現原理相似,我只對第一個效果進行說明。在具體分析之前,我們先看一張結構圖,它詳細的描述了實現自定義導航控制器效果的具體步驟。


看起來很複雜的樣子,現在我們就用第一個Demo進行上述方法的說明。

Step One: 主控制器遵循 UINavigationControllerDelegate 協議。

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

這裏是在viewWillAppear方法中完成的代理屬性的初始化,不可以在viewDidLoad中初始化。如果在viewDidLoad中實現初始化,則導航控制器Push或者Pop的操作過程中,delegate對象就會丟失了,只會在第一次的時候起作用。

Step Two: 實現下面的代理方法,完成自定義的Push或者Pop操作。

- (nullable id <UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController

上面的方法可以看出,它是要求返回一個遵守了id<UIViewControllerAnimatedTransitioning>>協議的對象。如果直接將返回結果設置爲nil,可以看出就是系統默認的Push或者Pop操作。

Step Three: 自定義遵守了id<UIViewControllerAnimatedTransitioning>>協議的對象,處理自定義的過度動畫。假設自定義的類爲 LFPushAnimator, 則"Step Two"中的代碼如下:

- (nullable id <UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController
                                            animationControllerForOperation:(UINavigationControllerOperation)operation
                                                         fromViewController:(UIViewController *)fromVC
                                                           toViewController:(UIViewController *)toVC  {
    if (operation == UINavigationControllerOperationPush) {
        LFPushAnimator *animator = [[LFPushAnimator alloc] init];
        return animator;
    } else {
        return nil;
    }
}
而遵循了UIViewControllerAnimatedTransitioning這個協議的類需要實現兩個代理方法,來處理具體的自定義過度動畫。

Step Four: 處理自定義的過度動畫。

@interface LFPushAnimator()
@property (nonatomic, strong) id<UIViewControllerContextTransitioning> transitionContext;
@property (nonatomic, strong) CAShapeLayer *maskLayer;
@end

@implementation LFPushAnimator
- (NSTimeInterval)transitionDuration:(nullable id <UIViewControllerContextTransitioning>)transitionContext {
    return 2.0f;
}
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext {
    self.transitionContext = transitionContext;
    UIView *containerView = [transitionContext containerView];
    FirstViewController *fromVC = (FirstViewController *)[transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    SecondViewController *toVC = (SecondViewController *)[transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    
    [containerView addSubview:fromVC.view];
    [containerView addSubview:toVC.view];
    
    UIButton *button = fromVC.pushButton;
    UIBezierPath *startPath = [UIBezierPath bezierPathWithOvalInRect:button.frame];
    
    CGFloat centerToLeft = button.center.x;
    CGFloat centerToBottom = toVC.view.frame.size.height - button.center.y;
    CGFloat radius = sqrtf(centerToLeft * centerToLeft + centerToBottom * centerToBottom);
    // CGRectInset的計算公式
    /*
     r1.origin.x+=dx;//dx爲正數是+=,負數則-=
     
     r1.size.width-=dx*2;
     
     r1.origin.y+=dy;//dy爲正數是+=,負數則-=
     
     r1.size.height-=dy*2;
     */
    UIBezierPath *endPath = [UIBezierPath bezierPathWithOvalInRect:CGRectInset(button.frame, -radius, -radius)];
    
    self.maskLayer = [CAShapeLayer layer];
    // 設置最終的path爲endPath,不然會反彈
    self.maskLayer.path = endPath.CGPath;
    toVC.view.layer.mask = self.maskLayer;
    
    CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"path"];
    anim.duration = [self transitionDuration:transitionContext];
    anim.fromValue = (__bridge id _Nullable)(startPath.CGPath);
    anim.toValue = (__bridge id _Nullable)(endPath.CGPath);
    anim.delegate = self;
    anim.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
    [self.maskLayer addAnimation:anim forKey:@"animation"];
}

- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag {
    //告訴 iOS 這個 transition 完成
    [self.transitionContext completeTransition:![self.transitionContext transitionWasCancelled]];
    
    [self.maskLayer removeAnimationForKey:@"animation"];
    
    [self.transitionContext viewControllerForKey:UITransitionContextToViewControllerKey].view.layer.mask = nil;
}

@end

如果大家已經學習過我以前介紹的關於動畫處理的文章,相信很容易就能明白上述代碼了。代碼的難點就是從一個小圓過度到最終的大圓,其實就是數學計算的問題。請看下面的分析圖

1. 上面的兩個紅色的圓是同心圓。

2. 動畫過程中是從小圓慢慢擴散到大圓。

3. 擴散過程中被圓覆蓋的地方就會透明化,然後就會顯示出後面的內容,讓人感覺上面一層圖片是被"掀開的"。

Step Five:實現手動拖拽圖片的效果。

1. 定義一個比例過度的屬性,用戶進行後面的控制操作。

@property (nonatomic, strong) UIPercentDrivenInteractiveTransition *percentTransition;

2. 定義UIScreenEdgePanGestureRecognizer手勢,用它可以實現圖片邊角的拖拽效果。

- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.frame = [UIScreen mainScreen].bounds;
    
    UIScreenEdgePanGestureRecognizer *edgePan = [[UIScreenEdgePanGestureRecognizer alloc] initWithTarget:self action:@selector(edgeEvent:)];
    edgePan.edges = UIRectEdgeLeft;
    [self.view addGestureRecognizer:edgePan];
}

3. 實現拖拽的具體方法。

- (void)edgeEvent:(UIScreenEdgePanGestureRecognizer *)edgePan {
    CGFloat transitionX = [edgePan translationInView:self.view].x;
    CGFloat percent = transitionX / self.view.frame.size.width;
    percent = MIN(1.0, MAX(0.0, percent));
    if (edgePan.state == UIGestureRecognizerStateBegan) {
        self.percentTransition = [[UIPercentDrivenInteractiveTransition alloc] init];
        // 這裏結合使用
        [self.navigationController popViewControllerAnimated:YES];
    } else if (edgePan.state == UIGestureRecognizerStateChanged) {
        [self.percentTransition updateInteractiveTransition:percent];
    } else if (edgePan.state == UIGestureRecognizerStateEnded || edgePan.state == UIGestureRecognizerStateCancelled) {
        if (percent < 0.3) {
            [self.percentTransition cancelInteractiveTransition];
        } else {
            [self.percentTransition finishInteractiveTransition];
        }
    } else {
        self.percentTransition = nil;
    }
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章