问道 Motion Graphics

缘起

这两天和 Ray 一起吃拉面的时候,除了关注那位可爱服务员之外,他还煞有介事的跟我讲 —— “我发现了一个超屌的设计师,balabala”,其实时至今日我已经不知道如何定义超屌的设计师这个概念了,就像超屌的开发者一样,这个问题探究到最后只会变成一句感叹——人类最屌竟然只能做成这个样子。

不过,还是捧捧 Ray 的场啦,我问道 “是何许人也?”

他拿过来几乎没有信号的 iPad 给我看,你看这个人,这个人,目光从 Safari 地址栏的 .cn 这个坑爹的后缀扫过后,赫然发现一个完全不知道的名字 Marcus Eckert 虽然不知道名字,但是接下来的内容绝对是如雷贯耳——Wide Sky

这段留着你来点开上面的两个链接欣赏下。

个人英雄主义如果不是个人能力超强的话,往往会变成一个自我重伤的事情,但是很显然 Marcus Eckert 把这一点发挥到了极致, Meek for iOS 太震撼了。 所以按捺不住心中的激动,我对 Motion Graphics 展开了一番问道。

##寻路

Motion Graphics 这个词我不久前第一次听说,当时是一米天给 CatchChat 做介绍动画的时候,跟我说他们是高大上的 MG,不过我的理解是怎么现在搞啥都得发明个名词来方便忽悠了,不过只要是好东西做做忽悠也没啥,但是此事也就这样没放在心上。

这次我借机好好研究了一下。

这里正好有一段关于 MG 是啥的 Youtube 视频,应该是某个专业做 MG 的团队出品的,质量一般般,不过我觉得是体现了绝大部分从业者对这个的理解。

所以我想了想,之前我应该也做过一个类似的东西,之前研究 Facebook POP 的时候,有个 Transform 的部分 正是异曲同工。

但是说实在的,探究 Motion Graphics 的过程是不愉快的,因为看得越多,越发现了大家在用那么几种相似的变换形式来来回折腾。这种体验相似于其它的行业,最顶尖的天才研究出来一些新式的效果,其他人抄袭个改进。但是想想如果人类最顶尖的想象力和效果就是这样了,还是很伤心的。

##问道

Motion Graphics 在 AE 里做应该是相对简单的,不过 Marcus Eckert 用代码实现了这些效果,就让我很感动了,他自己也说,其实对他而言更多的是技术上的实验,所以我也思考了下如何从技术上实现这些效果。

Actually,动效无论是软件还是在动画里,用多了其实都会让人吐的。

好好,先聊聊 iOS 里怎么实现动效。

iOS 里的动效初探

简单的飞来飞去,大小改变什么的就很简单了,聊聊复杂的形变问题。

还好我之前玩过 Blender,我的思路第一个就是对 Bezier 曲线进行改变,这个就比较类似关键帧动画。

比如我们如何把多边形进行变形

先画五边形

self.aPath = [UIBezierPath bezierPath];
// Set the starting point of the shape.
[self.aPath moveToPoint:CGPointMake(100.0, 0.0)];
// Draw the path.
[self.aPath addLineToPoint:CGPointMake(200.0, 40.0)];
[self.aPath addLineToPoint:CGPointMake(160, 140)];
[self.aPath addLineToPoint:CGPointMake(40.0, 140)];
[self.aPath addLineToPoint:CGPointMake(0.0, 40.0)];
[self.aPath closePath];

然后再搞一个变形后的曲线

//B path
self.bPath = [UIBezierPath bezierPath];
// Set the starting point of the shape.
[self.bPath moveToPoint:CGPointMake(0.0, 0.0)];
// Draw the lines.
[self.bPath addLineToPoint:CGPointMake(100.0, 40.0)];
[self.bPath addLineToPoint:CGPointMake(120, 140)];
[self.bPath addLineToPoint:CGPointMake(-10.0, 100.0)];
[self.bPath addLineToPoint:CGPointMake(0.0, 40.0)];
[self.bPath closePath];

那么先画出 A path

