第四章:实践视图动画(View Animations in Practice)

查看可用动画API的参考,很容易陷入思考视图动画限制淡入或淡出以及在屏幕周围移动视图的陷阱。 在本章中,您将学习三个结合不同动画的实际示例,以创建更复杂,非平凡的视觉效果。

话虽如此,请注意本章是可选的。 如果您想继续学习新的API,请随意跳到下一章 - 没有难过的感觉!

在本章中,您将添加一些很酷的动画来装扮航班摘要屏幕,如下所示:
第四章:实践视图动画(View Animations in Practice)_第1张图片
本章中有一些新的效果基于您在前几章中学到的动画基础:

  1. Crossfade animation:将一个图像混合到另一个图像的动画。
  2. Cube transition animation: 一种过渡动画,可创建虚拟3D过渡效果。
  3. Fade and bounce transition:对于简单动画,辅助视图和您到目前为止学到的所有其他内容的组合略有不同。

这一章代码很有趣,所以请继续下一部分立即开始!

交叉渐变动画(Crossfading animations)

该应用程序目前显示从伦敦到罗马的航班摘要,并在巴黎停留。

航班摘要屏幕在两个转机航班之间交替显示,目的地,航班号和登机口号等项目:

您的首要任务是在两个背景图像之间平滑过渡或混合。

你的第一直觉可能是简单地淡出当前的图像然后淡入新的图像。 但是当alpha接近零时,这种方法会显示图像背后的内容; 您希望此屏幕后面的内容在整个动画期间保持隐藏状态。
第四章:实践视图动画(View Animations in Practice)_第2张图片
显然,你需要一种不同的方法来解决这个问题。
创建一个新的工程
第四章:实践视图动画(View Animations in Practice)_第3张图片

打开ViewController.swift并添加以下新方法来处理新的交叉渐变效果:

func fade(imageView: UIImageView, toImage: UIImage, showEffects: Bool) {
  UIView.transition(with: imageView, duration: 1.0,
    options: .transitionCrossDissolve,
    animations: {
      imageView.image = toImage
    },
    completion: nil
  )
  UIView.animate(withDuration: 1.0, delay: 0.0,
    options: .curveEaseOut,
    animations: {
      self.snowView.alpha = showEffects ? 1.0 : 0.0
    },
    completion: nil
  )
}

在viewDidLoad中添加一下代码:

   summary.addSubview(summaryIcon)
   summaryIcon.center.y = summary.frame.size.height/2
   
   //add the snow effect layer
   snowView = SnowView(frame: CGRect(x: -150, y:-100, width: 300, height: 50))
   let snowClipView = UIView(frame: view.frame.offsetBy(dx: 0, dy: 50))
   snowClipView.clipsToBounds = true
   snowClipView.addSubview(snowView)
   view.addSubview(snowClipView)

需创建一个snowView全局变量

snowView:SnowView!

创建一个名为SnowView的swift文件,内容如下:

import UIKit
import QuartzCore

class SnowView: UIView {
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        
        let emitter = layer as! CAEmitterLayer
        emitter.emitterPosition = CGPoint(x: bounds.size.width / 2, y: 0)
        emitter.emitterSize = bounds.size
        emitter.emitterShape = CAEmitterLayerEmitterShape.rectangle
        
        let emitterCell = CAEmitterCell()
        emitterCell.contents = UIImage(named: "flake.png")!.cgImage
        emitterCell.birthRate = 200
        emitterCell.lifetime = 3.5
        emitterCell.color = UIColor.white.cgColor
        emitterCell.redRange = 0.0
        emitterCell.blueRange = 0.1
        emitterCell.greenRange = 0.0
        emitterCell.velocity = 10
        emitterCell.velocityRange = 350
        emitterCell.emissionRange = CGFloat(Double.pi/2)
        emitterCell.emissionLongitude = CGFloat(-Double.pi)
        emitterCell.yAcceleration = 70
        emitterCell.xAcceleration = 0
        emitterCell.scale = 0.33
        emitterCell.scaleRange = 1.25
        emitterCell.scaleSpeed = -0.25
        emitterCell.alphaRange = 0.5
        emitterCell.alphaSpeed = -0.15
        
