ios动态效果实现翻页_iOS实现日历翻页动画
本文我主要描述两方面:
1.日历(简单描述原理)
2.翻页动画(重点)
最终的效果如下图:
图中沿四个对角的翻页动画,代表对应方向手势的滑动
1. 日历
要实现一个日历,其实原理很简单,我们只要知道三个数据:
1.今天是哪一天
2.这个月的第一天是星期几(哪天)
3.这个月总共有多少天
基于这三个数据就可以将获得的日期在日历中呈现出来。关于如何展示日历个人更倾向于使用UITableView来实现这一功能每个单元格代表一天当然还可以使用多个label和按钮来完成展示
1.获取今天是哪一天
这个应该是最简单的: NSDate(), 就可以获取当前的日期
2.获取这个月的第一天是星期几(哪天)
下面的方法都是作为NSDate的extension扩展的
//当前月第一天
func firstDateOfCurrentMonth() ->NSDate{
let calendar = NSCalendar(identifier:NSCalendarIdentifierGregorian )
let currentDateComponents = calendar!.component([.Year,.Month], fromSelf)
设置变量startOfMonth为当前日期组件对应的月份。
let date = startOfMonth?.dateByAddingTimeInterval(86060)
return date!
}
//当前月的第一天是星期几
func firstDayOfCurrentMonth() -> Int {
let calendar = NSCalendar.currentCalendar()
让 components 代表日历表中从第一个工作日开始的成员。
return components.weekday-1
}
3.获取这个月总共有多少天
基于这些数据, 从而能够得到每个日历单元格应显示的日期. 具体的显示方式以及与日期相关的三个主要类: NSDate、NSCalendar和NSDateComponents. 由于这些内容并非本文的重点讨论对象,这里不做详细阐述. 如果有不明白的地方, 可以参考官方文档, 或者等我以后撰写一篇更加详细的介绍这三个类的文章 (又一次挖坑待填).
2. 翻页动画
动画思路:
该动画归类于转场动画的一种类型。因此该动画类型可通过CATransition进行配置。配置过程相对简便,在此过程中仅需指定以下关键参数: animationDuration 表示动画时长; timeFunction 用于控制过渡节奏; fillMode 则影响填充效果设置。此外, CATransition 的 type 属性主要决定着过渡的整体效果, 而 subType 则通常用于指定具体过渡的方向性设置。值得注意的是, 官方文档仅提供了四种预设的过渡效果选项:
NSString * const kCATransitionFade;
NSString * const kCATransitionMoveIn;
NSString * const kCATransitionPush;
NSString * const kCATransitionReveal;
我们所需的翻页动画不在内部,在查阅相关资料后发现了较为理想的动画效果。在设置Transition类型时,默认使用的是不同的曲线,在设置Transition类型时,默认使用的是不同的曲线,在设置Transition类型时,默认使用的是不同的曲线
但是默认情况下翻页仅支持左上方动画切换和右下方动画切换
做出来的效果如下图:
无法达到四个对角都能进行翻页动画的效果。
为了实现沿四个对角方向翻页的效果, 随后, 我们可以在容器视图中嵌入日历视图. 这样一来, 在容器视图中嵌入日历视图就能实现沿四个对角方向翻页的效果
为了实现朝右上角翻页的目的,只需将containerView的layer绕y轴旋转M_π弧度.这样做的结果是使原本位于右下角的区域移动到左下角.这样一来,在执行翻页操作时,页面会朝着右上角的方向进行翻转.
然而为了确保日历能够正确显示,在dayView层中必须反转其层级结构;通过这种操作虽然在容器视图中呈现倒置状态(即容器视图反过来了),但实际展示出来的日期仍然是正确的
左下角翻页也是同样的道理。
具体代码如下:
//为dayView(代表日历的collectionview)添加一个滑动手势
func addPanGestureToDayView() {
let swipe: swipeType = UIPanGestureRecognizer(target of self, paneAction: paneSelector(#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)的右下角将移动至左下方
animationContainerView.layer.transform被CATransform3DMakeRotation(...)所赋值
由于 dayView 是叠加在 containerView 上面的。若未将 dayView 重新翻转回来,则会导致显示界面出现倒置。
dayView.layer.transform = CATransform3DMakeRotation(CGFloat(-M_PI), 0, 1, 0)
}
//转场动画的效果
transitioin.type = kPageCurlKey
//转场动画方向
transitioin.subtype = kCATransitionFromBottom
//设定一个月份键用于判断动画是否处于当前月份或下个月份之间。
transitioin.setValue("next", forKey: "month")
}else{//下
if translation.x < 0 {//手势朝左下角滑动,朝左下翻页
animation层组件视图的transform属性被赋值为一个CATransform3DMakeRotationFunction实例,并传递了π值常量、零参数、单位参数和零参数。
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
让组件为GregorianCalendar?.valueOf(.年, .月, .日),自定义日期。
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动画提供些许帮助。
