App Store中Today轉場動畫實現

先看動畫,這是仿後的效果bash

Untitled.gif

這轉場動畫,乍看一下很複雜,實際上是拆化細分以後是很簡單的。大部分動畫都只是位移,旋轉,顏色變化這三分部疊加組合起來而已。工具

原理

所謂轉場動畫無非就是從A跳轉到B的過程當中,經過攔截重寫佈局

- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
複製代碼

在其中咱們能夠得到三個視圖:動畫

  • fromView: 轉場開始視圖 (即A視圖)
  • toView:轉場結束視圖 (即B視圖)
  • containerView:轉場視圖容器,全部轉場動畫在裏面完成

這樣一來,咱們就能拿到AB視圖裏的全部元素和位置,把它放進containerView裏,讓A視圖裏的元素位移到B視圖裏對應的位置,完成轉場,這樣在視覺上就無縫銜接了ui

思路

有了理論基礎咱們再來談思路,咱們能夠把這個動畫先拆成兩部分,即A到B,B到A兩部分,而後A到B能夠能夠有 點擊Cell變小 --> 鬆手Cell回覆正常 --> 轉場,A的元素位移到B裏對應的位置 --> A到B轉場完成。 B到A差點多也是 模糊背景 --> 縮小A視圖 --> 轉場,B的元素位移到A李對應的位置 --> B到A轉場完成,其實都大同小異。spa

######讓cell變小再正常一是爲了給用戶增長反饋,二是這樣才符合運動規律,具體能夠百度運動規律,要作好動畫對物體的運動規律仍是要有所瞭解,否則動畫會十分生硬代理

實現

  • 頁面

先搭建出想要跳轉的兩個頁面,這個比較簡單,因此這裏就不寫了,重點仍是放在動畫上,不過要注意,作動畫推薦用frame佈局。code

20190726113746.jpg

  • 動畫拆解 根據上面拆解的動畫步驟,一個個來實現
點擊Cell變小
- (BOOL)tableView:(UITableView *)tableView shouldHighlightRowAtIndexPath:(NSIndexPath *)indexPath  {
	 _selectIndexPath = indexPath;
    UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
    [UIView animateWithDuration:0.3 animations:^{
        cell.transform = CGAffineTransformMakeScale(0.8, 0.8);
    }];
    return YES;
}
複製代碼
鬆開回覆正常
- (void)tableView:(UITableView *)tableView didUnhighlightRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
    if ([_selectIndexPath isEqual:indexPath]) {
        [UIView animateWithDuration:0.3 animations:^{
            cell.transform = CGAffineTransformMakeScale(1, 1);
            return;
        }];
    }
}
複製代碼

轉場,A的元素位移到B裏對應的位置(重點)

咱們先設置代理 self.navigationController.delegate = self; 實現代理orm