        emitter.emitterCells = [emitterCell]
    }
    
    required init(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override class var layerClass: AnyClass {
        return CAEmitterLayer.self
    }
}

该方法采用以下三个参数:

  1. imageView:这是你要淡出的图像视图。
  2. toImage:这是您想要在动画结束时看到的新图像。
  3. showEffects:这是一个布尔标志,指示场景是否应显示或隐藏降雪效果。 降雪动画应仅在第一个转机航班的屏幕上运行,大概是机场正在经历恶劣天气。

这次使用的过渡动画类型为.transitionCrossDissolve - 这使您可以简单地将图像视图的图像更改为作为参数提供的图像,UIKit会自动创建交叉淡入淡出过渡。

此外,您可以根据show Effects的值,与其余动画并行淡入或淡出snowView。

注意:降雪效果看起来很酷,对吧? 您将在第22章“粒子发射器(Particle Emitters)”中学习如何产生类似的效果。

创建一个方法用于切换航班所有数据,方法如下:

  func changeFlight(to data: FlightData) {
    
    // populate the UI with the next flight's data
    summary.text = data.summary
    flightNr.text = data.flightNr
    gateNr.text = data.gateNr
    departingFrom.text = data.departingFrom
    arrivingTo.text = data.arrivingTo
    flightStatus.text = data.flightStatus
    bgImageView.image = UIImage(named: data.weatherImageName)
    snowView.isHidden = !data.showWeatherEffects
    
    // schedule next flight
    delay(seconds: 3.0) {
      self.changeFlight(to: data.isTakingOff ? parisToRome : londonToParis)
    }
  }

delay方法:

func delay(seconds: Double, completion: @escaping ()-> Void) {
  DispatchQueue.main.asyncAfter(deadline: .now() + seconds, execute: completion)
}

创建一个名为FlightData的swift文件,代码如下:

import Foundation

//
// Flight model
//

struct FlightData {
let summary: String
let flightNr: String
let gateNr: String
let departingFrom: String
let arrivingTo: String
let weatherImageName: String
let showWeatherEffects: Bool
let isTakingOff: Bool
let flightStatus: String
}

//
// Pre- defined flights
//

let londonToParis = FlightData(
summary: "01 Apr 2015 09:42",
flightNr: "ZY 2014",
gateNr: "T1 A33",
departingFrom: "LGW",
arrivingTo: "CDG",
weatherImageName: "bg-snowy",
showWeatherEffects: true,
isTakingOff: true,
flightStatus: "Boarding")

let parisToRome = FlightData(
summary: "01 Apr 2015 17:05",
flightNr: "AE 1107",
gateNr: "045",
departingFrom: "CDG",
arrivingTo: "FCO",
weatherImageName: "bg-sunny",
showWeatherEffects: false,
isTakingOff: false,
flightStatus: "Delayed")

由于您最终将为此屏幕上的所有UI元素设置动画,因此请更新该方法的签名,如下所示:

func changeFlight(to data: FlightData, animated: Bool = false) {

接下来,在changeFlight(to:,animated :)中找到更新图像视图和雪景的代码:

bgImageView.image = UIImage(named: data.weatherImageName)
snowView.isHidden = !data.showWeatherEffects

将其包装在if语句中,该语句可以选择设置转换的动画,如下所示:

if animated {
  fade(imageView: bgImageView,
    toImage: UIImage(named: data.weatherImageName)!,
    showEffects: data.showWeatherEffects)
} else {
  bgImageView.image = UIImage(named: data.weatherImageName)
  snowView.isHidden = !data.showWeatherEffects
}

当动画为真时,此方法调用 fade(imageView:, toImage:, showEffects:),并从数据传入适当的参数。

如果你想查看数据内部的内容,请查看FlightData.swift。 FlightData结构是代表单个航班的模型; FlightData.swift中预先定义了两个这样的航班:londonToParis和parisToRome。

回到ViewController.swift,你仍然需要再做一次更改才能在屏幕上看到交叉渐变效果你需要将动画参数添加到你调用changeFlight(to:, animated:).

在changeFlight(to:animated :)中,在延迟调用内的方法结尾附近更改对self.changeFlight的调用,如下所示:

 self.changeFlight(to: data.isTakingOff ? parisToRome : londonToParis, animated: true)

构建并运行您的应用程序; 您现在应该看到图像视图平滑过渡:
第四章:实践视图动画(View Animations in Practice)_第4张图片
这些图像完美地融合在一起,因为你同时淡化雪景效果,动画看起来很无缝。 你甚至可以在罗马看到它下雪一瞬间!
您已在此处学习了一项重要技术:转换可用于为视图的不可动画属性设置动画。

注意:如果您想获得乐趣,可以尝试上一章中的一些过渡效果。 请记住,像.transitionFlipFromLeft这样的转换对于当前项目而言太过分散注意力。 .transitionCrossDissolve是一种微妙的“背景”效果,它只会增强动画,这些动画将在前景中发生。
这会照顾图像视图,但是航班号和门号的文本标签看起来像是可以使用一些创意动画:

第四章:实践视图动画(View Animations in Practice)_第5张图片

下一步你将使用faux-3D transition.来处理这些问题。

立方体过渡(Cube transitions)

您将在此部分中构建的效果使得飞行和门信息看起来像在一个立方体的相邻侧面,该立方体围绕其中心旋转以显示下一个值。 完成后,您的动画将如下图所示:
第四章:实践视图动画(View Animations in Practice)_第6张图片
这不是一个真正的3D效果,但它看起来非常接近,这是一个很好的机会,您可以尝试以辅助视图为特色的动画。

您的方法是添加临时标签,同时为两个标签的高度设置动画,删除临时标签,最后自行清理。

这也是你创建的第一个有方向的动画,因为你可以向前和向后播放它。 方向将确定临时标签是从顶部还是底部出现。

首先在ViewController类中添加以下枚举:

enum AnimationDirection: Int {
  case positive = 1
  case negative = -1
}

您的动画方法将采用一个设置动画方向的参数。

接下来,添加该方法的初始版本,如下所示:

func cubeTransition(label: UILabel, text: String, direction:
AnimationDirection) {
  let auxLabel = UILabel(frame: label.frame)
  auxLabel.text = text
  auxLabel.font = label.font
  auxLabel.textAlignment = label.textAlignment
  auxLabel.textColor = label.textColor
  auxLabel.backgroundColor = label.backgroundColor
}

该方法有三个参数:

  1. label:要制作动画的标签。
  2. text:要在标签上显示的新文本。
  3. direction:为新文本标签设置动画的位置; 这是视图的顶部或底部。

首先,您将创建一个新标签auxLabel并将现有标签的所有关键属性复制到
其中,包括框架,字体和对齐方式。

两个标签之间的唯一区别是它们各自包含的文本:辅助标签将包含新文本。

继续过渡! 将以下代码添加到cubeTransition的末尾:

let auxLabelOffset = CGFloat(direction.rawValue) *
  label.frame.size.height/2.0
auxLabel.transform =
  CGAffineTransform(translationX: 0.0, y: auxLabelOffset)
  .scaledBy(x: 1.0, y: 0.1)
label.superview?.addSubview(auxLabel)

在上面的代码中,首先计算辅助标签的垂直偏移量。 方向的原始值为1或-1,这为您提供了正确的垂直偏移以定位临时标签。

接下来,调整辅助标签的变换以创建虚假透视效果。 当你单独在它的Y轴上缩放文本时,它看起来就像你正在查看边缘文本的平面一样被压扁:
第四章:实践视图动画(View Animations in Practice)_第7张图片

最后,将新创建的标签添加到层次结构中与现有标签相同的级别。

接下来将以下动画代码添加到cubeTransition的末尾:

 UIView.animate(withDuration: 0.5, delay: 0.0, options: .curveEaseOut, animations: {
            auxLabel.transform = .identity
            label.transform = CGAffineTransform(translationX: 0.0, y: -auxLabelOffset).scaledBy(x: 1.0, y: 0.1)
        }) { _ in
            label.text = auxLabel.text
            label.transform = .identity
            auxLabel.removeFromSuperview()
        }

在动画块中,您重置了auxLabel的变换; 这使得新文本在高度上增长并将其精确定位在旧文本之上。

说到旧文本,您还可以将变换应用于label,将其缩小并沿与新文本出现位置相反的方向移动。

在if语句中的changeFlight(to:animated :)中插入以下代码,就在你调用fade()方法的地方下面:

let direction: AnimationDirection = data.isTakingOff ?
 .positive : .negative
cubeTransition(label: flightNr, text: data.flightNr, direction:
direction)
cubeTransition(label: gateNr, text: data.gateNr, direction: direction)

在同一方法中,移动在else语句中设置flightStatus,flightNr,gateNr,departureFrom,arrivalTo文本的现有代码,否则这些语句将立即更改标签的文本并破坏您的动画。

完成的else块应该看起来像这样:

} else {
  bgImageView.image = UIImage(named: data.weatherImageName)
  snowView.isHidden = !data.showWeatherEffects
  flightNr.text = data.flightNr
  gateNr.text = data.gateNr
  departingFrom.text = data.departingFrom
  arrivingTo.text = data.arrivingTo
  flightStatus.text = data.flightStatus
}

