浅谈 iOS 渲染与动画的艺术

发布于:2018-01-10 22:14,阅读数:846,点赞数:0


# 引言 本文的主题是渲染和动画。 本文将通过简单的例子来展现 iOS 系统中的各个渲染框架能做什么,适合做什么。之后会对 Core Animation 的图层原理以及一些日常开发中遇到的奇怪的现象背后的原因做一个简单的讲解。看完这一章节,相信读者会对 iOS 的渲染和动画会进一步的了解。 # iOS 中的渲染框架 iOS 包含了多套渲染框架。从开发者们接触得最多的`UIKit`、`Core Animation`,到今年 iOS 11 最新推出的`Metal 2`、`Metal for VR`。它们各自的职责大不相同。作为开发者,我们或许并不能各个框架都精通,至少应该知道它们大概是用来做什么的,遇到具体需求的时候,才会有一个大致的方向。 ## UIKit & AppKit 作为开发者,对 UIKit 和 AppKit 应该是非常熟悉了,实际开发中大量应用到了它们。这不是本章节的重点,因此不再展开。 ## Core Animation Core Animation 是常用的框架之一。它比 UIKit 和 AppKit 更底层。正如我们所知,`UIView`底下封装了一层`CALayer`树,Core Animation 层是真正的渲染层,我们之所以能在屏幕上看到内容,真正的渲染工作是在 Core Animation 层的。关于`CALayer`的内容,会在下文中展开。 ## Core Graphics Core Graphics 也是常用的框架之一,相信开发者们都不陌生。它用于运行时绘制图像。开发者们可以通过 Core Graphics 绘制路径、颜色。当开发者需要在运行时创建图像时,可以使用 Core Graphics 去绘制。与之相对的是运行前创建图像,例如用 Photoshop 提前做好图片素材直接导入应用。相比之下,我们更需要 Core Graphics 去在运行时实时计算、绘制一系列图像帧来实现动画。 ## Core Image Core Image 与 Core Graphics 恰恰相反,Core Graphics 用于在运行时创建图像,而 Core Image 是用来处理已经创建的图像的。Core Image 框架拥有一系列现成的图像过滤器,能对已存在的图像进行高效的处理。 ![](//cdn.blog.yuusann.com/img/corpus/18003_1.jpg) 它的优点在于十分高效。大部分情况下,它会在 GPU 中完成工作,但如果 GPU 忙,会使用 CPU 进行处理。如果设备支持 Metal,那么会使用 Metal 处理。这些操作会在底层完成,Apple 的工程师们已经帮助开发者们完成这些操作了。 Core Image 的过滤器支持管道式串联,在下文中会有一个具体的使用 Core Image 框架的例子。 ## SceneKit & SpriteKit 普通开发者可能会对 SceneKit 和 SpriteKit 感到陌生,SceneKit 主要用于某些 3D 场景需求,而 SpriteKit 更多的用于游戏开发。SceneKit 和 SpriteKit 都包含了粒子系统、物理引擎等,看上去似乎是专门为游戏准备的,但事实上在普通应用中开发者们一样也可以使用它们来完成一些比较炫酷的特效和物理模拟。 ## Metal Metal 存在于以上渲染框架的最底层。Metal 对于大多数开发者来说都比较神秘,大多数开发者都没有直接使用过 Metal,但其实所有开发者都在间接地使用 Metal。Core Animation、Core Image、SceneKit、SpriteKit等等渲染框架都是构建于 Metal 之上的。 细心的开发者会发现,当在真机上调试 OpenGL 程序时,控制台会打印出启用 Metal 的日志。根据这点,笔者猜测,Apple 已经实现了一套机制将 OpenGL 命令无缝桥接到 Metal 上,由 Metal 担任真正于硬件交互的工作。 ## 小结 以上是本文中提到的渲染框架。正如上文所说,开发者们并不一定要对这些框架样样精通,真正的目的是让开发者在遇到具体某个需求时能想起来似乎有某个框架是专门做这个任务的,能有个大致的方向。 下面有一些具体的例子能更形象地展现这些框架的功能。 # 一些例子 这里将有一些简单的 Demo,代码可以从 [这里](https://developer.apple.com/sample-code/wwdc/2017/Advanced-Visual-Effects-Playground-Sample-Code.zip) 下载。例子使用 Swift 4 编写,因此需要最新的 Xcode 9 编译。 ## UIKit Customization 此范例将`UISlider`的滑轨替换成了自定义的图片,效果如下图所示: ![](//cdn.blog.yuusann.com/img/corpus/18003_2.jpg) 自定义图片经过`trackImage`方法,被重新渲染成了目标大小的图片: ```swift func trackImage(_ image: UIImage, width: CGFloat, resizingMode: UIImageResizingMode) -> UIImage { let capInsets = UIEdgeInsets(top: 0, left: 8, bottom: 0, right: 8) let bounds = CGRect(x: 0, y: 0, width: width, height: image.size.height) let renderer = UIGraphicsImageRenderer(bounds: bounds) return renderer.image { _ in image.resizableImage(withCapInsets: capInsets, resizingMode: .stretch).draw(in: bounds) }.resizableImage(withCapInsets: capInsets, resizingMode: resizingMode) } ``` 之后通过设置`UISlider`的以下方法修改滑轨图片。 ```swift tintSlider.setMinimumTrackImage(tintMinTrackImage, for: .normal) tintSlider.setMaximumTrackImage(tintMaxTrackImage, for: .normal) temperatureSlider.setMinimumTrackImage(temperatureMinTrackImage, for: .normal) temperatureSlider.setMaximumTrackImage(temperatureMaxTrackImage, for: .normal) ``` 这是一个抛砖引玉的范例,我们接着看。 ## Core Image 此范例将通过 Core Image,对图片进行实时滤镜处理。 ![](//cdn.blog.yuusann.com/img/corpus/18003_3.jpg) 上文中提到,Core Image 拥有大量现成的图像过滤器用于图像处理,过滤器称为`CIFilter`。但事实上 CIFilter 并不是真正处理任务的对象,而真正处理任务的对象为`CIContext`。CIContext 是底层硬件渲染的入口,因此在使用 Core Image 框架时,必须先要创建它。 ```swift let ciContext = CIContext() ``` Core Image 框架接收的图像为`CIImage`对象,它可以通过`UIImage`或`CGImage`去构建。 ```swift let originalCIImage = CIImage(cgImage: originalCGImage) ``` 关于 CIFilter,构造方法一定是值得吐槽的一点。它通过接收一个字符串类型的 name 参数和一个参数字典来构造对象,使得它无法脱离文档使用,期待在将来, CIFilter 的构造上能有进一步的改进。 ```swift public /*not inherited*/ init?(name: String, withInputParameters params: [String : Any]?) public /*not inherited*/ init?(name: String) ``` 范例使用一个名为`CITemperatureAndTint`的过滤器,其参数接收一个`CIImage`原始图像和两个`CIVector`二维向量参数,它的两个维度是色温和色彩。通过界面上的滚动条,用户可以任意更改其中一个向量,以达到调整色彩的作用。 ```swift let neutralTemperature = CGFloat(temperatureSlider.value) let neutralTint = CGFloat(tintSlider.value) let neutral = CIVector(x: neutralTemperature, y: neutralTint) let targetNeutralTemperature = CGFloat(6500) let targetNeutralTint = CGFloat(0) let targetNeutral = CIVector(x: targetNeutralTemperature, y: targetNeutralTint) let parameters = [ "inputImage": originalCIImage, "inputNeutral": neutral, "inputTargetNeutral": targetNeutral ] let filter = CIFilter(name: "CITemperatureAndTint", withInputParameters: parameters) ``` 准备好了这一切以后,就可以进行真正的图像处理了: ```swift let filteredImage = filter.outputImage // CIContext 创建 CGImage 的过程会比较耗时,可以在放在子线程执行。 let filteredCGImage = self.ciContext.createCGImage(filteredImage, from: filteredImage.extent) self.imageView.image = UIImage(cgImage: filteredCGImage) ``` 以上就是 Core Image 的图像滤镜处理流程。 由于图像处理是一项耗时的操作,如果你想你的应用对图像操作响应如飞,请参考接下来的`Core Graphics`。 ## Core Graphics 此范例将在运行时绘制一个张图像。 ![](//cdn.blog.yuusann.com/img/corpus/18003_4.jpg) 在去年 iOS 10 发布时,`UIGraphicsImageRenderer`诞生了,它能帮助我们更方便地使用绘图 API。 ```swift let renderer = UIGraphicsImageRenderer(size: size) ``` UIGraphicsImageRenderer 的 image 方法接收一个闭包,传入参数为`UIGraphicsImageRendererContext`。这就意味着, renderer 已经帮我们管理好了绘图上下文,我们只需在这个闭包内写入绘图内容即可。 ```swift let image = renderer.image { rendererContext in let cgContext = rendererContext.cgContext // 绘图 } ``` Core Graphics 的绘图十分形象。你可以想象手中有一根画笔,绘图时,调用`CGContext`的`move`方法将画笔移动到一个点,之后调用`addLine`方法画一条线到另一个点,就画好了一条线了。 ```swift public func move(to point: CGPoint) public func addLine(to point: CGPoint) ``` 对于范例中的五角星,我们只要画出封闭的五角星外轮廓,之后填充黄色即可。 ```swift let image = renderer.image { rendererContext in let cgContext = rendererContext.cgContext // 绘制轮廓 ...... //设置填充颜色 cgContext.setFillColor(fillColor.cgColor) //填充 cgContext.fillPath() } ``` 至此就是 Core Graphics 绘制图片的过程。 ## Core Animation Core Animation 是本文的重点。在这里只是一个简单的例子,在下文中会有更深层的剖析和一个更加精彩的例子。 此范例将实现一个基于 Core Animation 的动画。点击中间的按钮,将会有一圈星星散开,逐渐变小,最后消失。 ![](//cdn.blog.yuusann.com/img/corpus/18003_5.jpg) 正如图中所示,点击按钮后会出现很多星星。其实每一个星星都是一个`CALayer`,通过控制每一个星星的动画,即可实现整体的效果。 获得其中的一个五角星图像: ```swift let layer = CALayer() // 设置五角星,通过上面例子中的 Core Graphics 绘制五角星。 layer.contents = ... // 设置位置和大小 layer.position = ... layer.bounds = ... // 加入屏幕 button.layer.addSublayer(layer) ``` 获得了目标 Layer 后,设置动画的最终变换状态: ```swift // 设置终点位置 let finalPosition = ... // 设置终点的平移量 var finalTransform = CATransform3DMakeTranslation(finalPosition.x - initialPosition.x, finalPosition.y - initialPosition.y, 0) // 设置旋转,这里的 spinRadians 值是4π,转两圈。但由于转两圈后的状态和不转的状态是一模一样的,因此这里不会有任何旋转。 finalTransform = CATransform3DRotate(finalTransform, spinRadians, 0, 0, 1) // 设置缩放 finalTransform = CATransform3DScale(finalTransform, finalScale, finalScale, finalScale) ``` 完成了以上工作后,使用`CABasicAnimation`创建动画。 ```swift let animation = CABasicAnimation(keyPath: "transform") // 当前变换状态 animation.fromValue = layer.transform // 目标变换状态 animation.toValue = finalTransform // 动画时长 animation.duration = animationDuration // 动画时间函数,刚开始慢,最后变快。 animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut) layer.add(animation, forKey: "pew pew") // 将变换设置为最终的状态,以防动画结束后回到原点。 layer.transform = finalTransform // 在动画结束后移除Layer DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + animationDuration, execute: { layer.removeFromSuperlayer() }) ``` 这样,一个五角星的动画就设定好了。关于例子中防止状态回弹的代码,读者不妨思考一下,为什么要这样写?五角星已经移动过去了,为何状态会回弹?五角星是真的移动过去了还是看上去移动过去了?这些问题与 Core Animation 的图层结构有关,将在下文中探讨。 ## SpriteKit 此范例实现了一个简单的粒子效果。 ![](//cdn.blog.yuusann.com/img/corpus/18003_6.jpg) SpriteKit 是一款游戏引擎,但并不意味着只有游戏才能用,普通应用中一样也可以使用它来实现特定的效果。 场景是 SpriteKit 的一个大单位,称为`SKScene`。而粒子节点`SKEmitterNode`是实现粒子效果的载体,在 Xcode 中,粒子效果可以可视化定制,生成 sks 文件供程序调用。 ![](//cdn.blog.yuusann.com/img/corpus/18003_8.jpg) 图中为 Xcode 自带的粒子效果工具,通过调整这些参数,开发者能实时预览粒子的变化。最终生成 sks 文件,就可以在程序中直接加载了。在设计粒子系统上,开发者并不需要写任何一句代码,就可以完成。 ```swift // 创建场景 scene = SKScene(size: UIScreen.main.bounds.size) sceneView.presentScene(scene) // 从sks文件加载粒子效果 emitter = SKEmitterNode(fileNamed: "star-particle-effect.sks") // 生成粒子的贴图 emitter.particleTexture = SKTexture(image: starImage) // 设置粒子的喷射点 emitter.position = CGPoint(x: scene.size.width / 2, y: scene.size.height / 2) // 将节点加入场景,粒子系统会自动开始工作 sceneView.scene?.addChild(emitter) ``` 对于这样的一个简单的粒子系统,只需要上面的几行代码即可实现。 ## SceneKit 此范例实现了一个三维粒子和物理模拟的粒子系统,粒子喷出的同时会因为重力旋转、掉落。 ![](//cdn.blog.yuusann.com/img/corpus/18003_7.jpg) SceneKit 也是一款游戏引擎,也有场景的概念,称为`SCNScene`。不同的是,SceneKit 带来的是一个三维的场景。因此引入了摄像机`SCNCamera`的概念。通过设定摄像机不同的位置和不同观察角度,可以观察到三维世界里不同的现象。不过范例中并不需要操作摄像机。而这次的粒子并不是粒子系统自动创建的,而是通过手动生成`SCNNode`,赋予一定的初始速度,通过物理模拟喷射而出的。 在 SceneKit 中,视角变换、材质和光影只需要设定相应的值,具体的渲染过程已经不需要开发者去操心了。 设定摄像机: ```swift // 创建场景 scene = SCNScene() // 创建摄像机 let camera = SCNCamera() let cameraNode = SCNNode() cameraNode.camera = camera cameraNode.position = SCNVector3(x: 0, y: 0, z: 10) scene.rootNode.addChildNode(cameraNode) // 加载物体资源 let asset = MDLAsset(url: Bundle.main.url(forResource: "star", withExtension:"obj")!) // 从资源加载物体几何体 if let mesh = asset[0] as? MDLMesh { starGeometry = SCNGeometry(mdlMesh: mesh) } // 创建材质 let material = SCNMaterial() material.diffuse.contents = UIColor.yellow starGeometry.materials = [material] // 创建光源 let lightNode = SCNNode() lightNode.light = SCNLight() lightNode.light?.type = .directional lightNode.position = SCNVector3(x: 10, y: 10, z: 10) scene.rootNode.addChildNode(lightNode) ``` 设定完场景的信息以后,视角变换、材质、光影渲染会由引擎完成,而开发者只需要致力于业务开发即可。 ```swift // 定时每秒喷射20个星星 starTimer = Timer.scheduledTimer(withTimeInterval: 0.05, repeats: true) { _ in // 从几何体生成星星节点 let starNode = SCNNode(geometry: self.starGeometry) self.scene.rootNode.addChildNode(starNode) // 设置物理行为 starNode.physicsBody = SCNPhysicsBody(type: .dynamic, shape: SCNPhysicsShape(geometry: self.starGeometry, options: nil)) // 设置初始喷射速度 starNode.physicsBody?.velocity = SCNVector3(5 * (drand48() - 0.5), 5 + 2 * drand48(), 5 * (drand48() - 0.5)) // 定时移除节点 DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 5) { starNode.removeFromParentNode() } } ``` 使用定时器定时喷射节点,而重力下落、旋转等也已经由引擎接管。至此,一个三维的粒子系统就已经完成了。 ## 小结 通过几个简单的例子,向读者们展示了 iOS 系统中各个渲染框架的用途,相信大家对这些渲染框架已经有了基本的概念。接下来会针对日常开发中最常用的动画框架 Core Animation 做一个更为深入的讲解。 # Core Animation 本章节将会对 Core Animation 的图层原理和一些日常开发中需要注意的地方做讲解。 ## 图层原理 动画由一系列连续的帧组成,开发者需要手动编写每一帧的 UI 来实现动画。听上去很傻,我们从来都没那么做过。因为 Core Animation 已经为我们做了一部分工作,开发者只需要设定一些关键帧,就可以实现动画。 ```swift UIView.animate(withDuration: 0.25, animations: { self.view.frame.origin.y = 100; }) ``` 这是一个简单的移动动画,在设定`self.view`会在 0.25 秒的时间内向下移动 100 个单位。当这段代码提交以后,view 会匀速向下移动,看上去`self.view.frame.origin.y`是匀速增加的,如果在动画结束以前去查询坐标的`y`值,从表面上来看,动画还在进行过程中,因此`y`值应该处于 0 ~ 100 中的某个值,但事实上会发现值已经是 100 了。属性在提交的那一刻就已经发生了变化,但为什么界面上没有立刻跳到目标帧?这个动画到底是如何实现的呢? 其实用户看到的动画层,并不是开发者所修改的那个层。这里涉及到两个层,模型层(Model)和展现层(Presentation)。 在提交动画的那一刻,展现层会对模型层做拷贝,覆盖在模型层上面。提交动画时模型层的值会立刻被修改,而展现层还是原来的样子。在动画进行过程中去查询属性,查询到的是模型层里的属性,它已经在提交动画时被修改了。而用户看到的动画,是 Core Animation 每一帧所计算的展现层。因此会出现查询到的属性和界面现象不符的情况。 当动画结束时,展现层就不复存在的,此时用户将再次看到模型层的界面。这也就解释了为什么`CAAnimation`在动画结束时画面会立刻跳回到原始画面,因为`CAAnimation`只提交动画,Core Animation 只是计算了一堆展现层覆盖在上面来展示动画并不修改模型层的属性值。对于这个问题,网上很多人提出了这样的解决方法: ```swift animation.isRemovedOnCompletion = false animation.fillMode = kCAFillModeForwards ``` 它阻止了动画结束时展现层的移除,使得表面上看上去貌似是解决了问题,但模型层的属性仍然是原来的值。这会造成什么问题?很多“奇怪”的现象就出现了。例如移动了一个按钮,展现层上按钮是移动了,但按钮的模型层还在老地方,用户所看到的按钮是永远也点不中的。 这个问题正确的解决方案是什么?非常简单,在提交动画后,直接修改属性值。Core Animation 仍然会计算展现层覆盖在模型层之上,使用户看到的还是正常的动画。模型层实际上已经在目标位置了。动画结束时,展现层被移除,此时用户将看到已经在目标位置的模型层,整个动画过程也就结束了。 ## Frame & Transform 虽然`frame`和`bounds`属性在日常开发中用的很多,但不了解它们是如何计算的话,可能会遇上一些麻烦。例如以下一些列操作以后,会复现一个奇怪的现象。 - 设置了 layer 的 frame。 ```swift layer.frame = CGRect(x: 0, y: 0, width: 200, height: 100) ``` - 对 layer 进行缩放,缩小到原来的四分之一。 ```swift layer.transform = CGAffineTransform(scaleX: 0.25, y: 0.25) ``` - 重新设置 frame,layer 回到了之前的大小。 ```swift layer.frame = CGRect(x: 0, y: 0, width: 200, height: 100) ``` - 设置旋转角度,layer 突然放大了四倍,变成了 800 x 400。 ```swift layer.transform = CGAffineTransform(rotationAngle: 0.2) ``` 为什么会出现这种现象? 原因在于,`frame`是 layer 在外部的大小,`bounds`而是 layer 内部的大小。当 frame 被设置时,bounds 和 transform 会被作为依据进行计算。换句话说,在上面步骤中的第三步,重新设置 frame 时,transform 属性仍然是缩小四倍的效果,因此此时 frame 会变成目标大小的四倍。到此为止,在屏幕上看到的仍然是正确的大小,但在第四步,重新设置 transform 属性时,缩小四倍效果被替换成了旋转,因此此时 layer 会突然变成四倍大小。 因此,在使用 transform 属性的同时设置 frame 时,要格外小心,避免这类奇怪的现象的出现。 ## Mask & Shadow Mask 和 Shadow 的不恰当使用,会带来严重的性能问题。 先来说说 Mask。Mask 即遮罩,这是一个常用的裁剪异形图像的方法。通过一个形状或图像,对另一个形状或图像做遮罩,取出交集或其他感兴趣的部分,例如下图中通过右图对左图进行裁剪,得到中图。 ![](//cdn.blog.yuusann.com/img/corpus/18003_9.jpg) 如果要得到下图的效果,你可能会想到使用两次遮罩。 ![](//cdn.blog.yuusann.com/img/corpus/18003_10.jpg) 如下图所示,使用半透明遮罩得到中图后,再使用裁剪遮罩得到最终结果。 ![](//cdn.blog.yuusann.com/img/corpus/18003_11.jpg) 这么做虽然可行,但是会带来严重的性能问题。每一帧的渲染需要重复多次离屏渲染,多次开辟新的空间,拷贝图像内存,是非常大的开销。因此这类情况下,在裁剪前覆盖一张半透明图片会比重复遮罩要快的多,重复遮罩并不是明智的选择。 再来说说 Shadow。 在绘制阴影以前,系统并不知道这个 layer 的轮廓是怎样的。因此需要将整个 layer 树都完整的绘制出来以后才能去绘制阴影。同样是上面图里的例子,这个 layer 包含了非常多的 emoji 表情,相当于包含了很多子 layer。默认情况下在绘制阴影以前,需要将每一个表情都绘制出来,然后再计算阴影。 事实上那些 emoji 表情都在根 layer 内部,是不会对阴影造成任何影响的,因此绘制那些 layer 是没有必要的,传统的阴影绘制模型在这种情况下效率会非常低。解决方法是使用 Core Graphics 绘制阴影 path 进行阴影计算,这样做会非常高效,避免不必要的绘制。 ## Tips & Tricks 这里是几个可能被大家忽视却非常有用的东西。 ### CAShapeLayer `CAShapeLayer`是 CALayer 的一个子类,它使用 Core Graphics Path 绘制 layer。 ![](//cdn.blog.yuusann.com/img/corpus/18003_12.jpg) 有意思的是,修改它的属性时是会有过渡动画的。以及它`strokeStart`和`strokeEnd`属性可以让开发者去定义它从哪里开始绘制,到哪里结束。还可以通过`lineDashPattern`和`lineDashPhase`属性设置边线样式。 CAShapeLayer 有非常多跟线条、路径有关的属性,可以用来完成许多有趣的效果。 ### CAGradientLayer `CAGradientLayer`是 CALayer 的一个子类,用于绘制渐变的图层。 ![](//cdn.blog.yuusann.com/img/corpus/18003_13.jpg) 同样的,修改它的属性,也会产生过渡动画。它的`startPoint`和`endPoint`属性可以自定义绘制过程,产生类似金属高光反射的效果,如上图所示。 ### Layer Speed Layer Speed 指的是 layer 在执行动画的速度。 在动画过程中,每个 layer 都使用自己的 layer speed。这就意味着,一个layer 动画,它的子 layer 可以使用不同的动画速度。且当速度为 0 时,此 layer 的动画将会暂停。这是一个非常强大的功能,使得开发者能完全控制动画进程。意味着 layer 动画,是可以打断,可以暂停的,甚至可以是回溯的。 这里有一个可回溯的动画例子:[https://github.com/enums/LayerAnimationDemo](https://github.com/enums/LayerAnimationDemo) 在界面任意位置上下拖动时,界面上的 layer 会慢慢展开,往回拖动则可以回溯到动画的任意时刻。查看代码会发现,其实际上是一个`CABaseAnimation`,但在视图加载时,layer 的速度被设置为了 0,此时动画停止: ```swift containerLayer.speed = 0 ``` 在响应手势时设置 layer 的`timeOffset`属性使动画回溯到任意时间点。 ```swift containerView?.layer.timeOffset = currentContainerExpansion ``` 通过控制 layer 的`speed`和`timeOffset`,实现动画进程的控制。但需要注意的是,这里的动画只显示在展现层,并不更改模型层的参数。在 iOS 的事件响应链中,碰撞测试是以模型层参数为准的,因此任何事件仍然会按照模型层的位置传递。如果给一个 UIButton 添加了类似动画,点击屏幕所显示的按钮的位置时未必能够命中按钮。在实际使用个过程中需要注意这点。 ## 小结 通过几个简单的例子,介绍了 Core Animation 的一部分原理和日常使用过程中需要注意的点。图层原理可帮助读者更好的理解过渡动画的原理,避免一些由于对图层原理不熟悉而可能发生的麻烦。几个例子指出了日常开发中可能犯的错误,因此可能会造成界面混乱以及性能问题,在日常开发中需要多加注意。最后的 tips & Tricks 简单介绍了两个不常用的工具 layer,以及使用 layer speed 实现的可回溯动画。 # 结语 作为客户端开发者,用户体验是永恒的话题。上文所提到的渲染框架,是 Appler 工程师们给开发者们的馈赠。高效的 Core Graphics 和 Core Animation,炫酷的 SpriteKit,SceneKit。可以帮助开发者们快速开发动画和特效。 动画和渲染的学问远不止这些,本文的目的也是拓宽开发者们的视野,将渲染框架一一摆出来展示,让大家在遇到具体需求时能更合适的选择框架去做,在制作粒子特效时尽可能的选择 SpriteKit 和 SceneKit 去做,而不是一味地创建大量 UIView 去实现。看到这里,相信读者们都能够选择正确的渲染框架去做正确的事情了。 # 相关链接 - [Building Visually Rich User Experiences - Video](https://developer.apple.com/videos/play/wwdc2017/235/) - [Building Visually Rich User Experiences - SampleCode](https://developer.apple.com/sample-code/wwdc/2017/Advanced-Visual-Effects-Playground-Sample-Code.zip) - [Layer Speed Animation Demo](https://github.com/enums/LayerAnimationDemo)


评论:0条


返回列表

返回主页