Swift 表视图动画教程: Drop-In Cards

http://www.raywenderlich.com/76024/swift-table-view-animations-tutorial-drop-cards

标准 table view 是一个强大而又灵活的数据呈现方式。大部分情况下你的app 都使用了某种形式的 table view。可是。它有一个缺点就是。无法进行太多的定制,你的 app 会淹没在成千上万的相似是 app 中。

为了不使用千篇一律的 table view,我们能够利用某些动画以便使你的app 更加耀眼。看一下 Google+ app。卡片从边上滑过的动画。

假设你没看过,请在这里下载download it here (免费)! 你也能够看一下 Google 在 2014 I/O 大会上公布的 设计指南 。它包括了很多怎样使用动画的演示样例和技巧。

開始

下载開始项目 ,在 Xcode 6 中打开。在它的故事板中有一个UITableViewController 子类 MainViewController 以及 UITableViewCell 子类CardTableViewCell。模型对象是 Memeber 类。用于封装团队中每一个成员的信息。

数据来自本地资源束中的 JSON 文件。

执行程序,效果例如以下图所看到的:


最简单的动画

使用 File\New\File… 。先后旋转 iOS\Source\SwiftFile 。文件名称为TipInCellAnimator.swift ,然后点击Create.

编辑该文件内容为:

import UIKit  

class TipInCellAnimator {

   // placeholder for things to come -- only fades in for now

   class func animate(cell:UITableViewCell) {

     let view = cell.contentView

     view.layer.opacity = 0.1

     UIView.animateWithDuration(1.4) {

       view.layer.opacity = 1

     }

   }

}

该类仅仅有一个以单元格为參数方法。它将单元格的 contentView 透明度设置为 0.1,然后在 1.4 秒的时间内将透明度改为 1.0。

触发动画

打开 MainViewController.swift 增加下列方法:

override func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell,     forRowAtIndexPath indexPath: NSIndexPath) {

   TipInCellAnimator.animate(cell)

}

这种方法是 UITableViewDelegate 协议 中的方法之中的一个。

它在单元格被渲染之前调用。

在这种方法中我们调用了 TipInCellAnimator 的 animate 方法。

执行程序。滚动表格。单元格将呈现一个渐入的动画效果:


旋转

打开 TipInCellAnimator.swift, 将内容改动为:

import UIKit

import QuartzCore // 1  

class TipInCellAnimator {

   class func animate(cell:UITableViewCell) {

     let view = cell.contentView

     let rotationDegrees: CGFloat = -15.0

     let rotationRadians: CGFloat = rotationDegrees * (CGFloat(M_PI)/180.0)

     let offset = CGPointMake(-20, -20)

     var startTransform = CATransform3DIdentity // 2

     startTransform = CATransform3DRotate(CATransform3DIdentity,

         rotationRadians, 0.0, 0.0, 1.0) // 3

     startTransform = CATransform3DTranslate(startTransform, offset.x, offset.y, 0.0) // 4

       // 5

     view.layer.transform = startTransform

     view.layer.opacity = 0.8

       // 6

     UIView.animateWithDuration(0.4) {

       view.layer.transform = CATransform3DIdentity

       view.layer.opacity = 1

     }

   }

}

这次的动画时间为0.4 秒,进场的效果更加复杂,将呈现一个旋转特效。上述代码的核心是 startTransform 变量。并在动画结束时回到原状态。

1.     首先须要导入 QuartzCore,这样我们才可能使用 core animation 动画。

2.     定义一个 identity transform。它的意思是什么都不做。这是这个 view 的默认 transform。

3.     调用 CATransform3DRotate 实现一个 -15 度(反时针)旋转。旋转轴为 0.0, 0.0, 1.0(分别代表x,y,z 轴)。即在 z-轴上做旋转。

4.     除了简单的旋转。我们还做了一个平移。

5.     将旋转并平移后的 transform 设置为 view 的 transform。

6.      让 view 的状态由startTransform 变为默认的 transform。