构建并运行您的项目; 当你看门时,享受你的劳动成果,数字和航班号通过您的奇特仿3D过渡动画。

请注意,如何向前和向后运行动画会给人一种真实的印象,即您在两个备用状态之间切换。 感谢你,这个屏幕变得越来越活跃!

你还没完成; 您将通过在屏幕上的三字母机场代码中添加复合动画来完成本章,以使屏幕过渡感觉有机和活泼!

如果图片过渡有黑色边框,在storyboard中将tabel的属性背景颜色调为无色(red:0,green:0,blue:0)

淡入淡出和反弹过渡(Fade and bounce transitions)

本章的最后一个动画使用了标签、辅助视图以及到目前为止所学的一切。如果你愿意的话,把它当作一个章节回顾吧!

首先为新的过渡动画添加以下新方法:

func moveLabel(label: UILabel, text: String, offset: CGPoint) {
  let auxLabel = UILabel(frame: label.frame)
  auxLabel.text = text
  auxLabel.font = label.font
  auxLabel.textAlignment = label.textAlignment
  auxLabel.textColor = label.textColor
  auxLabel.backgroundColor = .clear
  auxLabel.transform = CGAffineTransform(translationX: offset.x, y:
offset.y)
  auxLabel.alpha = 0
  view.addSubview(auxLabel)
}

新方法有三个参数:

  1. label:您想要制作动画的label.
  2. text:要显示的新文本。
  3. offset:用于为辅助标签设置动画的任意偏移量。

上面的代码非常简单:正如您在上一节中所做的那样,您创建了一个辅助标签,并从现有标签中复制所有属性。 要创建标签变换,只需使用offset参数即可。

最后,在将新标签添加到视图控制器的视图之前,通过将其alpha属性设置为0.0来隐藏它。

这为您的动画设定了舞台。 接下来,您将要交换原始和辅助标签的位置。 这次,您不会为两个标签使用相同的动画,而是为每个标签创建单独的动画,并将它们彼此独立地移动。 这将产生有趣的有机视觉效果。

