iOS动画之日历翻页动画

前言

我对记事本之类的app非常感兴趣,AppStore上面的很多记事类的都下了用过,也自己尝试写过几个,只是由于我没有一点设计天赋,做出来的应用实在是太丑了。。。最近一直想做一款动画非常丰富的记事类应用,我自己觉得我理想中的记事类应用有两个方面我比较在意:简单,交互。
正好最近在看动画,就写一个翻页的日历,准备以后用到我的app中,哈哈。所以本文,我主要描述两方面:

//简书上markdown语法如何实现页面内的跳转我不太懂。。所以如果要看对应的内容,请自己收到跳转,当然从头看到尾也可以。。

  • 日历(简单描述原理)
  • 翻页动画(重点)

最终的效果如下图:(有没有人有好点的视频转gif的软件推荐 一下,我发现我用的两个做出来的gif都不是非常流畅)
图中沿四个对角的翻页动画,代表对应方向手势的滑动

calendar-1.gif

1. 日历

要实现一个日历,其实原理很简单,我们只要知道三个数据:

  • 今天是哪一天
  • 这个月的第一天是星期几(哪天)
  • 这个月总共有多少天

根据这个三个数据,就可以把得到的日期显示在日历上了,至于日历用什么来显示,我个人比较喜欢用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.获取这个月总共有多少天
//当前月总共有多少天
func daysCountOfCurrentMonth() -> Int {
    let calendar = NSCalendar(identifier:NSCalendarIdentifierGregorian )
    return (calendar?.rangeOfUnit(.Day, inUnit: .Month, forDate: self).length)!
}

根绝上面这些数据,就可以得到日历里面每个格子应该显示的日期,具体的显示和有关日期的三个主要的类: 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"进行动画,这两个值官方文档没有提供,我也不知道为啥这些大神能找到。。。貌似属于私有的api,原文地址

但是默认的朝上翻页只有左上角方向的动画,朝下翻页只有右下角方向的动画
做出来的效果如下图:


calendar-2.gif

无法达到四个对角都能进行翻页动画的效果。

为了得到可以沿着四个对角方向翻页的效果,我们可以先在最底部添加一个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")
    }
}

具体代码

git地址

总结:

这篇文章没有介绍太多详细的内容,其实翻页的动画实现还有别的方法,但是因为我想实现的是可以沿着四个对角进行动画的效果,所以最终选择了这个方法,上面说的好像不是很具体,如果不是很明白,可以先查看一下CATranstion的使用方法。

日历的阴历跟阳历上面没有单独列出来说明,具体的项目代码里面有,如果想要了解可以把代码下下来看看。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容

  • 在iOS中随处都可以看到绚丽的动画效果,实现这些动画的过程并不复杂,今天将带大家一窥ios动画全貌。在这里你可以看...
    每天刷两次牙阅读 8,471评论 6 30
  • 内容抽屉菜单ListViewWebViewSwitchButton按钮点赞按钮进度条TabLayout图标下拉刷新...
    皇小弟阅读 46,732评论 22 665
  • 显式动画 显式动画,它能够对一些属性做指定的自定义动画,或者创建非线性动画,比如沿着任意一条曲线移动。 属性动画 ...
    清风沐沐阅读 1,926评论 1 5
  • 在iOS实际开发中常用的动画无非是以下四种:UIView动画,核心动画,帧动画,自定义转场动画。 1.UIView...
    请叫我周小帅阅读 3,082评论 1 23
  • 今早看到一句话,现在的人啊,严于律人,宽于律己。许多人尤其喜欢拿着道德去压着别人。在他们眼中,一家企业第一要务是追...
    孤独的小人阅读 149评论 0 0