亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 系統 > iOS > 正文

在iOS中實現谷歌滅霸彩蛋的完整示例

2020-07-26 02:20:03
字體:
來源:轉載
供稿:網友

前言

最近上映的復仇者聯盟4據說沒有片尾彩蛋,不過谷歌幫我們做了。只要在谷歌搜索滅霸,在結果的右側點擊無限手套,你將化身為滅霸,其中一半的搜索結果會化為灰燼消失...那么這么酷的動畫在iOS中可以實現嗎?答案是肯定的。整個動畫主要包含以下幾部分:響指動畫、沙化消失以及背景音效和復原動畫,讓我們分別來看看如何實現。

圖1 左為沙化動畫,右為復原動畫

響指動畫

Google的方法是利用了48幀合成的一張Sprite圖進行動畫的:

圖2 響指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..<frameCount {  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") }}

//1: masksToBounds = true和contentsGravity = CALayerContentsGravity.left是為了當前只顯示Sprite圖的第一幅畫面

//2: 根據Sprite圖大小和每幅畫面的大小計算出畫面數量,預先計算出每幅畫面的contentsRect.origin.x偏移量

//3: 這里是關鍵,指定關鍵幀動畫的calculationMode為discrete確保關鍵幀動畫依次使用values中指定的關鍵幀值進行變化,而不是默認情況下采用線性插值進行過渡,來個對比圖可能比較容易理解:

圖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個元素之一的相同位置。隨機方法有點特別,原始圖片左邊的像素只會分配到前幾張圖片,而原始圖片右邊的像素只會分配到后幾張。

圖4 上部分為原始圖片,下部分為像素分配后的32張圖片依次顯示效果

//3: 這里循環2次將像素分配兩次,可能 Google 覺得只分配一遍會造成像素比較稀疏。個人認為在移動端,只要一遍就好了。

//4: 創建32張圖片并返回

添加動畫

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中的元素是這么運動的:

圖5 CSS中transform多值動畫

第一個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)

//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/…

總結

以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,謝謝大家對武林網的支持。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
午夜精品久久久久久久99热| 欧美影院在线播放| 97香蕉久久超级碰碰高清版| 久久精品美女视频网站| 亚洲精品自在久久| 欧美日韩一区二区免费在线观看| 国产精品视频免费在线| 成人午夜在线影院| 亚洲男人av电影| 国产精品国产三级国产aⅴ9色| 日韩av免费在线| 久热精品在线视频| 国内精品久久久久伊人av| 欧美激情va永久在线播放| 亚洲国产美女精品久久久久∴| 久久综合色影院| 国产精品一区二区久久| 4444欧美成人kkkk| 日韩视频在线免费| 国产精品亚洲美女av网站| 亚洲无av在线中文字幕| 懂色aⅴ精品一区二区三区蜜月| 亚洲国产天堂久久国产91| 韩国日本不卡在线| 欧美噜噜久久久xxx| 中文日韩电影网站| 亚洲精品免费网站| 久久成人免费视频| 不卡毛片在线看| 国产精品7m视频| 国产精品亚洲激情| 久久不射热爱视频精品| 亚洲精品日产aⅴ| 欧美亚洲国产成人精品| 精品久久久香蕉免费精品视频| 日韩中文第一页| 亚洲国产精品久久久| 国产精品中文在线| 人九九综合九九宗合| 国产ts人妖一区二区三区| 俺去了亚洲欧美日韩| 久久久久久综合网天天| 91精品久久久久久久久青青| 国产精品视频一| 久久影视三级福利片| 久久国产精品偷| 欧美一级视频一区二区| 国产91色在线播放| 久久人人爽人人爽爽久久| 欧美日韩国产va另类| 欧美日韩在线免费| 国产午夜精品一区二区三区| 在线观看欧美成人| 国产成人精品优优av| 欧美在线欧美在线| 欧美日韩精品在线播放| 亚洲国产精品网站| 久久久久久综合网天天| 91系列在线播放| 国产精品白嫩初高中害羞小美女| 国产精品视频导航| 美女精品久久久| 久久综合伊人77777尤物| 国产精品日韩在线一区| 国产精品最新在线观看| 亚洲人成在线观看| 日韩精品免费看| 91色琪琪电影亚洲精品久久| 欧美激情欧美狂野欧美精品| 尤物精品国产第一福利三区| 最近2019中文字幕大全第二页| 欧美激情极品视频| 91禁外国网站| 亚洲人线精品午夜| 日本高清视频一区| 日韩国产高清污视频在线观看| 国产亚洲一区二区在线| 国产精品美女主播在线观看纯欲| 九九久久久久久久久激情| 欧美成人午夜影院| 欧美性猛交xxxx黑人| 亚洲欧美激情另类校园| 国产精品精品久久久久久| 精品色蜜蜜精品视频在线观看| 亚洲人午夜精品免费| 亚洲福利视频免费观看| 丝袜情趣国产精品| 欧美日韩精品在线播放| 2025国产精品视频| 国产精品久久久久久久久久久新郎| 日韩一区二区欧美| 国产精国产精品| 亚洲国产欧美一区二区三区久久| 亚洲国产美女久久久久| 中文字幕一精品亚洲无线一区| 都市激情亚洲色图| 久久久国产精品免费| 亚洲伊人第一页| 久久亚洲国产精品| 久久色免费在线视频| 亚洲精品v天堂中文字幕| 成人444kkkk在线观看| 亚洲老板91色精品久久| 亚洲第一页自拍| 8x海外华人永久免费日韩内陆视频| 久久亚洲综合国产精品99麻豆精品福利| 欧美一级片免费在线| 91在线精品视频| 美女久久久久久久久久久| 国产亚洲日本欧美韩国| 91精品中国老女人| 国产精品∨欧美精品v日韩精品| 国产国产精品人在线视| 欧美老女人性视频| 国产精品电影在线观看| 韩国视频理论视频久久| 久久亚洲精品视频| 欧美另类极品videosbest最新版本| 尤物九九久久国产精品的分类| 久久久91精品| 亚洲欧美国产一区二区三区| 国a精品视频大全| 日本中文字幕久久看| 欧美一区第一页| 久久久久久久久网站| 热久久免费国产视频| 97视频在线看| 国产一区二区三区在线观看视频| 久久久999精品视频| 久久久亚洲精选| 亚洲黄在线观看| 98午夜经典影视| 日韩中文有码在线视频| 国产精品久久久久久久久久ktv| 久久久久久有精品国产| 国产偷国产偷亚洲清高网站| 国产精品久久久久久久久影视| 国产精品高潮在线| 992tv成人免费影院| 久久人体大胆视频| 亚洲欧洲日产国码av系列天堂| 欧美一级黑人aaaaaaa做受| 亚洲女人初尝黑人巨大| 久久久伊人欧美| 一区二区三区www| 欧美剧在线观看| 国产欧美精品xxxx另类| 色偷偷偷亚洲综合网另类| 日韩中文字幕免费看| 国产精品高潮视频| 久久视频这里只有精品| 7777精品久久久久久| 亚洲精品一区中文| 国产亚洲精品日韩| 九九久久久久久久久激情| 在线日韩欧美视频| 亚洲美女又黄又爽在线观看| 亚洲人精品午夜在线观看| 最新亚洲国产精品| 亚洲在线www| 亚洲乱码国产乱码精品精| 91产国在线观看动作片喷水| 欧美在线视频免费播放| 欧美性做爰毛片|