首先将以下代码添加到moveLabel的末尾:

UIView.animate(withDuration: 0.5, delay: 0.0,
  options: .curveEaseIn,
  animations: {
    label.transform = CGAffineTransform(translationX: offset.x, y:
offset.y)
   label.alpha = 0.0
  },
  completion: nil
)

此动画将label移离其原始位置并将其淡出。

接下来,添加以下代码以为辅助标签设置动画:

   UIView.animate(withDuration: 0.25, delay: 0.1, options: .curveEaseOut, animations: {
            auxLabel.transform = .identity
            auxLabel.alpha = 1.0
        }) { _ in
    
        }     

您重置上面的辅助标签的变换,有效地将其移动到其原始位置。 您还可以通过设置其alpha值的动画来淡化文本。

请注意,此动画在延迟0.1秒后开始播放,仅持续0.25秒。 这意味着两个标签在交换位置时会重叠,从而产生微妙但令人愉悦的“鬼”效果。
第四章:实践视图动画(View Animations in Practice)_第8张图片
在测试新动画之前,需要添加代码以在动画完成时删除辅助标签。

在以上面代码的完成中添加添加一下代码:

{ _ in
            auxLabel.removeFromSuperview()
            label.text = text
            label.alpha = 1.0
            label.transform = .identity
        }

这很好地包装了转换,将标签返回到其初始状态并设置标签的新文本。

现在找到changeFlight(to:,animated:) 并将以下代码添加到if animated中的底部:

let offsetDeparting = CGPoint(
  x: CGFloat(direction.rawValue * 80),
  y: 0.0)
moveLabel(label: departingFrom, text: data.departingFrom,
  offset: offsetDeparting)
let offsetArriving = CGPoint(
  x: 0.0,
  y: CGFloat(direction.rawValue * 50))
moveLabel(label: arrivingTo, text: data.arrivingTo,
  offset: offsetArriving)

这就是你需要的所有代码! 构建并运行您的应用程序,通过您快速的新过渡效果查看所有标签的动画效果。
第四章:实践视图动画(View Animations in Practice)_第9张图片
花一点时间欣赏,即使在这个令人印象深刻的动画中,你仍然只在任何给定视图上动画了一些属性。 你已经使用了边界,框架,中心,变换,backgroundColor和alpha - 但你已经设法用这些属性创建一些非常令人印象深刻的动画!

动画每个属性可能并不总是会产生令人兴奋的效果,但使用多个简单动画的组合并使用微妙而强大的技术(如添加辅助视图和过渡)可以产生令人印象深刻的视觉效果。

让你的想象力…飞翔!

关键点

  • 您不仅限于从单个动画调用中设置单个视图属性的动画; 你可以自由地组合和重叠动画。
  • 要创建复杂的效果,您可能会使用您觉得手头的任务需要的任何和所有“技巧”,包括在动画期间创建临时视图。

挑战

挑战1:为statusBanner设置动画

statusBanner上写着“Boarding”的文字是UILabel; 因此,您可以使用本章中创建的两个标签动画中的任何一个来显示备用航班状态。
第四章:实践视图动画(View Animations in Practice)_第10张图片
我相信你可以弄清楚如何自己制作横幅动画; 使用flightStatus插座访问标签和data.flightStatus以获取要设置动画的新文本。

我相信你可以弄清楚如何自己制作statusBanner动画; 使用flightStatus的outlet访问label和data.flightStatus以获取要设置动画的新文本。

随意进行实验和进一步运行。 您获得的经验越多,您对UIKit动画的理解就越好!

现在您已成为动画和过渡的专家,现在是View Animation的最终主题的时候了 - 将多个动画步骤与关键帧组合在一起。

挑战代码

只需要changeFlight方法中的if animated中添加一下代码

cubeTranstion(label: flightStatus, text: data.flightStatus, direction: direction)

你可能感兴趣的:(Animation)