一步步教你實(shí)現(xiàn)類似于格瓦拉啟動(dòng)頁中的放大轉(zhuǎn)場動(dòng)畫(Objective-C & Swift)

一、前言


用過格瓦拉電影,或者其他app可能都知道,一種點(diǎn)擊按鈕用放大效果實(shí)現(xiàn)轉(zhuǎn)場的動(dòng)畫現(xiàn)在很流行,效果大致如下

tap_oc

iOS中,在同一個(gè)導(dǎo)航控制器你可以自定義轉(zhuǎn)場動(dòng)畫實(shí)現(xiàn)兩個(gè)viewController之間的過渡。實(shí)際上在iOS7之后,通過實(shí)現(xiàn)UIViewControllerAnimatedTransitioning或者UIViewControllerContextTransitioning協(xié)議,就可以簡單的自定義轉(zhuǎn)場動(dòng)畫,比如一個(gè)NavigationControllerpushpop
還有一點(diǎn)你需要知道的是,我如果有一個(gè)矩形,有一個(gè)圓,想要在這個(gè)矩形上剪出和圓大小相同的面積,那么就要用到CALayermask屬性,下面用圖表達(dá)可能會(huì)直觀些:

laye.mask

現(xiàn)在可能你對(duì)mask屬性有一點(diǎn)了解了,下面代碼的實(shí)現(xiàn)中你將會(huì)看到具體的實(shí)現(xiàn)過程。先做這么多了解,下面開始一步步實(shí)現(xiàn)效果。

二、開始實(shí)現(xiàn)簡單的push效果


新建工程,這里用的是Swift,選中storyboard,然后加上一個(gè)導(dǎo)航,如下

添加導(dǎo)航控制器

然后效果如下

去掉導(dǎo)航欄

把右側(cè)的Shows Navigation Bar去掉,因?yàn)檫@個(gè)demo里面并不需要導(dǎo)航欄,同時(shí)保證Is Initial View Controller是被勾上的(不知道的童鞋可以去掉看一下效果),這里默認(rèn)的都是勾選上的。
然后在新建一個(gè)viewController,并設(shè)置其繼承于ViewController,如下

新建一個(gè)viewController

然后在兩個(gè)VC上分別在同樣的位置添加兩個(gè)完全相同的按鈕,位置約束在右上角距離右邊和上邊分別為2020的距離,為了區(qū)分,將這兩個(gè)VC設(shè)置不同的背景色,如下

按鈕的約束位置以及大小

添加按鈕以及背景色以后效果

然后右鍵一直按住第一個(gè)按鈕拖拽至第二個(gè)VC(也就是黃色背景的)點(diǎn)擊show
實(shí)現(xiàn)第一個(gè) VC 按鈕點(diǎn)擊方法

這時(shí)候兩個(gè)VC之間就會(huì)出現(xiàn)一條線,然后點(diǎn)擊線中間,設(shè)置identifierPushSegue,這里設(shè)置一個(gè)標(biāo)識(shí)符,為后面的跳轉(zhuǎn)做準(zhǔn)備,效果如下:

設(shè)置`identifier`

將兩個(gè)按鈕連接成ViewController的同一個(gè)屬性,名為popBtn,然后將第二個(gè)VC的按鈕實(shí)現(xiàn)一個(gè)點(diǎn)擊方法(因?yàn)槲覀円?code>pop回來)名為popClick,如下

import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var popBtn: UIButton!
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
    }
  
    @IBAction func popClick(sender: AnyObject) {
        
        self.navigationController?.popViewControllerAnimated(true)
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

}

最后,分別在兩個(gè)VC的中間添加一個(gè)imageView,最后的效果圖如下

最后效果圖

如果到這里你還沒錯(cuò)的話,那么運(yùn)行一下你的工程,運(yùn)行的效果將會(huì)是這樣

最后的運(yùn)行效果圖

沒錯(cuò),也就是一個(gè)簡單的push效果,現(xiàn)在準(zhǔn)備工作已經(jīng)做好了,想要實(shí)現(xiàn)放大效果的動(dòng)畫,還要繼續(xù)往下進(jìn)行。

三、開始實(shí)現(xiàn)放大效果


通過上面的步驟,我們已經(jīng)做好了準(zhǔn)備工作,我們還要知道的一點(diǎn)是,要想自定義導(dǎo)航的pushpop效果,需要實(shí)現(xiàn)UINavigationControllerDelegate協(xié)議里面的


   func navigationController(navigationController: UINavigationController,
        interactionControllerForAnimationController animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
            return nil
    }

這個(gè)協(xié)議方法,我們先新建一個(gè)繼承于NSObject的名為HWNavigationDelegate的一個(gè)類,然后引入UINavigationControllerDelegate,實(shí)現(xiàn)上面的協(xié)議方法,使返回值暫時(shí)為nil(從上面代碼中可以看出返回值是一個(gè)可選值,所以這里可以先用nil,待會(huì)再具體實(shí)現(xiàn))。然后你的HWNavigationDelegate里面的代碼大致如下

