多多色-多人伦交性欧美在线观看-多人伦精品一区二区三区视频-多色视频-免费黄色视屏网站-免费黄色在线

國內最全IT社區平臺 聯系我們 | 收藏本站
阿里云優惠2
您當前位置:首頁 > php開源 > 綜合技術 > iOS 動畫教程-自定義 View Controller 呈現轉換

iOS 動畫教程-自定義 View Controller 呈現轉換

來源:程序員人生   發布時間:2017-04-14 16:48:49 閱讀次數:7208次

原文:ios-animation-tutorial-custom-view-controller-presentation-transitions
作者:Marin Todorov
譯者:kmyhy

當你顯現相機、通訊錄、或某種自定義模式窗口時,你每次都會調用同1個 UIKit 方法 present(_:animated:completion:)。這個方法將當前屏幕“讓給”另外一個 view controller。

默許的顯現動畫簡單地用新視圖推開當前視圖。下圖演示了“新建聯系人” view controller 在聯系人列表視圖上層向上滑出:

在本教程中,你將用自己的自定義顯現動畫替換默許動畫,并完本錢教程中的項目。

開始

下載本文的開始項目 Beginner Cook。打開 Main.storyboard :

第1個 view controller(即 ViewController)包括了 app 的標題和主要介紹和底部的1個 scroll view,用于顯示1個有用的香草列表。

當用戶點擊了列表中的圖片,main view controller 會顯現1個 HerbDetailsViewController;這個 view controller 有1個背景、1個標題、1個描寫和幾個按鈕用于注明圖片的所有者。

在 ViewController.swift 和 HerbDetailsViewController.swift 已有部份代碼了,足以保持 app 運行。運行程序,app 是這個模樣:

點擊某個香草圖片,細節頁面以標準的彈出動畫方式顯現。對1般的 app 來講這也足夠了,但對你的 app 則需要做得更好!

你的任務是創建自定義顯現動畫讓你的 app 更加殘暴奪目!你需要將目前內置的動畫替換成:用所點擊的香草的圖片展開至全屏!

擼起手袖,系緊圍裙,準備動手開始定制顯現控制器!

自定義動畫的幕后工作

UIKit 允許你通過拜托模型定制化 view controller 的顯現進程;你可讓 main view controller(或可以用另外一個類專門來干這個)采取 UIViewControllerTransitioningDelegate 協議。

每當你顯現1個新的 view controller 時,UIKit 會詢問它的拜托是不是需要使用自定義動畫。自定義動畫的第1步是這樣的:

UIKit 會調用 animationController(forPresented:presenting:source:) 方法,看是不是有1個 UIViewControllerAnimatedTransitioning 對象返回。如果這個方法返回空,UIKit 使用默許動畫,否則,UIKit 使用返回的對象作為這次轉換的動畫控制器。

UIKit 首先詢問動畫控制器(簡稱為 animator),動畫需要幾秒鐘?然后調用它的animateTransition(using:) 方法。這時候你的自定義動畫開始生效了。

在 animateTransition(using:) 方法中,你可以同時訪問到正在顯示的 view controller 和行將顯現的新 view controller。你可以淡入、縮放、旋轉并為所欲為地操作已有的視圖和新的視圖。

你已大致了解了之定義顯現控制器是如何工作的了,現在,開始來創建我們自己的吧!

實現 Transition 拜托

由于拜托的責任是管理動畫控制器 animator 對象,而由 animator 來履行真實的動畫,因此在編寫拜托代碼之前的第1件事情就是創建1個 animator 類。

打開 Xcode 菜單 File\New\File… 選擇模板: iOS\Source\Cocoa Touch Class。
類名設置為 PopAnimator,語言選擇 Swift,繼承于 NSObject。
打開 PopAnimator.swift 修改類定義,實現 UIViewControllerAnimatedTransitioning 協議:

class PopAnimator: NSObject, UIViewControllerAnimatedTransitioning {

}

Xcode 會抱怨沒有實現必須的拜托方法,等下我們會解決這個。
在類中添加以下方法:

func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
  return 0
}

動畫時長返回 0 只是臨時的;后面會修改這個為真實的時長。
繼續新增以下方法:

func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {

}

這個方法將放入動畫代碼,暫時是空實現,以消除 Xcode 的報錯。

