本文我主要描述兩方面:
1.日歷(簡單描述原理)
2.翻頁動畫(重點)
最終的效果如下圖:
圖中沿四個對角的翻頁動畫,代表對應方向手勢的滑動
1. 日歷
要實現一個日歷,其實原理很簡單,我們只要知道三個數據:
1.今天是哪一天
2.這個月的第一天是星期幾(哪天)
3.這個月總共有多少天
根據這個三個數據,就可以把得到的日期顯示在日歷上了,至於日歷用什麼來顯示,我個人比較喜歡用UICollectionView,一個cell代表一天,當然也可以用很多個label,button來顯示。
1.獲取今天是哪一天
這個應該是最簡單的: NSDate()
, 就可以獲取當前的日期
2.獲取這個月的第一天是星期幾(哪天)
下面的方法都是作為NSDate的extension擴展的
//當前月第一天 func firstDateOfCurrentMonth() ->NSDate{ let calendar = NSCalendar(identifier:NSCalendarIdentifierGregorian ) let currentDateComponents = calendar!.components([.Year,.Month], fromDate: self) let startOfMonth = calendar!.dateFromComponents(currentDateComponents) let date = startOfMonth?.dateByAddingTimeInterval(8*60*60) return date! } //當前月的第一天是星期幾 func firstDayOfCurrentMonth() -> Int { let calendar = NSCalendar.currentCalendar() let components = calendar.components(.Weekday, fromDate: firstDateOfCurrentMonth()) return components.weekday-1 }
3.獲取這個月總共有多少天
根絕上面這些數據,就可以得到日歷裡面每個格子應該顯示的日期,具體的顯示和有關日期的三個主要的類: NSDate
, NSCalendar
, NSDateComponents
由於不是本文的重點,我這裡就不詳細說了,如果有不明白的可以去看一下文檔,或者如果我下次寫一個詳細的關於這三個類的(又挖一個坑。。)。
2. 翻頁動畫
動畫思路:
上面的動畫屬於轉場動畫的一種,所以我們可以利用CATrasition
進行動畫,CATransition
的使用非常簡單,只要設置動畫時長,時間函數,fillMode
等,就可以得到想要的動畫,CATransition
的type
代表的是過渡時候的動畫效果,subType
一般代表動畫的方向,但是查看了一下CATransition
的type
屬性,官方文檔裡面只描述了下面四種預定義的轉場動畫效果:
NSString * const kCATransitionFade; NSString * const kCATransitionMoveIn; NSString * const kCATransitionPush; NSString * const kCATransitionReveal;
我們需要的翻頁動畫並不在裡面,在google了一下之後,找到了一個比較理想的效果,通過直接設置CATransition
的type
為"pageCurl
"或"pangeUnCurl
"進行動畫,這兩個值官方文檔沒有提供,我也不知道為啥這些大神能找到。。。
但是默認的朝上翻頁只有左上角方向的動畫,朝下翻頁只有右下角方向的動畫
做出來的效果如下圖:
無法達到四個對角都能進行翻頁動畫的效果。
為了得到可以沿著四個對角方向翻頁的效果,我們可以先在最底部添加一個containerView
,然後在containerView
中添加dayView
(下面提到的dayView
和代碼中的dayView
都代表的是作為日歷的collectionView
)。
如果要進行朝右上角翻頁,我們只要把containerView
的layer
先沿y軸
翻轉M_PI
,這樣,最開始的右下角就變成左下角了,翻頁時就會變成向右上角翻頁
但是為了日歷顯示正確,我們需要把dayView
的layer
重新翻轉過來,這樣,containerView
是反的,但是我們看到的日期顯示是正的
左下角翻頁也是同樣的道理。
具體代碼如下:
//為dayView(代表日歷的collectionview)添加一個滑動手勢 func addPanGestureToDayView() { let swipe = UIPanGestureRecognizer(target: self, action: #selector(self.panOnDayView(_:))) dayView.addGestureRecognizer(swipe) } //當在dayView上滑動時觸發 func panOnDayView(pan: UIPanGestureRecognizer) { //如果手勢的狀態是結束狀態 //或者當前動畫已經結束(防止上一個翻頁動畫還沒結束,就開始下一個) //添加翻頁的轉場動畫到dayView上 if pan.state == .Ended && !animatiing{ addAnimationToDayView(pan) } } let pageCurlDuration = 0.5 //動畫時間 let kPageCurlKey = "pageCurl" //往上翻頁的的type let kPageUnCurlKey = "pageUnCurl" //往下翻頁的type //添加動畫到日歷 func addAnimationToDayView(pan: UIPanGestureRecognizer) { let translation = pan.translationInView(dayView) //創建一個轉場動畫 let transitioin = CATransition() transitioin.duration = pageCurlDuration transitioin.timingFunction = CAMediaTimingFunction(name: "default") //在動畫結束之後保證狀態不被移除(這個兩個屬性得同時設置) transitioin.fillMode = kCAFillModeForwards transitioin.removedOnCompletion = false //設置代理,在動畫開始和結束的代理方法中可以處理一些事情 transitioin.delegate = self if translation.y < 0 {//手勢向上 * * if translation.x > 0 {//手勢朝右上角滑動,朝右上翻頁 //沿y軸對containerView進行M_PI角度翻轉,使containerView的右下角變為左下角 animationContainerView.layer.transform = CATransform3DMakeRotation(CGFloat(M_PI), 0, 1, 0) //因為dayView是加在containerView上面的,如果不把dayView重新翻轉回去,顯示出來的界面都是反的 dayView.layer.transform = CATransform3DMakeRotation(CGFloat(-M_PI), 0, 1, 0) } //轉場動畫的效果 transitioin.type = kPageCurlKey //轉場動畫方向 transitioin.subtype = kCATransitionFromBottom //設置一個month的key,為了在動畫結束時判斷動畫代表的是上一個月,還是下一個月 transitioin.setValue("next", forKey: "month") }else{//下 if translation.x < 0 {//手勢朝左下角滑動,朝左下翻頁 animationContainerView.layer.transform = CATransform3DMakeRotation(CGFloat(M_PI), 0, 1, 0) dayView.layer.transform = CATransform3DMakeRotation(CGFloat(-M_PI), 0, 1, 0) } transitioin.type = kPageUnCurlKey transitioin.subtype = kCATransitionFromTop transitioin.setValue("pre", forKey: "month") } dayView.layer.addAnimation(transitioin, forKey: "pageCurl") }
動畫開始和停止時,進行一些處理:
//因為上面設置了 transitioin.delegate = self,這兩個代理方法會自動調用 override func animationDidStart(anim: CAAnimation) { //動畫開始時,判斷當前動畫是代表往上翻頁,還是往下翻頁,來刷新日歷時間 animatiing = true let components = GregorianCalendar?.components([.Year,.Month,.Day], fromDate: date) if anim.valueForKey("month") as! String == "next" { components?.month += 1 }else if anim.valueForKey("month") as! String == "pre"{ components?.month -= 1 } date = (GregorianCalendar?.dateFromComponents(components!))! dateDidChaged!(date: date) } //動畫結束時,將layer的transform屬性設置為初始值,並移除dayView的layer上翻頁的動畫 override func animationDidStop(anim: CAAnimation, finished flag: Bool) { if flag { animatiing = false animationContainerView.layer.transform = CATransform3DIdentity dayView.layer.transform = CATransform3DIdentity dayView.layer.removeAnimationForKey("pageCurl") } }
總結:
這篇文章沒有介紹太多詳細的內容,其實翻頁的動畫實現還有別的方法,但是因為我想實現的是可以沿著四個對角進行動畫的效果,所以最終選擇了這個方法,上面說的好像不是很具體,如果不是很明白,可以先查看一下CATranstion的使用方法。以上就是本文的全部內容,希望對大家開發IOS動畫的時候能有所幫助。