上面执行的效果例如以下图所看到的:


注意。你是在单元格的子视图上进行动画。而不是对单元格进行动画。

直接旋转单元格会导致一些意外的效果,比如抖动或切掉单元格的一部分。使用单元格的contentView 来代表单元格是全部子视图。

并不是全部的属性都支持动画, Core AnimationProgramming Guide 中的 list of animatableproperties 列出了支持动画的属性。

执行程序。查看表格渲染时的效果。


Swift 重构

这个教程最初的 O-C 版本号有一个地方值得一提。它会确保仅仅会对startTransform 进行一次计算。而在上面的代码中,每次调用 animate() 方法都会导致 startTransform 又一次被赋值。那么。在Swift 中怎样实现这一点?

一种办法是声明一个存储属性。该属性通过调用一个闭包计算获得。

One wayis to use an immutable stored property that is computed by calling a closure.

改动 TipInCellAnimator.swift 内容例如以下:

import UIKit

import QuartzCore  

let TipInCellAnimatorStartTransform:CATransform3D = {

   let rotationDegrees: CGFloat = -15.0

   let rotationRadians: CGFloat = rotationDegrees * (CGFloat(M_PI)/180.0)

   let offset = CGPointMake(-20, -20)

   var startTransform = CATransform3DIdentity

   startTransform = CATransform3DRotate(CATransform3DIdentity,

     rotationRadians, 0.0, 0.0, 1.0)

   startTransform = CATransform3DTranslate(startTransform, offset.x, offset.y, 0.0)

     return startTransform

}()  

class TipInCellAnimator {

   class func animate(cell:UITableViewCell) {

     let view = cell.contentView

       view.layer.transform = TipInCellAnimatorStartTransform

     view.layer.opacity = 0.8

       UIView.animateWithDuration(0.4) {

       view.layer.transform = CATransform3DIdentity

       view.layer.opacity = 1

     }

   }

}

注意,创建 startTransform 的代码如今放到了存储属性TipInCellAnimatorStartTransform 中。并且我们没实用定义 getter 方法的方式定义这个属性(这样做会导致每次调用getter 方法都创建一次 transform)。我们是通过一个闭包+()的方式给这个属性赋默认值的。一对空的圆括号表示强制调用这个闭包。闭包的返回值将传递给这个属性。这个细节在苹果的Swift 官方教程中“初始化”一章中讨论。

请參考“用闭包或函数设置属性的默认值”。

有克制地使用动画

尽管动画的效果非常好看,但却不能无克制地使用它。假设音效和动画的滥用以前让你深受其害,那么你应该知道过度依赖特效所带来的恶果!

在这个项目中。仅仅需在单元格第一次渲染的时候使用特效即可了——当表格从上到下滚动时。

当表格从下往上滚时,单元格不须要使用特效。

我们须要存储哪些单元格已经显示过的。以便第二次显示时不使用特效。这里。我们使用了一个 Swift 字典对象。

打开MainViewController.swift 增加一个属性:

var didAnimateCell:[NSIndexPath: Bool] = [:]

定义了一个字典对象, key 为 NSIndexPaths 类型,值为 Bools 类型。然后改动 tableView(tableView:,willDisplayCell:, forRowAtIndexPath:) 方法为:

override func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell,                          forRowAtIndexPath indexPath: NSIndexPath) {

     if didAnimateCell[indexPath] == nil || didAnimateCell[indexPath]! == false {

         didAnimateCell[indexPath] = true

         TipInCellAnimator.animate(cell)

     }

}

我们推断单元格的 index path 是否已经在didAnimateCell 字典中存在。假设不存在,说明是第一次显示这个单元格。那么我们就执行单元格动画并将 indexPath 设置到字典中。假设存在,什么都不做。

执行程序。上拉滚动表格,你将看到单元格动画:


posted @ 2016-02-23 17:51  blfshiye  阅读(306)  评论(0编辑  收藏  举报