有了基本的 animator 類以后,你可以在 view controller 中實現拜托方法了。
打開 ViewController.swift 新增以下擴大:

extension ViewController: UIViewControllerTransitioningDelegate {

}

這聲明對 transitioning delegate 協議的實現。等會我們再來添加這些方法。

找到 didTapImageView(_:) 方法。在方法底部,你看到了顯現詳情 view controller 的代碼。herbDetails 是新 view controller 的實例;你需要將它的 transitioning 拜托設置為 main controller。

在這個方法最后1行,即調用 present(…) 方法以后加入以下代碼:

// ...
present(herbDetails, animated: true, completion: nil)
herbDetails.transitioningDelegate = self // 加入這行

現在 UIKit 會在每次顯現 details view controller 的時候都索要1個 animator 對象。但你還沒有實現任何 UIViewControllerTransitioningDelegate 方法,所以 UIKit 還是會使用默許的動畫。

接下來應當實例化1個 animator 對象并在 UIKit 詢問的時候返給它。
在 ViewController 中添加1個屬性:

let transition = PopAnimator()

這個是1個 PopAnimator 對象,用于驅動你的 view controller 動畫。你只需要1個 PopAnimator 對象,由于你可以在每次顯現 view controller 時都使用同1個 animator 對象,由于每次的動畫都是同1個。

在 ViewController 的擴大中加入第1個拜托方法:

func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
  return transition
}

這個方法提供幾個參數,你可以根據它們來決定是返回1個自定義的動畫還是不。在本文中,你總是返回同1個 PopAnimator 實例,由于你只有1個顯現動畫。

你已添加了1個用于顯現 view controller 的拜托方法,那末用于解散的呢?

這是另外一個拜托方法:

func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
  return nil
}

這個方法和前1個方法基本是干同1件事情:判斷要解散的是哪一個 view controller,覺此來決定是不是返回 nil,返回 nil 表示采取默許的解散動畫,或返回1個自定義的 animator。這里你返回的是 nil,由于你還沒有實現解散動畫。

你已具有了1個自定義的 animator 來負責自定義動畫,但它是如何工作的呢?

運行程序,點擊任何1張香草圖片:

甚么也沒產生。為啥?你有1個用于驅動動畫的自定義 animator,但是……等等,在 animator 類中還沒有編寫代碼!你會在下1節完成這個任務。

創建 Transition Animator

打開 PopAnimator.swift; 這里我們將加入兩個 view controller 之間進行轉換的代碼。
首先,加入幾個屬性:

let duration = 1.0
var presenting = true
var originFrame = CGRect.zero

duration 變量會用到幾個地方,比如告知 UIKit 動畫時長,和創建動畫時。
我們還定義了1個 presenting 變量,用于告知 animator 類,當前是在顯現還是解散進程。我們需要記住這個變量,由于我們將以正面順序履行顯現,而以相反順序履行解散。

最后,我們用 originFrame 變量保存原來用戶所點到的圖片的 frame 形狀——我們會將圖片由這個 frame 以動畫方式放大到全屏,反過來則履行相反動作。當你獲得當前所選圖片并將它的 frame 傳遞給 animator 實例時,需要注意這個 originFrame。

現在,你可以回到 UIViewControllerAnimatedTransitioning 方法來了。

在 transitionDuration() 方法中,用下句替換:

return duration

重用 duration 屬性,能讓你很容易可以調試 transition 動畫。你可以簡單修改這個值,使動畫變快變慢。

設置轉換上下文

現在為 animateTransition 注入魔力。這個方法有1個 UIViewControllerContextTransitioning 參數,通過它你能訪問和轉換相干的 view controller 和參數。

在開始編寫代碼之前,1個重要的問題就是理解 animation context 的實際上是甚么。

當兩個 view controller 之間開始轉換時,原來的 view 被添加到 transition container 轉換容器,新的 view controller 的 view 被創建出來,但依然不可見,以下圖所示:

因此你的任務是在 animateTransition() 方法中將新的 view 添加到轉換容器,“以動畫方式”顯示它,如果有必要的話,將原本的 view 以動畫方式移除。

默許,當轉換動畫完成時,原有 view 從轉換容器中移除。

