watchOS 4 教程(3):动画

原文:watchOS 4 Tutorial Part 3: Animation
作者:Audrey Tam
译者:kmyhy

更新说明: 本教程由 Audrey Tam 更新至 Swift 4/watchOS 4。原文作者是 Mic Pringle。

欢迎回到 watchOS 4 系列教程!

在教程(1)中,你学习了基本的 watchOS 4 开发,创建了你的第一个 interface controller。

在教程(2)中,你学习了如何在 app 中添加表格。

在这部教程(3)中,你将学习如何使用 watchOS 4 动画,在 app 中添加一个新的登机界面。

在这个过程中,你将学到:

  • 如何创建基于 image 的动画
  • 如何使用 watchOS 4 的动画 API

让我们立即开始吧!

注意:这部分教程基于教程(2)的工作基础之上。你可以用之前的项目开始,如果你想重新开始,也可以从这里下载代码。

开始

打开 Watch\Interface.storyboard,从 Object Library 中拖一个 interface controller 到故事板中。选中这个 interface controller,打开属性面板,将 Identifier 设置为 CheckIn。这样才能从 ScheduleInterfaceController 中呈现这个 interface controller。

然后,拖一个 group 到新的 interface controller 上。在属性面板中,修改属性:

  • 设置 Layout 为 Vertical。
  • 设置 Mode 为 Center。
  • 设置 Horizontal Alignment 为 Center。
  • 设置 Height 为 Relative to Container。

你的 interface controller 看起来应该像这样:

接下来,我们会构建一个 label-image-label 样的 group,和你创建表格行时差不多。

拖一个 group 到刚才的 group 中,在属性面板修改下列属性:

  • 设置 Spacing 为 4。
  • 设置 Horizontal Alignment 为 Center。
  • 设置 Width 为 Size To Fit Content。
  • 设置 Height 为 Fixed,值为 30 (比表格行略矮)。

在新 group 中添加一个 label 和一个 image。我们会设置 label,然后复制修改它,用于显示航班起点和终点。

选中 image,无论从故事板中还剩 document outline 中都可以。在属性面板中,修改其属性:

  • 设置 Image 为 Plane。
  • 设置 Tint 为 #FA114F (又是我们的 Air Aber 粉!)。
  • 设置 Vertical Alignment 为 Center。
  • 设置 Width 为 Fixed,值为 24。
  • 设置 Height 为 Fixed,值为 20。

和之前一样,这个图片不会被上色,你无法将它从 interface controller 的黑色背景中看见它。但你应该知道它是存在的。

选中 label,将 Text 设置为 MEL。将 Font 设置为 System,字体样式为 Semibold、大小为 20。最后,设置它的 Vertical alignment 为 Center,将它的宽、高都修改成 Size To Fit Content。

复制这个 label,然后将它粘贴到 image 旁边。将 text 修改为 SFO,Horizontal Alignment 设置为 Right。你的 interface controller 现在应该看起来是这个样子:

现在来添加一个大大的登机按钮。

添加登机按钮

从 Object Library 中拖一个按钮到 interface controller,将位置放到和起点、终点 label 同一个 group 中:

WatchKit 中的按钮非常灵活;你可以只使用它们的内置样式——这样你只能添加一种样式——你还可以将它们转换成布局 group,将其它界面元素添加进去以定制其外观。这就是我们接下来要做的。

选中按钮,在属性面板中修改下列属性:

  • 设置 Content 为 Group。
  • 设置 Horizontal Alignment 为 Center。
  • 设置 Vertical Alignment 为 Center。

现在的 interface controller 应该是这个样子:

你可能注意到了,当你修改按钮的 Content 属性时,在 document outline 中出现了一个新的 group:

你将用它作为登机按钮的背景。选中这个 group,在属性面板中修改属性:

  • 设置 Color 为 #FA114F。
  • 设置 Radius 为 39。
  • 设置 Width 为 Fixed,值为 78。
  • 设置 Height 为 Fixed,值为 78。

