iOS 模仿格瓦拉電影的轉(zhuǎn)場動畫

模仿格瓦拉電影的轉(zhuǎn)場動畫


demo.gif

自定義轉(zhuǎn)場動畫

  • 首先就要聲明一個遵守UIViewControllerAnimatedTransitioning協(xié)議的類.
  • 然后實現(xiàn)協(xié)議中的兩個函數(shù)
// This is used for percent driven interactive transitions, as well as for container controllers that have companion animations that might need to
// 返回轉(zhuǎn)場時間
- (NSTimeInterval)transitionDuration:(nullable id <UIViewControllerContextTransitioning>)transitionContext;
// 轉(zhuǎn)場的動作
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext;
  • push和pop都在animateTransition:里面實現(xiàn),所以需要一個參數(shù)表示是push還是pop;還有一個參數(shù)表示轉(zhuǎn)場時間
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>


typedef enum : NSUInteger {
    animate_push = 0,
    animate_pop = 1,
    
} Animate_Type;

@interface SFTrainsitionAnimate : NSObject<UIViewControllerAnimatedTransitioning>

- (instancetype)initWithAnimateType:(Animate_Type)type andDuration:(CGFloat)dura;

@property (assign, nonatomic) CGFloat duration;
@property (assign, nonatomic) Animate_Type type;

@end

那么要如何使用新建的對象呢?可以通過UINavigationControllerDelegate中的

