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