-(id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC {
    return self;
}
- (NSTimeInterval)transitionDuration:(nullable id <UIViewControllerContextTransitioning>)transitionContext {
    return 1.0f;
}

複製代碼

當點擊跳轉時,會進入animateTransition:方法cdn

// 修改轉場動畫
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext {
    //獲取的轉場視圖容器
    UIView *containerView = [transitionContext containerView];
    //獲取的B控制器
    GSInfoViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    //獲取b視圖的背景圖 這裏須要注意,我是將b視圖bg上lb的標籤所有添加在bgImageView上,因此作動畫時只拿到bgImageView就好了;
    UIView *toBgImageView = toVC.bgImageView;
    toBgImageView.hidden = YES;
    toVC.view.alpha = 0;
    //將B添加到轉場視圖容器裏
    [containerView addSubview:toVC.view];
     
    //獲取對應的A頁面的cell
    GSTodayTableViewCell *cell = (GSTodayTableViewCell *)[self.tableView cellForRowAtIndexPath:[self.tableView indexPathForSelectedRow]];
    //獲取A頁面的各個元素
    //建立A頁面的cell的背景,設置frame
    UIView *fromBgImageView = [[UIImageView alloc]initWithImage:cell.bgImageView.image];
    fromBgImageView.frame = [containerView convertRect:cell.bgImageView.frame fromView:cell.bgImageView.superview];
    //建立A頁面的cell的標籤,設置frame
    UILabel *typeL = [self copyLabel:cell.typeL];
    [fromBgImageView addSubview:typeL];
    //建立A頁面的cell的標題,設置frame
    UILabel *contentLabel =[self copyLabel:cell.contentL];
    [fromBgImageView addSubview:contentLabel];
    //建立關閉按鈕,雖然A頁面沒有該元素,但動畫時會出現,這裏的frame能夠根據本身感受調整
    UIButton *closeBtn = [UIButton buttonWithType:0];
    closeBtn.x = cell.width - 2*kMargin - 60;
    closeBtn.centerY = kHeight(20)-10;
    closeBtn.size = CGSizeMake(40, 40);
    closeBtn.alpha = 0.6;
    [closeBtn setBackgroundImage:[UIImage imageNamed:@"close"] forState:UIControlStateNormal];
    [fromBgImageView addSubview:closeBtn];
    //將A添加到轉場視圖容器裏,注意,須要先添加B在添加A,應該A是在前面作動畫的
    [containerView addSubview:fromBgImageView];
    
    //這裏能夠設置動畫時間,彈性等
    [UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0.0f usingSpringWithDamping:0.6f initialSpringVelocity:1.0f options:UIViewAnimationOptionCurveLinear animations:^{
        [containerView layoutIfNeeded];
        toVC.view.alpha = 1.0f;
        //這裏將B頁面各個元素的frame給A頁面對應的
        typeL.frame = toVC.typeL.frame ;
        closeBtn.frame = toVC.closeBtn.frame;
        contentLabel.frame = toVC.titleL.frame;
        fromBgImageView.frame = [containerView convertRect:toBgImageView.frame fromView:toBgImageView.superview];
    } completion:^(BOOL finished) {
        //都是爲了動畫弄出來的工具對象,所有刪了隱藏了
        toBgImageView.hidden = NO;
        closeBtn.hidden = YES;
        [fromBgImageView removeFromSuperview];
        [transitionContext completeTransition:!transitionContext.transitionWasCancelled];
    }];
}
複製代碼

上面用到的複製一個完整的label用方法

- (UILabel *)copyLabel:(UILabel *)label {
    UILabel *newLabel = [[UILabel alloc] init];
    newLabel.text = label.text;
    newLabel.font = label.font;
    newLabel.alpha = label.alpha;
    newLabel.frame =  label.frame;
    newLabel.textColor = label.textColor;
    newLabel.textAlignment = label.textAlignment;
    return newLabel;
}
複製代碼

這樣從A跳轉到B的過程場動畫就完成了


接下作從B到A跳轉的過程,其實差異並不大,同樣的設置代理self.navigationController.delegate = self;,再加個背景毛玻璃

// 背景毛玻璃
UIBlurEffect *effect = [UIBlurEffect effectWithStyle:UIBlurEffectStyleLight];
UIVisualEffectView *effectView = [[UIVisualEffectView alloc] initWithEffect:effect];
effectView.frame = CGRectMake(0, 0, kScreenW, kScreenH);
[self.view addSubview:effectView];
複製代碼

實現代理

-(id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC {
    return self;
}
- (NSTimeInterval)transitionDuration:(nullable id <UIViewControllerContextTransitioning>)transitionContext {
    return 1.0f;
}
複製代碼

點擊跳轉進入animateTransition:

- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext {
    UIView *containerView = [transitionContext containerView];
    //獲取跳轉後的頁面
    GSTodayViewController *toVC = (GSTodayViewController *)[transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    toVC.view.frame = [transitionContext finalFrameForViewController:toVC];
    //根據selectIndexPath獲取對應的cell, selectIndexPath能夠在跳轉的時候傳進來
    GSTodayTableViewCell *cell = (GSTodayTableViewCell *)[toVC.tableView cellForRowAtIndexPath:self.selectIndexPath];
    //獲取cell的背景
    UIView *cellBgImageView = cell.bgImageView;
    cellBgImageView.hidden = YES;
    
    //獲取當前頁面
    GSInfoViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    //獲取當前的背景圖
    UIView *bgImageView = fromVC.bgImageView;
    bgImageView.hidden = YES;
    //建立背景圖作動畫用
    UIView *screenshotView = [bgImageView snapshotViewAfterScreenUpdates:NO];
    //圓角與cell保持一致
    screenshotView.layer.masksToBounds = YES;
    screenshotView.layer.cornerRadius = 15;
    screenshotView.frame = [containerView convertRect:bgImageView.frame fromView:bgImageView.superview];
    //建立標籤
    UILabel *typeL = [self copyLabel:self.typeL];
    [screenshotView addSubview:typeL];
    //建立標題 到這裏能夠看出這個剛剛的如出一轍
    UILabel *titleL =  [self copyLabel:self.titleL];
    [screenshotView addSubview:titleL];
    [containerView addSubview:screenshotView];
    [containerView insertSubview:toVC.view belowSubview:fromVC.view];
    //動畫
    [UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0 usingSpringWithDamping:0.5f initialSpringVelocity:1.0 options:UIViewAnimationOptionCurveEaseIn animations:^{
        [containerView layoutIfNeeded];
        //隱藏當前視圖
        fromVC.view.alpha = 0.0f;
        //轉場過程當中保持視圖圓角
        screenshotView.layer.cornerRadius = 15;
        self.tableView.frame = CGRectMake(self.tableView.frame.origin.x, self.tableView.frame.origin.y, self.tableView.frame.size.width,kScreenW*1.3*0.8);
        self.tableView.layer.cornerRadius = 15;
        screenshotView.frame = [containerView convertRect:cellBgImageView.frame fromView:cellBgImageView.superview];
        //這裏須要設置label的frame,與外層cell的位置保持一致就行,外層cell是裏的元素frame是寫死的,我這裏也就同樣寫死
        typeL.x = kWidth(16);
        typeL.y = kHeight(20);
        titleL.x = kWidth(16);
        titleL.y = kHeight(20)+ typeL.height + 8;
        _closeBtn.alpha = 0;
    } completion:^(BOOL finished) {
        //同樣,動畫結束後該隱藏的隱藏,該刪除的刪除
        bgImageView.hidden = YES;
        [screenshotView removeFromSuperview];
        cellBgImageView.hidden = NO;
        [transitionContext completeTransition:![transitionContext transitionWasCancelled]];
    }];
}
複製代碼

這樣一個完整的A跳B,B返A的過場動畫就搞定了。

相關文章
相關標籤/搜索