interface controller 现在是这个样子:

登机按钮开始有点样子了。唯一缺少的仅仅是一个标签,接下来我们就添加它。

拖一个 label 到按钮下面的这个 group 中,然后选中 label。用属性面板修改其属性:

  • 设置 Text 为 Check In。
  • 设置 Font 为 System,字体样式为 Semibold 字体大小为 16。
  • 设置 Horizontal Alignment 为 Center。
  • 设置 Vertical Alignment 为 Center。

完成后的这个登机按钮长这个样子:

接下来应该专门为这个 controller 创建一个 WKInterfaceController 子类并修改 ScheduleInterfaceController 以便显示它。

创建 Controller

在项目导航器中,右键点击 Watch Extension 文件夹,选择 New File…。在对话框中选择 watchOS\Source\WatchKit Class 然后点 Next。文件名命名为 CheckInInterfaceController,继承自 WKInterfaceController,将 Language 设置为 Swift。

点击 Next、Create。

新文件打开后,删除 3 个空方法,只留下 import 语句和类定义。

然后,在类头部添加:

@IBOutlet var backgroundGroup: WKInterfaceGroup!
@IBOutlet var originLabel: WKInterfaceLabel!
@IBOutlet var destinationLabel: WKInterfaceLabel!

这里为最外层的 group 和两个 label 都创建了 outlet。后面我们会连接它们。

然后,在 outlet 下面添加:

var flight: Flight? {
  didSet {
    guard let flight = flight else { return }

    originLabel.setText(flight.origin)
    destinationLabel.setText(flight.destination)
  }
}

你应该知道了吧!这里,我们添加了一个可空的 Flight 对象,同时定义了一个属性观察器。当观察器触发时,我们队 flight 解包,如果解包成功,用这个 flight 来设置两个 label。这个套路你已经相当熟悉了,不是吗?

当 controller 被呈现时,我们必须对 flight 属性赋值。在 CheckInInterfaceController 中添加方法:

override func awake(withContext context: Any?) {
  super.awake(withContext: context)

  if let flight = context as? Flight {
    self.flight = flight
  }
}

这个你也应该很熟悉。我们首先解包 context 对象成一个 Flight 对象。如果解包成功,将它赋给 self.flight,这会触发属性观察器并更新 UI。

最后,在 awake(withContext:) 下面添加:

@IBAction func checkInButtonTapped() {
  // 1
  let duration = 0.35
  let delay = DispatchTime.now() + (duration + 0.15)
  // 2
  backgroundGroup.setBackgroundImageNamed("Progress")
  // 3
  backgroundGroup.startAnimatingWithImages(in: NSRange(location: 0, length: 10), 
    duration: duration,
    repeatCount: 1)
  // 4
  DispatchQueue.main.asyncAfter(deadline: delay) { [weak self] in
    // 5
    self?.flight?.checkedIn = true
    self?.dismiss()
  }
}

分成几个步骤:

  1. 这里创建了两个常量:一个是动画的时长,一个是当控制器解散时的延时。这 delay 没有使用 Double 类型,而是使用了 DispatchTime 类型,因为我们将在 GCD 中使用它。
  2. 加载一个名叫 Progress 的图片序列,并用作 backgroundGroup 的背景图片。布局 group 实现了 WKImageAnimatable 协议,这个协议允许我们播放图片序列。
  3. 播放动画序列。播放的范围指定为整个序列,repeatCount:1 表示动画只播放一次。
  4. WatchKit 没有完成处理块,因此只能用 GCD 延时回调来代替回调闭包。
  5. 在闭包中,你可以将航班标记为已登机,然后解散控制器。

现在需要在项目中添加用到的图片,以及连接 outlet 和一个 IBAction。

下载这个 zip 文件,解压缩,将文件夹整个拖到 Watch\Assets.xcassets。

确认你拖的是文件夹,而不是它的内容。这会在 asset catalog 中创建一个新的 group 叫做 Progress,其中包含以下几张图片:

添加完图片,就要连接 outlet 和按钮的 action 了。

打开 Watch\Interface.storyboard,选中我们的新 interface controller。在 Identity 面板中,修改 Custom Class\Class 为 CheckInInterfaceController:

在 document outline 中,右键点击 CheckIn 调出 outlets and actions 菜单。将 backgroundGroup 连接到 interface controller 最外层的 group:

在故事板中,连接 destinationLabel 到 SFO 标签,连接 originLabel 到 MEL 标签。

然后,连接 checkInButtonTapped 到圆圆的大按钮上:

在 Build & run 之前,还有最后一件事情,就是呈现这个 interface controller。

呈现控制器

打开 ScheduleInterfaceController.swift,找到 table(_:didSelectRowAt:) 方法,将它的内容修改为:

let flight = flights[rowIndex]
let controllers = ["Flight", "CheckIn"]
presentController(withNames: controllers, contexts: [flight, flight])

这里,我们从 flights 数组检索出 rowIndex 对应的 flight 对象,创建一个包含了要呈现的 2 个 interface 控制器的 identifier 的数组,并分别向它们传入两个 flight 作为 context。

Build & run。点击一个航班,你会两个 interface controller 显示出来。向左扫显示登机 controller,点击按钮播放动画并登机:

这看起来很好,但如果在 schedule interface controller 中能对已登机的航班加亮显示就更好了。我们将在下一节也就是本文的最后一节解决这个问题。

航班加亮

打开 FlightRowController.swift 添加下列方法:

func updateForCheckIn() {
  let color = UIColor(red: 90/255, green: 200/255, blue: 250/255, alpha: 1)
  planeImage.setTintColor(color)
  separator.setColor(color)
}

这里我们创建了一个 UIColor 实例,用它设置 planeImage 和 separator 的 tint 颜色。这个方法将在动画闭包中调用,因此颜色的改变是以动画的方式进行的。

然后,打开 ScheduleInterfaceController.swift 在 flights 属性后面添加:

var selectedIndex = 0

用这个属性记录当呈现两个 interface 控制器时选中的行。当表格行被选中时对这个属性赋值就好了。在 table(_:didSelectRowAt:) 中调用 presentController(withNames:contexts:) 的一句前添加:

selectedIndex = rowIndex

将 selectedIndex 设置为选中行。

最后,在 ScheduleInterfaceController 的 awake(withContext:) 方法后面添加:

override func didAppear() {
  super.didAppear()
  // 1
  guard flights[selectedIndex].checkedIn,
    let controller = flightsTable.rowController(at: selectedIndex) as? FlightRowController else {
      return
  }

  // 2
  animate(withDuration: 0.35) {
    // 3
    controller.updateForCheckIn()
  }
}

代码解释如下:

  1. 判断所选航班是否已经 check in,如果是,转换对应行索引的 row controller 为 FlightRowController 对象。
  2. 如果转换成功,用 WKInterfaceController 的 animation API 执行指定闭包,动画时长设置为 0.35 秒。
  3. 在闭包中,调用前面添加到 FlightRowController 中的方法,修改该表格行上的飞机图片和分隔线的颜色,给用户知道他们现在已经登记的航班。

Build & run。执行前面同样的步骤进行航班登机。当你返回 schedule interface controller,你会发现飞机图片和分隔线的颜色以一种渐变的方式发生了改变。

恭喜你!你已经完成了你的第一个 WatchKit 动画。

接下来做什么?

这里是本系列完成后的示例项目。

在这部分教程中,你学习了如何创建两个不同的 WatchKit 动画。第一个使用的是动画图片序列,第二个使用的是 WKInterfaceController 的 animation API。你现在已经可以为你自己的 watchOS 4 app 中添加大量的视觉效果了!

遗憾的是,本系列教程就到此为止了。

如果你有疑问或建议,请在下面的论坛中留言!

猜你喜欢

转载自blog.csdn.net/kmyhy/article/details/79485947