如何实现iOS图书动画-第2部分(上)

来源:转载

 

欢迎回到iOS图书动画系列教程!在第一部分,我们学习了如何创建两个自定义的collection view layout并在图书书页中使用了阴影图层以使我们的App显得更加立体和真实。在这一部分,我们将学习如何创建自定义的转场动画并通过捏放手势来打开一本书。

注意:感谢Attila Hegedüs创建了本教程的示例程序。

开始

本教程以前一部分的内容为基础。如果你还没有看过第一部分,或者想从一个新项目开始,你可以在这里下载上一部分教程中的完整示例程序。

在Xcode中打开项目。现在,你可以选择一本书进行阅读,并从右边滑动进行翻页。这时的转场动画使用的是UINavigationController自带的动画效果。通过本教程的学习,我们将自定义这个动画效果,如下图所示:

这个动画会在“打开书”和“合起书”两个状态之间一一种更加自然的方式平滑过渡,这将更能获得用户的欢心。让我们马上开始吧!

创建自定义的导航控制器

要创建自定义的push动画和pop动画,我们必须创建自定义导航控制器并实现UINavigationControllerDelegate协议。
在App文件夹上右击(或ctrl+左键)并点击New File。选择iOSSourceCocoa Touch Class模板并将类名设置为CustomNavigationController。让它继承自UINavigationController并将语言设置为Swift。点击Next,Create。
打开CustomNavigationController.swift,编辑其内容为:

import UIKitclass CustomNavigationController: UINavigationController, UINavigationControllerDelegate { override func viewDidLoad() { super.viewDidLoad() //1 delegate = self } //2 func navigationController(navigationController: UINavigationController, animationControllerForOperation operation: UINavigationControllerOperation, fromViewController fromVC: UIViewController, toViewController toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? { if operation == .Push { return nil } if operation == .Pop { return nil } return nil }}

上述代码分别解释如下:

在 viewDidLoad 方法中,设置CustomNavigationController的delegate属性为它自己。 navigationController(_:animationControllerForOperation:fromViewController:toViewController:) 方法属于UINavigationControllerDelegate协议。这个方法在两个View Controller之间发生push或pop导航时调用。你可以在这个方法中分别针对push导航和pop导航返回各自的Transition对象。目前我们都返回了nil,这表明我们将使用UINavigationController内置的标准Transition。稍后我们将替换为自己的Transition对象。

现在我们拥有了自己的Navigation Controller类,接下来在故事板中将默认的UINavigationController替换为我们的CustomNavigationController。
打开Main.storyboard,在故事板编辑器左侧的对象窗口中选择Navigation Controller对象,打开Identity窗口,在Custom Class下,将Class由UINavigationController修改为CustomNavigationController,如下图所示:

编译运行,什么变化都没有发生。这是因为在委托方法中我们仍然返回了nil,因此使用的仍然是UINavigationController内置的标准Transition。

创建自定义导航动画

最有趣的部分来了——创建我们的自定义Transition对象!:]
要自定义Transition类,我们必须实现UIViewControllerAnimatedTransitioning协议,最主要的是这几个方法:

transitionDuration: 必须实现。这个方法返回一个动画时长,使两个动画的播放时间相同(或不同)。 animateTransition: 必须实现。这个方法负责提供参与动画的两个控制器:一个to控制器,一个from控制器。Transiton的大部分工作在这个方法中完成。 animationEnded: 可选的方法。这个方法主要是用来通知你什么时候动画完成了。我们可以在这个方法中进行必要的清理动作。

创建自定义Transition类

在App文件夹上右击(或ctrl+左键),然后点击New File。选择iOSSourceCoca Touch Class 模板,将类名设置为BookOpeningTransition,继承NSObject,语言Swift。然后点击Next,Create。
打开BookOpeningTransition.swift,编辑代码如下:

import UIKit//1class BookOpeningTransition: NSObject, UIViewControllerAnimatedTransitioning { // MARK: Stored properties var transforms = [UICollectionViewCell: CATransform3D]() //2 var toViewBackgroundColor: UIColor? //3 var isPush = true //4 //5 // MARK: UIViewControllerAnimatedTransitioning func transitionDuration(transitionContext: UIViewControllerContextTransitioning) -> NSTimeInterval { return 1 } func animateTransition(transitionContext: UIViewControllerContextTransitioning) { }}

以上代码对应注释中的编号,分别解释如下:

声明 BookOpeningTransition 类实现UIViewControllerAnimatedTransitioning 协议。 声明一个 transforms 字典,键存储UICollectionViewCell,值则存储对应的CATransiform3D。这个字典保存在书打开后所有cell的翻页动画。 指定to视图控制器的背景色,它将让淡出淡入动画看起来更加清楚。 布尔值isPush 用于标识当前动画是Push动画还是Pop动画。 增加必须实现的 UIViewControllerAnimatedTransitioning 协议方法,以使编译错误不再出现,稍后我们会实现这些方法。

定义好需要的变量,接下来就是实现协议方法。
首先是transitionDuration(_:)方法:

if isPush { return 1} else { return 1}

transitionDuration(_:)方法返回了动画播放时长。这里,无论是Push动画还是Pop动画我们都设置为1秒。通过这个方法我们可以很方便地改变Push动画或Pop动画的时长。

然后,是第二个协议方法——animateTransition——这是最核心的部分!:]我们将这个方法分成两部分来介绍:

实现一个助手方法,用于创建Push动画所需的Transition对象。 实现一个助手方法,用于创建Pop动画所需的Transition对象。

创建Push动画

回想你在生活中打开一本书的样子:

虽然看起来复杂,但我们只需要考虑两个状态,同时让UIView的animateWithDuration方法根据这两个状态进行不同的处理:

状态1,书处于合起状态。 状态2,书处于打开状态;这就是我们在第一部分教程中实现的部分。

首先,在实现animateTransition(:_)协议方法之前,我们来实现几个助手方法。
仍然在BookOpeningTransition.swift中,编写如下方法:

// MARK: Helper Methodsfunc makePerspectiveTransform() -> CATransform3D { var transform = CATransform3DIdentity transform.m34 = 1.0 / -2000 return transform}

这个方法返回了一个Transform对象,并在z轴上增加了一点立体感。在播放动画时,我们将用到这个方法。

状态 1 - 合起书

在makePerspectiveTransform方法后实现如下方法:

func closePageCell(cell : BookPageCell) {
// 1
var transform = self.makePerspectiveTransform()
// 2
if cell.layer.anchorPoint.x == 0 {
// 3
transform = CATransform3DRotate(transform, CGFloat(0), 0, 1, 0)
// 4
transform = CATransform3DTranslate(transform, -0.7 * cell.layer.bounds.width / 2, 0, 0)
// 5
transform = CATransform3DScale(transform, 0.7, 0.7, 1)
}
// 6
else {
// 7
transform = CATransform3DRotate(transform, CGFloat(-M_PI), 0, 1, 0)
// 8
transform = CATransform3DTranslate(transform, 0.7 * cell.layer.bounds.width / 2, 0, 0)
// 9
transform = CATransform3DScale(transform, 0.7, 0.7, 1)
}

//10cell.layer.transform = transform

}
回想一下BookViewController,它是一个CollectionView,代表了书中的一页。我们将每一页和书脊对齐,以y轴为心进行旋转实现翻页效果。首先,书是合起(关闭)的。这个方法将每个cell(即书页)放平并置于封面的下面。
这是动画运行效果:

以上代码解释如下:

用前面创建的助手方法,生成一个Transform对象。 判断cell是否是右侧页。 如果是,设置其角度为0,即放置为水平。 将它移动到封面下方并居中对齐。 将它缩放为70%。还记得我们前面将封面也缩放为70%吗?这里是同样的意思。 如果cell不是右侧页,则就是左侧页。 设置左侧页的角度为180度。要将它水平放置,我们需要将它翻到书脊的右边。 将它放到封面下方并居中对齐。 缩放70%。 最后,赋给cell的transform属性。

在上面的方法后添加如下方法:

func setStartPositionForPush(fromVC: BooksViewController, toVC: BookViewController) { // 1 toViewBackgroundColor = fromVC.collectionView?.backgroundColor toVC.collectionView?.backgroundColor = nil //2 fromVC.selectedCell()?.alpha = 0 //3 for cell in toVC.collectionView!.visibleCells() as! [BookPageCell] { //4 transforms[cell] = cell.layer.transform //5 closePageCell(cell) cell.updateShadowLayer() //6 if let indexPath = toVC.collectionView?.indexPathForCell(cell) { if indexPath.row == 0 { cell.shadowLayer.opacity = 0 } } }}

setStartPositionForPush(_:toVC:)方法创建状态1的Transition。这个动画涉及到两个ViewController:

fromVC,类型为BooksViewController,用于滚动浏览图书列表。

toVC,BookViewController类型,让你可以翻阅选定的书。

以上代码解释如下:

保存BooksViewController的Cellection View的背景色,然后设置BookViewController的Collection View的背景色为nil。 隐藏封面。现在toVC将负责处理封面图片的显示。 遍历书中所有书页。 保存每一页的当前Transform到transforms字典。 由于书一开始是合起的,我们将该页转换为合起状态,然后更新阴影图层。 最后,忽略封面图片的阴影。



分享给朋友:
您可能感兴趣的文章:
随机阅读: