最近上映的复仇者联盟4据说没有片尾彩蛋,不过谷歌帮我们做了。只要在谷歌搜索灭霸,在结果的右侧点击手套,你将化身为灭霸,其中一半的搜索结果会化为灰烬消失...那么这么酷的动画在iOS中可以实现吗?答案是肯定的。整个动画主要包含以下几部分:响指动画、沙化消失以及背景音效和复原动画,让我们分别来看看如何实现。
响指动画
Google的方法是利用了48帧合成的一张Sprite图进行动画的:
原始图片中48幅全部排成一行,这里为了显示效果截成2行
iOS 中通过这张图片来实现动画并不难。CALayer有一个属性contentsRect,通过它可以控制内容显示的区域,而且是Animateable的。它的类型是CGRect,默认值为(x:0.0, y:0.0, width:1.0, height:1.0),它的单位不是常见的Point,而是单位坐标空间,所以默认值显示100%的内容区域。新建Sprite播放视图层AnimatableSpriteLayer:
- class AnimatableSpriteLayer: CALayer {
- private var animationValues = [CGFloat]()
- convenience init(spriteSheetImage: UIImage, spriteFrameSize: CGSize ) {
- self.init()
- //1
- masksToBounds = true
- contentsGravity = CALayerContentsGravity.left
- contents = spriteSheetImage.cgImage
- bounds.size = spriteFrameSize
- //2
- let frameCount = Int(spriteSheetImage.size.width / spriteFrameSize.width)
- for frameIndex in 0.. animationValues.append(CGFloat(frameIndex) / CGFloat(frameCount))
- }
- }
- func play() {
- let spriteKeyframeAnimation = CAKeyframeAnimation(keyPath: "contentsRect.origin.x")
- spriteKeyframeAnimation.values = animationValues
- spriteKeyframeAnimation.duration = 2.0
- spriteKeyframeAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
- //3
- spriteKeyframeAnimation.calculationMode = CAAnimationCalculationMode.discrete
- add(spriteKeyframeAnimation, forKey: "spriteKeyframeAnimation")
- }
- }
沙化消失
这个效果是整个动画较难的部分,Google的实现很巧妙,它将需要沙化消失内容的html通过html2canvas渲染成canvas,然后将其转换为图片后的每一个像素点随机地分配到32块canvas中,对每块画布进行随机地移动和旋转即达到了沙化消失的效果。
像素处理
新建自定义视图 DustEffectView,这个视图的作用是用来接收图片并将其进行沙化消失。首先创建函数createDustImages,它将一张图片的像素随机分配到32张等待动画的图片上:
- class DustEffectView: UIView {
- private func createDustImages(image: UIImage) -> [UIImage] {
- var result = [UIImage]()
- guard let inputCGImage = image.cgImage else {
- return result
- }
- //1
- let colorSpace = CGColorSpaceCreateDeviceRGB()
- let width = inputCGImage.width
- let height = inputCGImage.height
- let bytesPerPixel = 4
- let bitsPerComponent = 8
- let bytesPerRow = bytesPerPixel * width
- let bitmapInfo = CGImageAlphaInfo.premultipliedLast.rawValue | CGBitmapInfo.byteOrder32Little.rawValue
- guard let context = CGContext(data: nil, width: width, height: height, bitsPerComponent: bitsPerComponent, bytesPerRow: bytesPerRow, space: colorSpace, bitmapInfo: bitmapInfo) else {
- return result
- }
- context.draw(inputCGImage, in: CGRect(x: 0, y: 0, width: width, height: height))
- guard let buffer = context.data else {
- return result
- }
- let pixelBuffer = buffer.bindMemory(to: UInt32.self, capacity: width * height)
- //2
- let imagesCount = 32
- var framePixels = Array(repeating: Array(repeating: UInt32(0), count: width * height), count: imagesCount)
- for column in 0.. for row in 0.. let offset = row * width + column
- //3
- for _ in 0...1 {
- let factor = Double.random(in: 0..<1) + 2 * (Double(column)/Double(width))
- let index = Int(floor(Double(imagesCount) * ( factor / 3)))
- framePixels[index][offset] = pixelBuffer[offset]
- }
- }
- }
- //4
- for frame in framePixels {
- let data = UnsafeMutablePointer(mutating: frame)
- guard let context = CGContext(data: data, width: width, height: height, bitsPerComponent: bitsPerComponent, bytesPerRow: bytesPerRow, space: colorSpace, bitmapInfo: bitmapInfo) else {
- continue
- }
- result.append(UIImage(cgImage: context.makeImage()!, scale: image.scale, orientation: image.imageOrientation))
- }
- return result
- }
- }
添加动画
Google的实现是给canvas中css的transform属性设置为rotate(deg) translate(px, px) rotate(deg),值都是随机生成的。如果你对CSS的动画不熟悉,那你会觉得在iOS中只要添加三个CABasicAnimation然后将它们添加到AnimationGroup就好了嘛,实际上并没有那么简单... 因为CSS的transform中后一个变换函数是基于前一个变换后的新transform坐标系。假如某张图片的动画样式是这样的:rotate(90deg) translate(0px, 100px) rotate(-90deg) 直觉告诉我应该是旋转着向下移动100px,然而在CSS中的元素是这么运动的:
rotate和translate决定了最终的位置和运动轨迹,至于第二个rotate作用,只是叠加rotate的值作为最终的旋转弧度,这里刚好为0也就是不旋转。那么在iOS中该如何实现相似的运动轨迹呢?可以利用UIBezierPath, CAKeyframeAnimation的属性path可以指定这个UIBezierPath为动画的运动轨迹。确定起点和实际终点作为贝塞尔曲线的起始点和终止点,那么如何确定控制点?好像可以将“预想”的终点(下图中的(0,-1))作为控制点。
图6 将“预想”的终点作为控制点的贝塞尔曲线,看起来和CSS中的运动轨迹差不多
扩展问题
通过文章中描述的方式生成的贝塞尔曲线是否与CSS中的动画轨迹完全一致呢?
现在可以给视图添加动画了:
- let layer = CALayer()
- layer.frame = bounds
- layer.contents = image.cgImage
- self.layer.addSublayer(layer)
- let centerX = Double(layer.position.x)
- let centerY = Double(layer.position.y)
- let radian1 = Double.pi / 12 * Double.random(in: -0.5..<0.5)
- let radian2 = Double.pi / 12 * Double.random(in: -0.5..<0.5)
- let random = Double.pi * 2 * Double.random(in: -0.5..<0.5)
- let transX = 60 * cos(random)
- let transY = 30 * sin(random)
- //1:
- // x' = x*cos(rad) - y*sin(rad)
- // y' = y*cos(rad) + x*sin(rad)
- let realTransX = transX * cos(radian1) - transY * sin(radian1)
- let realTransY = transY * cos(radian1) + transX * sin(radian1)
- let realEndPoint = CGPoint(x: centerX + realTransX, y: centerY + realTransY)
- let controlPoint = CGPoint(x: centerX + transX, y: centerY + transY)
- //2:
- let movePath = UIBezierPath()
- movePath.move(to: layer.position)
- movePath.addQuadCurve(to: realEndPoint, controlPoint: controlPoint)
- let moveAnimation = CAKeyframeAnimation(keyPath: "position")
- moveAnimation.path = movePath.cgPath
- moveAnimation.calculationMode = .paced
- //3:
- let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation")
- rotateAnimation.toValue = radian1 + radian2
- let fadeOutAnimation = CABasicAnimation(keyPath: "opacity")
- fadeOutAnimation.toValue = 0.0
- let animationGroup = CAAnimationGroup()
- animationGroup.animations = [moveAnimation, rotateAnimation, fadeOutAnimation]
- animationGroup.duration = 1
- //4:
- animationGroup.beginTime = CACurrentMediaTime() + 1.35 * Double(i) / Double(imagesCount)
- animationGroup.isRemovedOnCompletion = false
- animationGroup.fillMode = .forwards
- layer.add(animationGroup, forKey: nil)
结尾
到这里,谷歌灭霸彩蛋中较复杂的技术点均已实现。如果您感兴趣,完整的代码(包含音效和复原动画)可以通过文章开头的链接进行查看,可以尝试将沙化图片的数量从32提高至更多,效果会越好,内存也会消耗更多 :-D。
不知不觉中微信已经成为聚合各种功能的超级平台,我们用微信来支付购物费用、来...
1月22日消息,据外媒报道,谷歌云宣布推出一套全新的解决方案,旨在帮助全球零售...
2020年即将走向尾声,疫情并未阻挡科技前进的脚步,量子计算、基础材料、生物医...
据backendnews报道,Oracle公司与市场研究公司Wakefield Research和总部位于纽约...
本文转载自网络,原文链接:https://www.sohu.com/a/444257473_422657?spm=smpc....
1.越是对你发誓的人,就越是在骗你,因为真相是不需要佐证的。同样道理,越是疯...
本文转载自公众号读芯术(ID:AI_Discovery) 在过去的几周里,笔者一直在和最喜欢...
到了 2021 年,人们普遍认为,通过学习算法和人工智能研究,机器在很多方面都优...
为做好疫情防控工作,阻断疫情蔓延,工业和信息化部科技司 发布《充分发挥人工智...
如今,人工智能技术持续快速发展,在图像识别、语音识别、语义理解等诸多特定领...