在你能夠“烹制”出更多的食品之前,你需要創建1個簡單的動畫,看看它是如何實現的,然后再實現更酷的、同時也是更復雜的轉換。

添加淡入動畫

開始,我們用1個簡單的淡出動畫來實現自定義動畫。在 animateTransition 方法中加入:

let containerView = transitionContext.containerView

let toView = transitionContext.view(forKey: .to)!

首先,獲得容器 view,你的動畫將在這個 view 中產生。然后獲得新的 view 并賦給 toView 變量。
轉換上下文有兩個非常方便的方法,允許你訪問動畫的參與者

  • view(forKey:): 你可以訪問到 “原本的” and “新的” view,通過指定 key 參數為 UITransitionContextViewKey.from 或 UITransitionContextViewKey.to。
  • viewController(forKey:): 你可以通過指定 key 參數為 UITransitionContextViewControllerKey.from 或 UITransitionContextViewControllerKey.to 來訪問 “原本的” 和 “新的” view controller。

這里,你要同時用到 container view 和要顯現的 view 。然后你將要顯現的 view 添加為 container view 的 subview 并以某種方式動畫。
在 animateTransition() 中添加:

containerView.addSubview(toView)
toView.alpha = 0.0
UIView.animate(withDuration: duration, 
  animations: {
    toView.alpha = 1.0
  }, 
  completion: { _ in
    transitionContext.completeTransition(true)
  }
)

注意,你需要在動畫完成塊中調用轉換上下文的 completeTransition() 方法,這是為了通知 UIKit 你的轉換動畫已完成,UIKit 可以完成這次 view controller 轉換了。

運行程序,點擊某張香草圖片,你會看到香草的介紹以淡入的方式出現在主 view controller 中:

這個轉換委曲過得去,你已大概弄清了在 animateTransition 方法中應當干些甚么——你將在里面加入1些更好的東西!

加入1個 Pop 動畫

新的動畫需要重新調劑1下代碼結構,因此將 animateTransition() 中的代碼替換為:

let containerView = transitionContext.containerView
let toView = transitionContext.view(forKey: .to)!
let herbView = presenting ? toView : 
  transitionContext.view(forKey: .from)!

containerView 是你的動畫行將產生的地方,toView 是需要顯現的新視圖。如果你正在顯現,herbView 就是 toView,否則它應當從上下文中獲得。對解散和顯現,herbView 都會是你將履行動畫的 view。

當你顯現細節頁面時,它會拉伸到全部屏幕大小。解散時,它又會縮小到原始 frame 大小。

在 animateTransition() 添加:

let initialFrame = presenting ? originFrame : herbView.frame
let finalFrame = presenting ? herbView.frame : originFrame

let xScaleFactor = presenting ?

  initialFrame.width / finalFrame.width :
  finalFrame.width / initialFrame.width

let yScaleFactor = presenting ?

  initialFrame.height / finalFrame.height :
  finalFrame.height / initialFrame.height

在上述代碼中,我們需要根據條件取得本來的 frame 和終究動畫結束時的 frame,然后計算兩個 view 之間的橫縱比例。

現在我們需要關注新 view 的位置,由于它需要顯示在所點擊的 image 上方,看起來就像是被點的圖象拉伸到了全屏大小。
在 animateTransition() 中添加:

let scaleTransform = CGAffineTransform(scaleX: xScaleFactor, 
                                            y: yScaleFactor)

if presenting {
  herbView.transform = scaleTransform
  herbView.center = CGPoint(
    x: initialFrame.midX,
    y: initialFrame.midY)
  herbView.clipsToBounds = true
}

當顯現新 view 時,我們設置了它的 scale 和位置以便和原圖 frame 的位置大小匹配。
現在在 animateTransition() 中加入最后的代碼:

containerView.addSubview(toView)
containerView.bringSubview(toFront: herbView)

UIView.animate(withDuration: duration, delay:0.0, 
  usingSpringWithDamping: 0.4, initialSpringVelocity: 0.0,
  animations: {
    herbView.transform = self.presenting ?
      CGAffineTransform.identity : scaleTransform
    herbView.center = CGPoint(x: finalFrame.midX, y: finalFrame.midY)
  }, 
  completion:{_ in
    transitionContext.completeTransition(true)
  }
)

首先將 toView 添加到 container。然后,讓 herbView 放在 subview 的最上層,由于你只會對這個 view 進行動畫。記住,在解散時,toView 是原始 view,因此在第1個行代碼,你會將 toView 加在最上層,這樣你的動畫會被隱藏在下層看不見,所以你需要將 herbView 放到上層。

然后,開始動畫。這里使用了1個 spring 動畫,這會帶來1種彈簧效果。

在 animations 塊中,我們修改 herbView 的 transform 屬性和位置。在顯現時,你將底部的小尺寸動畫到全屏,因此目標 transform 就是 identity transform。在解散時,你將它的大小縮小到原始圖片大小。

這里,我們已準備好了將新 view 的位置對齊被點到的圖片,在原來的 frame 和終究的 frame 之間進行動畫,最后調用 completeTransition() 方法將控制轉給 UIKit。讓我們來看看代碼的實際效果!

運行程序,點擊第1個香草圖片,看看你的動畫效果:

是的,它還不是10分完善。但當你修改了這些瑕疵,你的動畫就會同你想象的1模1樣!

當前你的動畫是從左上角開始;由于 originFrame 默許的 origin 是(0,0)——你并沒有修改過這個值。

打開 ViewController.swift 在 animationController(forPresented:) 頭部加入:

transition.originFrame = 
selectedImage!.superview!.convert(selectedImage!.frame, to: nil) 
transition.presenting = true
selectedImage!.isHidden = true

將 transition 的 originFrame 設置為 selectedImage 即你剛剛點擊的圖片的 frame。然后將 presenting 設置為 true,在動畫期間隱藏所選圖片。

運行程序,點擊列表中的不同香草,你會看到:

添加解散動畫

剩下來的事情就是解散詳情頁面。實際上大部份工作都已在 animator 中做完了——轉換動畫中的代碼中開始、結束 frame 都已設置正確,你最后的工作就是在顯現和解散時適時地播放動畫。開心吧?

打開 ViewController.swift 修改 animationController(forDismissed:) 方法為:

transition.presenting = false
return transition

這將告知 animator 對象,你將解散1個 view controller,這樣動畫代碼會以正確的方式進行。
運行程序,點擊1張香草圖片然后點擊屏幕任何地方解散它:

轉換動畫看起來沒甚么問題,但請注意,你選擇的香草從 scroll view 中消失了!當你解散細節頁面時,你需要讓所點擊的圖片重新顯示。
打開 PopAnimator.swift 添加1個新的閉包屬性:

var dismissCompletion: (()->Void)?

這將允許解散動畫完成時履行你傳入的代碼。
然后,找到 animateTransition() 方法在完成塊中,在調用 completeTransition() 之前加入:

if !self.presenting {
  self.dismissCompletion?()
}

當解散完成,調用 dismissCompletion —— 這里恰好可以顯示原來的圖片。
打開 ViewController.swift 在 viewDidLoad() 中加入:

transition.dismissCompletion = {
  self.selectedImage!.isHidden = false
}

這里當轉換動畫完成重新顯示了原來的圖片,以替換詳情頁面。

運行程序,體驗轉換動畫,包括顯現和解散。現在,香草不會在平白無故消失了!

裝備方向的轉換

注意: 這部份內容是可選的。如果你對裝備方向改變不感興趣的話,請跳到挑戰部份。

你可以將裝備方向改變看成是1種顯現,從1個 view controller 轉換到它自己,僅僅是 size 不同。

iOS 8 中出現的 viewWillTransition(to size:coordinator:)方法,允許你以1種簡單直白的方式處理裝備方向的變化。你不再需要為橫屏豎屏分別設計不同的布局,相反,你只需改變 view controller 的視圖 size。
打開 ViewController.swift ,實現 viewWillTransition(to:with:) 方法:

override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
  super.viewWillTransition(to: size, with: coordinator)

}

第1個參數 size 通知你 view controller 當前正在轉換到哪一個 size。第2個參數 coordinator 是1個 transition coordinator 對象,通過它可以訪問該轉換的許多屬性。

當橫屏的時候,你需要做的僅僅是下降 app 背景圖片的 alpha 值,提高文字的可讀性。

在 viewWillTransitionToSize 加入:

coordinator.animate(
  alongsideTransition: {context in
    self.bgImage.alpha = (size.width>size.height) ? 0.25 : 0.55
  }, 
  completion: nil
)