- (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC{
    if (operation == UINavigationControllerOperationPush) {
        self.animate = [[SFTrainsitionAnimate alloc] init];
        return self.animate;
    }else{
        return nil;
    }
    
}

當(dāng)然,要調(diào)用這個方法還得先把UINavigationControllerDelegate委托給視圖控制器

self.navigationController.delegate = self;

再來看看UIViewControllerAnimatedTransitioning這個協(xié)議,返回時間的方法就不介紹了。重點在

- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext{
    //起始視圖控制器
    UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    //目標(biāo)視圖控制器
    UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    //在這個視圖上實現(xiàn)跳轉(zhuǎn)動畫
    UIView *containView = [transitionContext containerView];
}

如上注釋,我們可以通過參數(shù)transitionContext獲取到起始和目標(biāo)控制。并在containView這個視圖實現(xiàn)我們想要實現(xiàn)的動畫。


接下來就是轉(zhuǎn)場動畫的實現(xiàn)了,push動畫的流程大概就是:點擊第一個頁面的一個控件,模擬出控件然后移動到第二個界面同一個樣式控件的位置,之后再把第二個界面以控件的位置中心擴(kuò)散顯示出來。

  • 那么現(xiàn)在我們就在協(xié)議方法中通過fromVC獲取到第一個視圖的控件,并制造一個鏡像視圖移動到toVC的目標(biāo)控件的位置。在這里,我調(diào)用了UIViewcontroller分類關(guān)聯(lián)一個對象,用來記錄控件(非擁有關(guān)系)。
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>

@interface UIViewController (SFTrainsitionExtension)

@property (assign, nonatomic) CGFloat sf_targetHeight;//灰白背景的分割線高度
@property (weak  , nonatomic) UIView *sf_targetView;

@end

產(chǎn)生targetView鏡像:

//產(chǎn)生targetView鏡像
- (UIView *)customSnapshoFromView:(UIView *)inputView {
    
    // Make an image from the input view.
    UIGraphicsBeginImageContextWithOptions(inputView.bounds.size, NO, 0);
    [inputView.layer renderInContext:UIGraphicsGetCurrentContext()];
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    
    // Create an image view.
    UIView *snapshot = [[UIImageView alloc] initWithImage:image];
    snapshot.layer.masksToBounds = NO;
    snapshot.layer.cornerRadius = 0.0;
    snapshot.layer.shadowOffset = CGSizeMake(0.0, 0.0);
    snapshot.layer.shadowRadius = 5.0;
    snapshot.layer.shadowOpacity = 0.4;
    
    return snapshot;
}
  • 現(xiàn)在就可以制作移動的動畫:
//起始位置
    CGRect originFrame = [fromVC.sf_targetView convertRect:fromVC.sf_targetView.bounds toView:fromVC.view];
    //動畫移動的視圖鏡像
    UIView *customView = [self customSnapshoFromView:fromVC.sf_targetView];
    customView.frame = originFrame;
    
    //移動的目標(biāo)位置
    CGRect finishFrame = [toVC.sf_targetView convertRect:toVC.sf_targetView.bounds toView:toVC.view];
    
    UIView *containView = [transitionContext containerView];
    
    //背景視圖 灰色高度
    CGFloat height = CGRectGetMidY(finishFrame);
    toVC.sf_targetHeight = height;
    
    //背景視圖 灰色
    UIView *backgray = [[UIView alloc] initWithFrame:CGRectMake(0, 0, k_SF_SCREEN_WIDTH, k_SF_SCREEN_HIGHT)];
    backgray.backgroundColor = [UIColor lightGrayColor];
    //背景視圖  白色
    UIView *backwhite = [[UIView alloc] initWithFrame:CGRectMake(0, height, k_SF_SCREEN_HIGHT, k_SF_SCREEN_HIGHT-height)];
    backwhite.backgroundColor = [UIColor whiteColor];
    
    toVC.view.frame = [transitionContext finalFrameForViewController:toVC];
    
    //注意添加順序
    [containView addSubview:toVC.view];
    [containView addSubview:backgray];
    [backgray addSubview:backwhite];
    [containView addSubview:customView];
 
    //動畫
    [UIView animateWithDuration:_duration/3 animations:^{
        customView.frame = finishFrame;
        customView.transform = CGAffineTransformMakeScale(1.1, 1.1);
    } completion:^(BOOL finished) {
        if (finished) {
            
            [UIView animateWithDuration:_duration/3 animations:^{
                
                customView.transform = CGAffineTransformIdentity;
                
            } completion:^(BOOL finished) {
                if (finished) {
                    [UIView animateWithDuration:_duration/3 animations:^{
                        customView.alpha = 0.0;
                    } completion:^(BOOL finished) {
                        if (finished) {
                            [backgray removeFromSuperview];
                            [customView removeFromSuperview];
                            [transitionContext completeTransition:YES];
                        }
                    }];
                    
                    [self addPathAnimateWithView:backgray fromPoint:customView.center];
                    
                }
            }];
  • 移動完之后,要以圓形擴(kuò)散顯示出push之后的界面。就可以通過UIBezierPath和CAShapeLayer來實現(xiàn)。UIBezierPath表示路徑,CAShapeLayer可以根據(jù)路徑來顯示區(qū)域,那么我們可以以第一個界面的視圖先看看效果。
    UIBezierPath *path = [UIBezierPath bezierPathWithRect:self.collectionView.bounds];

    [path appendPath:[UIBezierPath bezierPathWithArcCenter:self.collectionView.center radius:50 startAngle:0 endAngle:2*M_PI clockwise:NO]];
    
    CAShapeLayer *layer = [CAShapeLayer layer];
    layer.path = path.CGPath;
    self.collectionView.layer.mask = layer;

初始化path路徑以collectionView的四邊畫一個路徑,然后加入一個以collectionView的中心為圓點,半徑為50的路徑,顯示的區(qū)域就為兩個路徑之間的區(qū)域。

123.png

然后再把代碼中的radius大小設(shè)為200

456.png

現(xiàn)在,我們創(chuàng)建一個CABasicAnimation對象去完成擴(kuò)散的動畫,起始位置是半徑為10的圓,終點位置是半徑為200的圓.

    UIBezierPath *path = [UIBezierPath bezierPathWithRect:self.collectionView.bounds];
    
    [path appendPath:[UIBezierPath bezierPathWithArcCenter:self.collectionView.center radius:10 startAngle:0 endAngle:2*M_PI clockwise:NO]];
    
    UIBezierPath *path2 = [UIBezierPath bezierPathWithRect:self.collectionView.bounds];
    
    [path2 appendPath:[UIBezierPath bezierPathWithArcCenter:self.collectionView.center radius:200 startAngle:0 endAngle:2*M_PI clockwise:NO]];
    
    CAShapeLayer *layer = [CAShapeLayer layer];
    self.collectionView.layer.mask = layer;
    
    CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:@"path"];
    pathAnimation.fromValue =  (__bridge id)path.CGPath;
    pathAnimation.toValue   =  (__bridge id)path2.CGPath;
    pathAnimation.duration  = 1.0;
    pathAnimation.repeatCount = 1;
    pathAnimation.removedOnCompletion = NO;
    pathAnimation.fillMode = kCAFillModeForwards;
    pathAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
    [layer addAnimation:pathAnimation forKey:@"pathAnimate"];
789.gif

現(xiàn)在就可以完成push的 轉(zhuǎn)場效果了。注意圓圈白色部分顯示的是collectionView底部的self.view的視圖。

//加入收合動畫
- (void)addPathAnimateWithView:(UIView *)toView fromPoint:(CGPoint)point{
    //create path
    UIBezierPath *path = [UIBezierPath bezierPathWithRect:CGRectMake(0, 0, k_SF_SCREEN_WIDTH, k_SF_SCREEN_HIGHT)];
    //create path
    [path appendPath:[UIBezierPath bezierPathWithArcCenter:point radius:0.1 startAngle:0 endAngle:2*M_PI clockwise:NO]];
    
    CGFloat radius = point.y > 0?k_SF_SCREEN_HIGHT*3/4: k_SF_SCREEN_HIGHT*3/4-point.y;
    UIBezierPath *path2 = [UIBezierPath bezierPathWithRect:CGRectMake(0, 0, k_SF_SCREEN_WIDTH, k_SF_SCREEN_HIGHT)];
    [path2 appendPath:[UIBezierPath bezierPathWithArcCenter:point radius:radius startAngle:0 endAngle:2*M_PI clockwise:NO]];
    
    CAShapeLayer *shapeLayer = [CAShapeLayer layer];
    //shapeLayer.path = path.CGPath;
    toView.layer.mask = shapeLayer;
    
    CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:@"path"];
    pathAnimation.fromValue = _type == animate_push? (__bridge id)path.CGPath:(__bridge id)path2.CGPath;
    pathAnimation.toValue   = _type == animate_push? (__bridge id)path2.CGPath:(__bridge id)path.CGPath;
    pathAnimation.duration  = _duration/3;
    pathAnimation.repeatCount = 1;
    pathAnimation.removedOnCompletion = NO;
    pathAnimation.fillMode = kCAFillModeForwards;
    pathAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
    [shapeLayer addAnimation:pathAnimation forKey:@"pathAnimate"];
}

pop動畫其實和push差不多,這里就不說了。

github地址:點這里

參考博客:點這里

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

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

  • 前言的前言 唐巧前輩在微信公眾號「iOSDevTips」以及其博客上推送了我的文章后,我的 Github 各項指標(biāo)...
    VincentHK閱讀 5,566評論 3 44
  • WebSocket-Swift Starscream的使用 WebSocket 是 HTML5 一種新的協(xié)議。它實...
    香橙柚子閱讀 24,703評論 8 183
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 15,056評論 4 61
  • 七月的天氣很燥熱,落榜的我跟著爸媽的腳步走進(jìn)了再次奮斗的戰(zhàn)場。對于這次高考,我心有不甘。。。也因為此次失敗我才相...
    WAHZBB閱讀 328評論 0 0
  • 2007年的一天晚上,電視上一名叫做胡雪峰的控球后衛(wèi)用他的運球,三分,一人瀟灑砍下36分,10年過去了,20歲的我...
    顧米夕77閱讀 378評論 0 2

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