推扬网

 找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
推扬网 门户 经验分享 查看内容

在iOS中实现谷歌灭霸彩蛋的完整示例

2020-4-11 13:54| 发布者: admin| 查看: 426| 评论: 0

这篇文章主要给大家介绍了关于如何在iOS中实现谷歌灭霸彩蛋的相关资料,文中通过示例代码介绍的非常详细,对各位iOS开发者们具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧

前言

最近上映的复仇者联盟4据说没有片尾彩蛋,不过谷歌帮我们做了。只要在谷歌搜索灭霸,在结果的右侧点击无限手套,你将化身为灭霸,其中一半的搜索结果会化为灰烬消失...那么这么酷的动画在iOS中可以实现吗?答案是肯定的。整个动画主要包含以下几部分:响指动画、沙化消失以及背景音效和复原动画,让我们分别来看看如何实现。

图1 左为沙化动画,右为复原动画

响指动画

Google的方法是利用了48帧合成的一张Sprite图进行动画的:

图3 左边为离散模式,右边为默认的线性模式

沙化消失

这个效果是整个动画较难的部分,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..<width {
  for row in 0..<height {
  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
 }
}

//1: 根据指定格式创建位图上下文,然后将输入的图片绘制上去之后获取其像素数据

//2: 创建像素二维数组,遍历输入图片每个像素,将其随机分配到数组32个元素之一的相同位置。随机方法有点特别,原始图片左边的像素只会分配到前几张图片,而原始图片右边的像素只会分配到后几张。

图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)

//1: 实际的偏移量旋转了radian1弧度,这个可以通过公式x' = x*cos(rad) - y*sin(rad), y' = y*cos(rad) + x*sin(rad)算出

//2: 创建UIBezierPath并关联到CAKeyframeAnimation中

//3: 两个弧度叠加作为最终的旋转弧度

//4: 设置CAAnimationGroup的开始时间,让每层Layer的动画延迟开始

结尾

到这里,谷歌灭霸彩蛋中较复杂的技术点均已实现。如果您感兴趣,完整的代码(包含音效和复原动画)可以通过文章开头的链接进行查看,可以尝试将沙化图片的数量从32提高至更多,效果会越好,内存也会消耗更多 :-D。

示例代码下载

参考资料

  • www.calayer.com/core-animat…
  • stackoverflow.com/questions/3…
  • weibo.com/1727858283/…

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对推扬网www.tuiyang.com的支持。


鲜花

握手

雷人

路过

鸡蛋

最新评论

精选推荐

    广告服务|投稿要求|禁言标准|版权说明|免责声明|手机版|小黑屋|推扬网 ( 粤ICP备18134897号 )|网站地图 | 邮箱:vayae@hotmail.com

    GMT+8, 2025-6-15 05:48 , Processed in 0.124569 second(s), 28 queries .

    Powered by Discuz! X3.4

    © 2001-2017 Comsenz Inc.

    返回顶部