你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發基礎 >> 教你實現類似於格瓦拉啟動頁中的放大轉場動畫(OC&Swift)

教你實現類似於格瓦拉啟動頁中的放大轉場動畫(OC&Swift)

編輯:IOS開發基礎

本文是投稿文章,作者:HenryCheng 

一、前言

用過格瓦拉電影,或者其他app可能都知道,一種點擊按鈕用放大效果實現轉場的動畫現在很流行,效果大致如下

65.gif

在iOS中,在同一個導航控制器你可以自定義轉場動畫實現兩個viewController之間的過渡。實際上在iOS7之後,通過實現UIViewControllerAnimatedTransitioning或者UIViewControllerContextTransitioning協議,就可以簡單的自定義轉場動畫,比如一個NavigationController的push和pop。還有一點你需要知道的是,我如果有一個矩形,有一個圓,想要在這個矩形上剪出和圓大小相同的面積,那麼就要用到CALayer的mask屬性,下面用圖表達可能會直觀些:

QQ截圖20160317112653.png

laye.mask

現在可能你對mask屬性有一點了解了,下面代碼的實現中你將會看到具體的實現過程。先做這麼多了解,下面開始一步步實現效果。

二、開始實現簡單的push效果

新建工程,這裡用的是Swift,選中storyboard,然後加上一個導航,如下

QQ截圖20160317112628.png

添加導航控制器

然後效果如下

QQ截圖20160317112608.png

去掉導航欄

把右側的Shows Navigation Bar去掉,因為這個demo裡面並不需要導航欄,同時保證Is Instal View Controller是被勾上的(不知道的童鞋可以去掉看一下效果),這裡默認的都是勾選上的。然後在新建一個viewController,並設置其繼承於ViewController,如下

571495-e11e7c6ef3da2226.png

新建一個viewController

然後在兩個VC上分別在同樣的位置添加兩個完全相同的按鈕,位置約束在右上角距離右邊和上邊分別為20,20的距離,為了區分,將這兩個VC設置不同的背景色,如下

571495-1de74e63578102a7.png

按鈕的約束位置以及大小

QQ截圖20160317112426.png

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

然後右鍵一直按住第一個按鈕拖拽至第二個VC(也就是黃色背景的)點擊show

QQ截圖20160317112405.png

實現第一個 VC 按鈕點擊方法

這時候兩個VC之間就會出現一條線,然後點擊線中間,設置identifier為PushSegue,這裡設置一個標識符,為後面的跳轉做准備,效果如下:

QQ截圖20160317112315.png

設置identifier

將兩個按鈕連接成ViewController的同一個屬性,名為popBtn,然後將第二個VC的按鈕實現一個點擊方法(因為我們要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.
    }

}

最後,分別在兩個VC的中間添加一個imageView,最後的效果圖如下

QQ截圖20160317112200.png

最後效果圖

如果到這裡你還沒錯的話,那麼運行一下你的工程,運行的效果將會是這樣

362.gif

最後的運行效果圖

沒錯,也就是一個簡單的push效果,現在准備工作已經做好了,想要實現放大效果的動畫,還要繼續往下進行。

三、開始實現放大效果

通過上面的步驟,我們已經做好了准備工作,我們還要知道的一點是,要想自定義導航的push或pop效果,需要實現UINavigationControllerDelegate協議裡面的

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

這個協議方法,我們先新建一個繼承於NSObject的名為HWNavigationDelegate的一個類,然後引入UINavigationControllerDelegate,實現上面的協議方法,使返回值暫時為nil(從上面代碼中可以看出返回值是一個可選值,所以這裡可以先用nil,待會再具體實現)。然後你的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;
    }
}

現在繼續打開storyboard,然後在右下角搜索Object,並將其拖拽至左邊Navigation Controller Source裡,

QQ截圖20160317111833.png

添加Object

並在選中Object,在右邊將其類改成剛剛創建的HWNavigationDelegate

571495-0445b2fa9a72eb61.png

HWNavigationDelegate.png

最後在左側,點擊UINavigationController,並將其delegate設置為剛才的Object

QQ截圖20160317111645.png

設置導航的delegate

現在上面HWNavigationDelegate裡面導航的協議方法的返回值還是nil,我們需要創建一個實現動畫效果的類,並使其返回,這裡我們新建一個同樣繼承於NSObject的名為HWTransitionAnimator的類,並使其實現UIViewControllerAnimatedTransitioning協議,和其中的協議方法,為了便於閱讀,這裡貼出所有的代碼,

//
//  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
    }

}

關於上面的所有代碼,其中func animateTransition(transitionContext: UIViewControllerContextTransitioning),func animateTransition(transitionContext: UIViewControllerContextTransitioning)分別是設置時間和動畫過程的方法,都是UIViewControllerAnimatedTransitioning的協議方法,func animationDidStop是實現動畫結束後的操作,這裡動畫結束後需要做取消動畫和將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這個上面開始的時候就說過了,第一段代碼其實就是一個計算的過程,求出最後大圓效果的半徑,原理如圖(粗糙的畫了一下,畫得不好見諒^_^)

QQ截圖20160317111407.png

動畫效果關鍵的實現原理圖

最後將剛才HWNavigationDelegate裡的協議方法返回值修改成HWTransitionAnimator的對象就可以了

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

如果上面步驟,你操作沒錯的話,運行工程效果如下

23.gif

tap_swift

四、添加手勢引導動畫

添加手勢實現動畫效果,我們在剛才的HWNavigationDelegate類裡實現UINavigationControllerDelegate的另外一個斜一方法

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

這裡的self.interactionController就是我們的導航控制器,如下圖

571495-2df754c60b597012.png

設置導航屬性

然後重寫awakeFromNib()方法,關於整個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 {

    @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的幾個狀態

  • Begin :手勢被識別時時,初始化UIPercentDrivenInteractiveTransition實例對象和設置屬性,比如如果是第一個VC就實現push,反之則是pop

  • Changed:開始手勢到結束手勢的一個過程,上面代碼中是根據偏移量改變self.interactionController的位置

  • Ended:手勢結束以後的操作,設置動畫結束或者取消動畫,最後將self.interactionController置為nil

  • default:其他的狀態運行你的工程,拖拽屏幕時效果如下

571495-c33e2bc273dea3f6.gif

pan_swift.gif

五、最後

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

  • HWAnimationTransition_Swift(swift版本)

  • HWAnimationTransition_OC (OC版本)

  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved