网站上职业学校排名 该怎么做,龙口网站建设公司哪家好,企业内网,怒江企业网站建设启动画面(Splash Screen)——不但给开发者们提供了一个尽情发挥、创建有趣动画的机会#xff0c;也填补了App启动时从终端慢吞吞地下载数据的时间。启动画面(动态的)对于App至关重要#xff1a;它可以让用户不失兴趣地耐心等待应用完成加载。尽管现在的启动画面多种多样…启动画面(Splash Screen)——不但给开发者们提供了一个尽情发挥、创建有趣动画的机会也填补了App启动时从终端慢吞吞地下载数据的时间。启动画面(动态的)对于App至关重要它可以让用户不失兴趣地耐心等待应用完成加载。尽管现在的启动画面多种多样但很少有像Uber这般精美的。2016年第一季度Uber的CEO发表了关于重塑品牌的策略其中之一就是现在这个超酷的启动画面。这篇教程的目的是尽可能真实地再现Uber的动画。我们会大量地使用到CALayers、CAAnimations以及它们的子类。我不会从头介绍这些类的基本概念而是把重点放在如何应用这些类创建高质量的动画。如果你想要了解动画背后的基本原理可以参考Marin Todorove的iOS动画中级教程。入门由于有非常多的动画要实现我们不妨在这个初始项目的基础上进行修改。初始项目里已经为你创建好了所有需要的CALayer我们给它们添加动画即可。译者注为了保持教程简洁删除了原文里一些与教学无关的文字。如有兴趣可通过文章最后的链接阅读原文相关内容。先来看一眼最终效果打开初始项目看看里面的文件。从控制器的角度分析项目中的SplashViewController通过它的父视图控制器RootContainerViewController生成。SplashViewController会不停循环播放启动动画直到App完全加载完成即与终端API握手成功并获取了必要的数据。值得一提的是在这个示例项目里启动动画抽象成了一个单独的模块(译者注可以直接集成到其他项目里)。RootContainerViewController里有两个方法showSplashViewController()和showSplashViewControllerNoPing()。我们主要使用第二个方法它只会不停循环播放动画(不会进入主界面)便于我们把精力集中在SplashViewController的子视图上。当然最后我们还是会切换回第一个方法模拟API延迟并过渡到主界面。启动画面的视图和层SplashViewController包含了两个子视图。其一是“波纹格子”背景我们把它叫做TileGridView 由一系列TileView组成。另一个是带有动画效果的“U”字Logo我们把它叫做AnimatedULogoView。AnimatedULogoView里有4个CAShapeLayercircleLayer“U”型Logo的圆形白色背景lineLayercircleLayer中心到边界的一条直线squareLayercircleLayer中心位置的正方形maskLayer遮罩层当它的边界随着动画改变的时会遮盖其他层这些CAShapeLayer组合在一起构成了Fuber标志性的“U”。既然已经知道这些层的组合方式那我们可以开始创建动画让AnimatedULogoView动起来了。圆形的动画制作动画的时候最好过滤掉其他视觉“噪音”只关注当前实现的动画。打开AnimatedULogoView.swift在init(frame:)方法里把除了cricleLayer的其他层全都注释掉实现完动画后我们会重新添加回来。注释后的代码应该像下面一样override init(frame: CGRect) { super.init(frame: frame)circleLayer generateCircleLayer()lineLayer generateLineLayer()squareLayer generateSquareLayer()maskLayer generateMaskLayer() // layer.mask maskLayerlayer.addSublayer(circleLayer) // layer.addSublayer(lineLayer)// layer.addSublayer(squareLayer)}定位到generateCricleLayer()方法试着理解一下这里的圆是怎么绘制的。其实它只不过是用UIBezierPath绘制的一个CAShapeLayer。注意这一行layer.path UIBezierPath(arcCenter: CGPointZero, radius: radius/2, startAngle: -CGFloat(M_PI_2), endAngle: CGFloat(3*M_PI_2), clockwise: true).CGPath默认情况下如果你把startAngle设置为0圆弧会从右侧开始绘制(3点钟方向)。如果设置为-M_PI_2也就是-90°的话则会从上方开始绘制endAngle最终是270°或者说3*M_PI_2同样也是圆的正上方。另外要注意的是在这里我们把弧线的宽度lineWidth设置为圆的半径radius因为我们想让它动起来(画圆的过程)。circleLayer的动画是由三个CAAnimation组成的一个描绘笔端动画的CAKeyframeAnimation一个进行图形变换的CABasicAnimation以及一个CAAnimationGroup把它们合成在一起。定位到animateCricleLayer()添加下面代码// 笔画变化的动画let strokeEndAnimation CAKeyframeAnimation(keyPath: strokeEnd)strokeEndAnimation.timingFunction strokeEndTimingFunctionstrokeEndAnimation.duration kAnimationDuration - kAnimationDurationDelaystrokeEndAnimation.values [0.0, 1.0]strokeEndAnimation.keyTimes [0.0, 1.0]通过把动画的values设置为0.0和1.0我们告诉Core Animation从startAngle开始到endAngle结束创建像时钟一样的动画。随社storkeEnd的值变大沿着周长的弧线长度也逐渐增加整个圆逐渐被填满。对于这个例子如果你把values改为[0.0, 0.5]那么整个动画只会填满半个圆。接着添加形变动画let transformAnimation CABasicAnimation(keyPath: transform)transformAnimation.timingFunction strokeEndTimingFunctiontransformAnimation.duration kAnimationDuration - kAnimationDurationDelay// 旋转放大的动画// 起始时逆时针旋转45°x、y为正常大小的0.25倍var startingTransform CATransform3DMakeRotation(-CGFloat(M_PI_4), 0, 0, 1)startingTransform CATransform3DScale(startingTransform, 0.25, 0.25, 1)transformAnimation.fromValue NSValue(CATransform3D: startingTransform)transformAnimation.toValue NSValue(CATransform3D: CATransform3DIdentity)这个动画既包括了图形的缩放也包括了沿z轴的旋转。其结果是circleLayer在顺时针旋转45°的同时逐渐放大。这里旋转的参数设置非常重要因为和其它层的动画组合的时候它需要和lineLayer的位置及速度相匹配。最后在方法的末尾添加一个CAAnimationGroup它负责把前面两个动画合成在一起这样你只需给cricleLayer添加一个动画即可。// 把两个动画合成let groupAnimation CAAnimationGroup()groupAnimation.animations [strokeEndAnimation, transformAnimation]groupAnimation.repeatCount Float.infinity // 无限重复动画groupAnimation.duration kAnimationDurationgroupAnimation.beginTime beginTimegroupAnimation.timeOffset startTimeOffsetcircleLayer.addAnimation(groupAnimation, forKey: looping)CAAnimationGroup设定了两个重要的属性beginTime和timeOffset如果你对它们不熟悉的话可以参考这篇文章里面有这两个属性的描述以及用法。这里的groupAnimation的beginTime属性是根据父视图的时间设定的。timeOffset在这里也需要设定因为这个动画在第一次运行的时候实际上是从中途开始的。当我们完成更多动画时你可以回到这里尝试改变startTimeOffset的值并观察效果的差别。把groupAnimation添加给circleLayer编译运行一下看看目前的效果提示试着删除strokeEndAnimation或者transformAnimation看看单独的每一个动画是什么样的。在这篇教程里你可以尝试不同动画的效果。你可能会惊奇地发现不同是动画组合竟能创建出如此意想不到的独特视觉效果。直线的动画现在我们已经完成circleLayer的动画了该开始说说lineLayer了。还是在AnimatedULogoView.swift里定位到startAnimating()把除了animateLineLayer()以外的方法全部注释掉。注释完的代码应该如下面所示public func startAnimating() {beginTime CACurrentMediaTime()layer.anchorPoint CGPointZero// animateMaskLayer()// animateCircleLayer()animateLineLayer() // animateSquareLayer()}此外还需要调整一下init(frame:)只显示circleLayer和lineLayeroverride init(frame: CGRect) { super.init(frame: frame)circleLayer generateCircleLayer()lineLayer generateLineLayer()squareLayer generateSquareLayer()maskLayer generateMaskLayer() // layer.mask maskLayerlayer.addSublayer(circleLayer)layer.addSublayer(lineLayer) // layer.addSublayer(squareLayer)}注释完毕定位到animateLineLayer()方法实现下一组动画效果// 线段宽度动画let lineWidthAnimation CAKeyframeAnimation(keyPath: lineWidth)lineWidthAnimation.values [0.0, 5.0, 0.0]lineWidthAnimation.timingFunctions [strokeEndTimingFunction, circleLayerTimingFunction]lineWidthAnimation.duration kAnimationDurationlineWidthAnimation.keyTimes [0.0, 1.0 - kAnimationDurationDelay/kAnimationDuration, 1.0]这个动画会先增加lineLayer的宽度随后变回来。添加下面代码实现下一个动画// 变形let transformAnimation CAKeyframeAnimation(keyPath: transform)transformAnimation.timingFunctions [strokeEndTimingFunction, circleLayerTimingFunction]transformAnimation.duration kAnimationDurationtransformAnimation.keyTimes [0.0, 1.0 - kAnimationDurationDelay/kAnimationDuration, 1.0]// 和之前一样的旋转放大动画var transform CATransform3DMakeRotation(-CGFloat(M_PI_4), 0.0, 0.0, 1.0)transform CATransform3DScale(transform, 0.25, 0.25, 1.0)// 先放大再缩小transformAnimation.values [NSValue(CATransform3D: transform), NSValue(CATransform3D: CATransform3DIdentity), NSValue(CATransform3D: CATransform3DMakeScale(0.15, 0.15, 1.0))]和circleLayer的动画非常相似我们在这里也定义了一个沿z轴顺时针旋转的动画。在这里我们同样对线条定义了一个缩放动画从原始大小的25%开始先变为原始大小紧接着变为原始大小的15%。用CAAnimationGroup把它们合成到一起添加到lineLayer里// 合成动画let groupAnimation CAAnimationGroup()groupAnimation.repeatCount Float.infinitygroupAnimation.removedOnCompletion falsegroupAnimation.duration kAnimationDurationgroupAnimation.beginTime beginTimegroupAnimation.animations [lineWidthAnimation, transformAnimation]groupAnimation.timeOffset startTimeOffsetlineLayer.addAnimation(groupAnimation, forKey: looping)编译运行观察一下效果。注意在这里我们把线条的初始位置设置为了-M_PI_4同时把keyTimes设置为了[0.0, 1.0 - kAnimationDurationDelay/kAnimationDuration, 1.0]。数组的第一个和最后一个元素显而易见0.0代表起始1.0代表终止。为了得到中间时间点我们需要计算出圆形动画完成、后半部分动画开始(缩小的动画)的时间。用kAnimationDurationDelay除以kAnimationDuration可以得到我们需要的结果。但因为它是个延迟动画所以我们应该用1.0减去它我们需要从末尾往前倒减去延迟时间。现在我们已经完成了circleLayer和lineLayer的动画了接下来该处理中间的方形了。方形的动画现在你应该已经轻车熟路了。定位到startAnimation()方法注释掉animateSquareLayer()以外的方法。并把init(frame:)方法修改成下面这样override init(frame: CGRect) { super.init(frame: frame)circleLayer generateCircleLayer()lineLayer generateLineLayer()squareLayer generateSquareLayer()maskLayer generateMaskLayer() // layer.mask maskLayerlayer.addSublayer(circleLayer) // layer.addSublayer(lineLayer)layer.addSublayer(squareLayer)}修改完前往animateSquareLayer()开始解决下一个动画// 边框let b1 NSValue(CGRect: CGRect(x: 0.0, y: 0.0, width: 2.0/3.0 * squareLayerLength, height: 2.0/3.0 * squareLayerLength))let b2 NSValue(CGRect: CGRect(x: 0.0, y: 0.0, width: squareLayerLength, height: squareLayerLength))let b3 NSValue(CGRect: CGRectZero)// 边框从原始长度的2/3开始放大到原始大小后再逐渐缩小到0let boundsAnimation CAKeyframeAnimation(keyPath: bounds)boundsAnimation.values [b1, b2, b3]boundsAnimation.timingFunctions [fadeInSquareTimingFunction, squareLayerTimingFunction]boundsAnimation.duration kAnimationDurationboundsAnimation.keyTimes [0, 1.0-kAnimationDurationDelay/kAnimationDuration, 1.0]上面的动画改变了CALayer的边框。我们创建了一个关键帧动画从边长的2/3开始放大到完整尺寸再缩小到0。接下来是背景颜色的动画// 背景颜色的变化let backgroundColorAnimation CABasicAnimation(keyPath: backgroundColor)backgroundColorAnimation.fromValue UIColor.whiteColor().CGColorbackgroundColorAnimation.toValue UIColor.fuberBlue().CGColorbackgroundColorAnimation.timingFunction squareLayerTimingFunctionbackgroundColorAnimation.fillMode kCAFillModeBothbackgroundColorAnimation.beginTime kAnimationDurationDelay * 2.0 / kAnimationDurationbackgroundColorAnimation.duration kAnimationDuration / (kAnimationDuration - kAnimationDurationDelay)注意这里的fillMode属性。由于beginTime不是零动画会把开始和结束时的CGColor包含进去。因此当我们把动画添加到父CAAnimationGroup里的时候不会闪现不同颜色。说到CAAnimationGroup该实现它了// 合成动画let groupAnimation CAAnimationGroup()groupAnimation.animations [boundsAnimation, backgroundColorAnimation]groupAnimation.repeatCount Float.infinitygroupAnimation.duration kAnimationDurationgroupAnimation.removedOnCompletion falsegroupAnimation.beginTime beginTimegroupAnimation.timeOffset startTimeOffsetsquareLayer.addAnimation(groupAnimation, forKey: looping)编译运行检查一下我们的进度嗯看来方形动画已经顺利完成了。是时候把之前的动画合并到一起看看效果了提示在iOS模拟器里的动画可能会有些卡顿因为我们需要在Mac上模拟平时由iOS的GPU完成的工作。如果你的电脑不能流畅运行动画试着缩小模拟器的屏幕大小或者在真机上测试。遮罩层首先取消init(frame:)和startAnimating()里所有注释。把所有动画组合到一起我们重新编译运行一下Fuber。看起来好像还是差点意思cricleLayer的缩小消失太过突然。幸运的是遮罩层动画可以修正这个问题让它平滑地缩小。定位到animateMaskLayer()添加下面的代码// 边框缩小let boundsAnimation CABasicAnimation(keyPath: bounds)boundsAnimation.fromValue NSValue(CGRect: CGRect(x: 0.0, y: 0.0, width: radius * 2.0, height: radius * 2))boundsAnimation.toValue NSValue(CGRect: CGRect(x: 0.0, y: 0.0, width: 2.0/3.0 * squareLayerLength, height: 2.0/3.0 * squareLayerLength))boundsAnimation.duration kAnimationDurationDelayboundsAnimation.beginTime kAnimationDuration - kAnimationDurationDelayboundsAnimation.timingFunction circleLayerTimingFunction上面的代码用于设定遮罩层边界动画。别忘了当遮罩层的边界改变时整个AnimatedULogoView都会被遮挡因为它作用于所有子层。现在我们来实现圆角动画保持遮罩是圆形的// 边角弧度let cornerRadiusAnimation CABasicAnimation(keyPath: cornerRadius)cornerRadiusAnimation.beginTime kAnimationDuration - kAnimationDurationDelaycornerRadiusAnimation.duration kAnimationDurationDelaycornerRadiusAnimation.fromValue radiuscornerRadiusAnimation.toValue 2cornerRadiusAnimation.timingFunction circleLayerTimingFunction把这两个动画合成为一个CAAnimationGroup这个层就完成了// 合成动画let groupAnimation CAAnimationGroup()groupAnimation.removedOnCompletion falsegroupAnimation.fillMode kCAFillModeBothgroupAnimation.beginTime beginTimegroupAnimation.repeatCount Float.infinitygroupAnimation.duration kAnimationDurationgroupAnimation.animations [boundsAnimation, cornerRadiusAnimation]groupAnimation.timeOffset startTimeOffsetmaskLayer.addAnimation(groupAnimation, forKey: looping)编译运行。看起来不错教程的上半部分至此结束关于背景网格的水波效果会在下一篇教程中介绍。