2016.11.30更新

最近在看 Raywenderlich 的动画书,碰巧看到了一样的转场动画。我新写了一篇文章介绍正常的做法。

然后根据这段时间的积累,我优化了一下之前的代码,具体请看看正文部分。


动画效果

花瓣APP的转场动画比较有意思,我第一次看到就喜欢上了,为了美化自己毕业设计的游戏APP,我模仿了这个动画效果,花瓣APP的动画效果是这样的:

这是我自己模仿的动画效果:

其实效果还可以,不流畅是gif图的问题。

实现思路

我的思路是点击视图时给该视图截图,生成UIImage,然后在0.4秒的时间内把这个图片放大到全屏幕(即转场后的视图的初始模样),动画效果结束的回调中放置真正的转场逻辑。

我的思路就是这样,感觉比较简单,有个问题是我截图之后图片放大材质会失真,转场后的场景是Spritekit的GameScene,重新加载了材质,就清晰很多,在它们衔接的时候会明显感觉到材质变清晰了,这个问题我没有去解决它,可能因为我比较粗的原因。不过回退的转场动画是从第二个视图截的图,不会有材质失真的问题。
Spritekit场景下截图参考了这篇文章

附上核心代码

UIView截图

1
2
3
4
5
6
//UIGraphicsBeginImageContext(self.previewZone.bounds.size) // 低清晰度
UIGraphicsBeginImageContextWithOptions(self.previewZone.bounds.size, false, 0.0) // 高清晰度
self.previewZone.layer.renderInContext(UIGraphicsGetCurrentContext()!)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
self.superView.imageDic[self.fileName!] = image

动画效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//topPanel是gif中顶部的黑色View,image就是存放截图的UIImageView
self.view.addSubview(topPanel)
self.view.addSubview(image)
UIApplication.sharedApplication().beginIgnoringInteractionEvents()
UIView.animateWithDuration(0.4, animations: {
image.frame = CGRect(x: 0, y: 64, width: 1024, height: 704)
topPanel.frame = CGRect(x: 0, y: 0, width: 1024, height: 64)
topPanel.alpha = 1
}) { (res) in
image.removeFromSuperview()
topPanel.removeFromSuperview()
self.navigationController?.pushViewController(vc, animated: false)
UIApplication.sharedApplication().endIgnoringInteractionEvents()
}

动画部分是可以优化的,用变形(transform)替代尺寸(frame)可以让细节更加平滑一些

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//topPanel是gif中顶部的黑色View,image就是存放截图的UIImageView,尺寸为最终全屏时的尺寸
image.transform = CGAffineTransform(scaleX: imageCurrentFrame.size.width / screenWidth, y: imageCurrentFrame.size.height / screenHeight)
image.center = CGPoint(x: imageCurrentFrame.midX, y: imageCurrentFrame.midY)
topPanel.transform = CGAffineTransform(scaleX: panelCurrentFrame.size.width / screenWidth, y: panelCurrentFrame.size.height / screenHeight)
topPanel.center = CGPoint(x: panelCurrentFrame.midX, y: panelCurrentFrame.midY)
self.view.addSubview(topPanel)
self.view.addSubview(image)
UIApplication.sharedApplication().beginIgnoringInteractionEvents()
UIView.animateWithDuration(0.4, animations: {
image.transform = CGAffineTransform.identity
image.center = CGPoint(x: imageFinalFrame.midX, y: imageFinalFrame.midY)
topPanel.transform = CGAffineTransform.identity
topPanel.center = CGPoint(x: panelFinalFrame.midX, y: panelFinalFrame.midY)
topPanel.alpha = 1
}) { (res) in
image.removeFromSuperview()
topPanel.removeFromSuperview()
self.navigationController?.pushViewController(vc, animated: false)
UIApplication.sharedApplication().endIgnoringInteractionEvents()
}

Spritekit场景中截图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
func imageFromSKNode(node: SKNode) -> UIImage {
let view = node.scene?.view
let scale = UIScreen.mainScreen().scale
let nodeFrame = node.calculateAccumulatedFrame()
UIGraphicsBeginImageContextWithOptions(view!.bounds.size, true, 0)
view?.drawViewHierarchyInRect(view!.bounds, afterScreenUpdates: true)
let sceneSnapshot = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
let originY = sceneSnapshot.size.height*scale - nodeFrame.origin.y*scale - nodeFrame.size.height*scale
let cropRect = CGRect(x: node.frame.origin.x * scale,
y: originY,
width: node.frame.size.width * scale,
height: node.frame.size.height * scale)
let croppedSnapshot = CGImageCreateWithImageInRect(sceneSnapshot.CGImage, cropRect)
let nodeSnapshot = UIImage(CGImage: croppedSnapshot!)
//retain屏幕的rect
let resRect = CGRect(x: 0, y: 128, width: 2048, height: 1408)
let resCGSnapshot = CGImageCreateWithImageInRect(nodeSnapshot.CGImage, resRect)
let resSnapshot = UIImage(CGImage: resCGSnapshot!)
return resSnapshot
}