animate(alongsideTransition:) 允許你在旋屏進程中同時履行你指定的動畫,也就是在 UIKit 履行默許旋屏動畫的同時。
你的動畫塊會收到1個 transitionging 上下文,這和你在顯現 view controller 時使用的上下文是1樣的。這里,你沒有 from 和 to 視圖控制器了,由于它們是同1個,但你可以取得比如動畫時長等屬性。

在動畫塊中,我們判斷目標 size 的寬度是不是大于高度,如果是,下降背景圖的 alpha 值為 0.25。這將使橫屏下的背景變淡。如果是豎屏模式,alpha 值設為 0.55。

運行程序,旋轉裝備(如果是摹擬器,按 command+左箭頭),查看實際效果。

你將看到當旋轉到橫屏時背景變暗。這使得長文本更容易瀏覽。

如果你點擊圖片,你會注意到動畫有點亂。由于屏幕旋轉為橫屏后,圖片依然是豎向的大小。在原始圖片和拉伸至全屏的圖象之間的轉換其實不流暢。

不要擔心——你有1個新方法 viewWillTransition(to:with:) 能夠解決這個問題。
ViewController 有1個成員方法叫 positionListItems(),它負責香草圖片的大小和位置。這個方法在 app 1啟動時,被 viewDidLoad()方法所調用。

在 animate(alongsideTransition:) 方法的動畫塊中,在設置 alpha 值以后加入以下代碼:

self.positionListItems()

這將在裝備旋轉后改變香草圖片的 size 和位置。當屏幕完成旋轉后,香草圖片也會被重新改變大小:

由于這些圖片都已有了1個橫屏布局,因此你的轉換動畫就可以正常運行了。試試看!

結束語

從這里下載終究完成的項目。

這里,你可以對這個轉換進行大量的改進。例如,這些點子:

  • 在轉換期間隱藏被點擊的圖片,以便它們看起來真的想“長大”到全部屏幕。
  • 讓每一個香草的描寫文本以淡入淡出的方式動畫,這樣轉換動畫會更加平滑。
  • 針對橫屏對轉換進行測試和調劑。
    如果你想學習更多內容,請參考我們的iOS Animations by Tutorials。這本書已完全遲滯 Swift 3 和 iOS10 。你會學習如何使用 spring 動畫、轉換、關鍵幀動畫、CALayer 動畫、自動布局束縛動畫、view controller 轉換動畫等等!

希望你喜歡本教程,如果有任何問題和建議,請在下面留言!

生活不易,碼農辛苦
如果您覺得本網站對您的學習有所幫助,可以手機掃描二維碼進行捐贈
程序員人生
------分隔線----------------------------
分享到:
------分隔線----------------------------
關閉
程序員人生
主站蜘蛛池模板: 久草在线观看首页 | 国产精品欧美一区二区在线看 | 久久综合九九亚洲一区 | 亚洲手机看片 | 全网毛片 | www一区 | 午夜dj高清免费观看视频www | julia一区福利视频在线观看 | 91精品国产美女福到在线不卡 | 国产精品久久久久久免费播放 | 美女一级黄色片 | 亚洲国产精品欧美日韩一区二区 | 国产成人精品一区二区不卡 | 欧美一区二区三区在线视频 | 亚洲春色图片 | 亚洲色欲色欲综合网站 | 黑人一区二区三区中文字幕 | 欧美激情一区二区三区在线播放 | 国产亚洲一区二区在线观看 | 免费一区二区三区 | 国产精品福利视频一区二区三区 | 成人午夜免费在线观看 | 正在播放国产露脸做 | jizz黄色| 亚洲精品久久一区影院 | www.久久久| 亚洲嫩草影院久久精品 | 成人久久久久久 | h视频无遮挡免费网站 | 香蕉成人啪国产精品视频综合网 | 日韩综合第一页 | 精品成人久久 | 一二三四在线播放免费视频中国 | 国产日韩欧美自拍 | 日本japan色系videos护士 日本jizz在线播放 | 福利区在线观看 | 日本欧美精品 | 国产精品1区2区3区 国产精品1页 | 激情视频在线观看免费 | 校园春色欧美色图 | 欧美精品一区二区在线观看 |