CAShapeLayer *shape = [CAShapeLayer layer];
shape.drawsAsynchronously = YES;
shape.frame = self.view.bounds;
shape.path = self.aPath.CGPath;
shape.lineWidth = 3.0f;
shape.lineCap = kCALineCapRound;
shape.lineJoin = kCALineJoinRound;
shape.strokeColor = [UIColor whiteColor].CGColor;
shape.fillColor = [UIColor redColor].CGColor;
[self.view.layer addSublayer:shape];

接下来要做的就是加入个动画让 A path 变成 B path

CABasicAnimation * pathAnimation = [CABasicAnimation animationWithKeyPath:@"path"];
pathAnimation.fromValue = (id)[self.aPath CGPath];
pathAnimation.toValue = (id)[self.bPath CGPath];
pathAnimation.duration = 0.5f;
pathAnimation.autoreverses = NO;
pathAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
[shape addAnimation:pathAnimation forKey:@"animationKey"];
shape.path = self.bPath.CGPath;

Build Run 一下就可以发生这个渐变动画了,瞬间有小学时候玩 Flash 的感觉有木有。 timingFunction 可以经过复杂的定义实现一个比较生动的动画。

通过 addQuadCurveToPoint 这个也可以实现控制点方式对形状进行控制,也可以写一个函数进行 Path 的 Smooth 的操作。这方面我们已经有先行者做了尝试 https://github.com/Petrakeas/outlineDEMO

同时我也给 PNChart 0.6 版本加入了一个实时更新数据的功能,用的就是这个方法。

###动效物理效果进阶

之后我又发现了一位先驱

这效果看起来非常高大上,但是知道原理后又不是那么神奇。

[self.bPath moveToPoint:CGPointMake(0.0, 0.0)];
[self.bPath addQuadCurveToPoint:CGPointMake(0.0, ScreenHeight)
controlPoint:CGPointMake(100.0, ScreenHeight/2.0)];

通过 addQuadCurveToPoint 增加控制点,然后添加一个 Dummy 的 UIView 跟随控制点,在释放的时候给 Dummy 一个 Spring 的 Animation 即可,或者使用 UIDynamic 增加一个高空坠落的效果。使用 CADisplayLink 同步 Path 和 Dummy View 的数值。

在 Ray 提到波纹没有像 Android L 里面那样跟手之后,我想到确实有个可以改进的地方,就是在移动手指的时候 controlPoint 的 Y 坐标可以跟随手的位置进行变动,这样波纹的尖端就会和手一直。

###动效果冻效果

那么随着进一步探索,又找到一位复制 Skype 的果冻菜单效果的先驱。

实现的原理也是一样的,不过是创建两个 Dummy View ,你可以研究下这篇文章。

后来作者创建了一个独立的库 https://github.com/fastred/AHKBendableView

有兴趣的话可以研究下,实现的方式就更加复杂了,不过原理还是一样的。

let path = UIBezierPath()
path.moveToPoint(CGPoint(x: 0, y: 0))
path.addQuadCurveToPoint(CGPoint(x: width, y: 0),
controlPoint:CGPoint(x: width / 2.0, y: 0 + bendableOffset.vertical))
path.addQuadCurveToPoint(CGPoint(x: width, y: height),
controlPoint:CGPoint(x: width + bendableOffset.horizontal, y: height / 2.0))
path.addQuadCurveToPoint(CGPoint(x: 0, y: height),
controlPoint: CGPoint(x: width / 2.0, y: height + bendableOffset.vertical))
path.addQuadCurveToPoint(CGPoint(x: 0, y: 0),
controlPoint: CGPoint(x: bendableOffset.horizontal, y: height / 2.0))
path.closePath()

使用 CADisplayLink 同步 bendableOffset 和 Dummy View 的移动。

##再续前缘

研究到这里,回想 Marcus Eckert 的工作,觉得越发碉堡,如果要在三维空间实现这些效果,那么还要再去搞 OpenGL,不禁为此觉得潸然泪下,无论是工程师还是设计师,局限于自己的领域是无法做出极具创造性的工作的。

望着窗外广州大道的车灯,被雨夜的风吹的更清醒一点了。