//
//  HWNavigationDelegate.swift
//  HWAnimationTransition_Swift
//
//  Created by HenryCheng on 16/3/16.
//  Copyright ? 2016年 www.igancao.com. All rights reserved.
//

import UIKit

class HWNavigationDelegate: NSObject, UINavigationControllerDelegate {
    
    func navigationController(navigationController: UINavigationController, animationControllerForOperation operation: UINavigationControllerOperation, fromViewController fromVC: UIViewController, toViewController toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        
       return nil;
    }
}

現(xiàn)在繼續(xù)打開storyboard,然后在右下角搜索Object,并將其拖拽至左邊Navigation Controller Source里,

添加`Object`

并在選中Object,在右邊將其類改成剛剛創(chuàng)建的HWNavigationDelegate

HWNavigationDelegate.png

最后在左側(cè),點(diǎn)擊UINavigationController,并將其delegate設(shè)置為剛才的Object

設(shè)置導(dǎo)航的`delegate`

現(xiàn)在上面HWNavigationDelegate里面導(dǎo)航的協(xié)議方法的返回值還是nil,我們需要?jiǎng)?chuàng)建一個(gè)實(shí)現(xiàn)動(dòng)畫效果的類,并使其返回,這里我們新建一個(gè)同樣繼承于NSObject的名為HWTransitionAnimator的類,并使其實(shí)現(xiàn)UIViewControllerAnimatedTransitioning協(xié)議,和其中的協(xié)議方法,為了便于閱讀,這里貼出所有的代碼,

//
//  HWTransitionAnimator.swift
//  HWAnimationTransition_Swift
//
//  Created by HenryCheng on 16/3/16.
//  Copyright ? 2016年 www.igancao.com. All rights reserved.
//

import UIKit

class HWTransitionAnimator: NSObject, UIViewControllerAnimatedTransitioning {
    weak var transitionContext: UIViewControllerContextTransitioning?
    
    func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval {
        
        return 0.5
    }
    
    func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
        
        self.transitionContext = transitionContext
        
        let containerView = transitionContext.containerView()
        let fromVC = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey) as! ViewController
        let toVC = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey) as! ViewController
        let button = fromVC.popBtn
        
        containerView?.addSubview(toVC.view)
        
        let circleMaskPathInitial = UIBezierPath(ovalInRect: button.frame)
        let extremePoint = CGPoint(x: button.center.x - 0, y: button.center.y - CGRectGetHeight(toVC.view.bounds))
        let radius = sqrt((extremePoint.x * extremePoint.x) + (extremePoint.y * extremePoint.y))
        let circleMaskPathFinal = UIBezierPath(ovalInRect: CGRectInset(button.frame, -radius, -radius))
        
        let maskLayer = CAShapeLayer()
        maskLayer.path = circleMaskPathFinal.CGPath
        toVC.view.layer.mask = maskLayer
        
        let maskLayerAnimation = CABasicAnimation(keyPath: "path")
        maskLayerAnimation.fromValue = circleMaskPathInitial.CGPath
        maskLayerAnimation.toValue = circleMaskPathFinal.CGPath
        maskLayerAnimation.duration = self.transitionDuration(transitionContext)
        maskLayerAnimation.delegate = self
        maskLayer.addAnimation(maskLayerAnimation, forKey: "path")
        
    }
    
    override func animationDidStop(anim: CAAnimation, finished flag: Bool) {
        
        self.transitionContext?.completeTransition(!self.transitionContext!.transitionWasCancelled())
        self.transitionContext?.viewControllerForKey(UITransitionContextFromViewControllerKey)?.view.layer.mask = nil
    }

}

關(guān)于上面的所有代碼,其中func transitionDuration(transitionContext: UIViewControllerContextTransitioning?)func animateTransition(transitionContext: UIViewControllerContextTransitioning)分別是設(shè)置時(shí)間和動(dòng)畫的方法,都是UIViewControllerAnimatedTransitioning的協(xié)議方法,func animationDidStop是實(shí)現(xiàn)動(dòng)畫結(jié)束后的操作,這里動(dòng)畫結(jié)束后需要做取消動(dòng)畫和將fromViewController釋放掉的操作。
里面的

        let circleMaskPathInitial = UIBezierPath(ovalInRect: button.frame)
        let extremePoint = CGPoint(x: button.center.x - 0, y: button.center.y - CGRectGetHeight(toVC.view.bounds))
        let radius = sqrt((extremePoint.x * extremePoint.x) + (extremePoint.y * extremePoint.y))
        let circleMaskPathFinal = UIBezierPath(ovalInRect: CGRectInset(button.frame, -radius, -radius))
        
        let maskLayer = CAShapeLayer()
        maskLayer.path = circleMaskPathFinal.CGPath
        toVC.view.layer.mask = maskLayer

這段代碼,下面第二段代碼的maskLayer這個(gè)上面開始的時(shí)候就說過了,第一段代碼其實(shí)就是一個(gè)計(jì)算的過程,求出最后大圓效果的半徑,原理如圖(粗糙的畫了一下,畫得不好見諒*_*

動(dòng)畫效果關(guān)鍵的實(shí)現(xiàn)原理圖

最后將剛才HWNavigationDelegate里的協(xié)議方法返回值修改成HWTransitionAnimator的對(duì)象就可以了

    func navigationController(navigationController: UINavigationController, animationControllerForOperation operation: UINavigationControllerOperation, fromViewController fromVC: UIViewController, toViewController toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        
        return HWTransitionAnimator()
    }

如果上面步驟,你操作沒錯(cuò)的話,運(yùn)行工程效果如下

tap_swift

四、添加手勢引導(dǎo)動(dòng)畫


添加手勢實(shí)現(xiàn)動(dòng)畫效果,我們?cè)趧偛诺?code>HWNavigationDelegate類里實(shí)現(xiàn)UINavigationControllerDelegate的另外一個(gè)斜一方法

    func navigationController(navigationController: UINavigationController,
        interactionControllerForAnimationController animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
            return self.interactionController
    }

這里的self.interactionController就是我們的導(dǎo)航控制器,如下圖

設(shè)置導(dǎo)航屬性

然后重寫awakeFromNib()方法,關(guān)于整個(gè)HWNavigationDelegate最后的代碼實(shí)現(xiàn),如下

//
//  HWNavigationDelegate.swift
//  HWAnimationTransition_Swift
//
//  Created by HenryCheng on 16/3/16.
//  Copyright ? 2016年 www.igancao.com. All rights reserved.
//

import UIKit

class HWNavigationDelegate: NSObject, UINavigationControllerDelegate {
    
    @IBOutlet weak var navigationController: UINavigationController!
    var interactionController: UIPercentDrivenInteractiveTransition?
    
    func navigationController(navigationController: UINavigationController,
        interactionControllerForAnimationController animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
            return self.interactionController
    }
    
    func navigationController(navigationController: UINavigationController, animationControllerForOperation operation: UINavigationControllerOperation, fromViewController fromVC: UIViewController, toViewController toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        
        return HWTransitionAnimator()
//        return nil;
    }
    
    override func awakeFromNib() {
        super.awakeFromNib()
        let panGesture = UIPanGestureRecognizer(target: self, action: Selector("panned:"))
        self.navigationController.view.addGestureRecognizer(panGesture)
    }
    
    func panned(gestureRecognizer: UIPanGestureRecognizer) {
        switch gestureRecognizer.state {
        case .Began:
            
            self.interactionController = UIPercentDrivenInteractiveTransition()
            if self.navigationController?.viewControllers.count > 1 {
                self.navigationController?.popViewControllerAnimated(true)
            } else {
                self.navigationController?.topViewController!.performSegueWithIdentifier("PushSegue", sender: nil)
            }
        case .Changed:
            
            let translation = gestureRecognizer.translationInView(self.navigationController!.view)
            let completionProgress = translation.x / CGRectGetWidth(self.navigationController!.view.bounds)
            self.interactionController?.updateInteractiveTransition(completionProgress)
        case .Ended:
            
            if (gestureRecognizer.velocityInView(self.navigationController!.view).x > 0) {
                self.interactionController?.finishInteractiveTransition()
            } else {
                self.interactionController?.cancelInteractiveTransition()
            }
            self.interactionController = nil
            
        default:
            self.interactionController?.cancelInteractiveTransition()
            self.interactionController = nil
        }
    }
}

這里需要注意的是gestureRecognizer的幾個(gè)狀態(tài)

  • 1、Begin :手勢被識(shí)別時(shí)時(shí),初始化UIPercentDrivenInteractiveTransition實(shí)例對(duì)象和設(shè)置屬性,比如如果是第一個(gè)VC就實(shí)現(xiàn)push,反之則是pop
  • 2、Changed:開始手勢到結(jié)束手勢的一個(gè)過程,上面代碼中是根據(jù)偏移量改變self.interactionController的位置
  • 3、Ended:手勢結(jié)束以后的操作,設(shè)置動(dòng)畫結(jié)束或者取消動(dòng)畫,最后將self.interactionController置為nil
  • 4、default:其他的狀態(tài)
    運(yùn)行你的工程,拖拽屏幕時(shí)效果如下
pan_swift.gif

五、最后


由于最近工作比較忙,好久沒有寫博客了,趁著這回功夫?qū)⑦@個(gè)小動(dòng)畫分享一下,希望大家喜歡,時(shí)間不早了,該回去休息了(在公司加班完成的,喜歡的就star一下吧),最后,這里只是swift版本的代碼,同時(shí)如果你需要全部代碼的話,你可以在下面下載

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,245評(píng)論 4 61
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,381評(píng)論 25 708
  • 相信自己是一個(gè)奇跡,相信自己能創(chuàng)造無數(shù)的奇跡!努力,勤奮,勇敢!你一定能創(chuàng)造奇跡!
    張文雁閱讀 333評(píng)論 0 0
  • 記得大學(xué)時(shí)有個(gè)女同學(xué)小Z的人緣特別好,惹得好一些朋友羨慕,有一天幾個(gè)好朋友圍在一起聊天,從網(wǎng)上的各種八卦,再到討論...
    我就是貓姐閱讀 641評(píng)論 1 8
  • 李相赫
    李相赫00閱讀 497評(píng)論 0 0