/ Ios

问道 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,不禁为此觉得潸然泪下,无论是工程师还是设计师,局限于自己的领域是无法做出极具创造性的工作的。

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