动CAShapeLayer来实现环图片加载动画[译]

  • 原来文链接 : How To Implement A Circular Image Loader Animation
    with
    CAShapeLayer
  • 原稿作者 : Rounak
    Jain
  • 译文出自 : 开发技术前线
    www.devtf.cn
  • 译者 : Sam
    Lau
  • 校对者:
    Lollypo
  • 状态 : 校正完

差一点独星期日前,Michael Villar以Motion试验中开创一个挺有趣的加载动画。

脚的GIF图片展示是加载动画,它以一个圈进度指示器和环渐现动画组成。这个做的功效有趣,独一无二和微可爱。

图片 1

这科目将见面令你怎样行使Swift和Core
Animatoin来还创设是功能。让咱开吧!

基础

先是下充斥者课程的启动项目,然后编译和运作。过一样晤下,你应当看到一个简易的image显示:

图片 2

此启动项目已先在方便的职务将views和加载逻辑编写好了。花同样分钟来浏览来飞了解此类型;那里有一个ViewControllerViewController里出一个命名也CustomImageViewUIImageView子类,
还有一个SDWebImage的方法被调用来加载image。

汝可能注意到当你首先不善运行此app的时光,当image下载时这app似乎会停顿几秒,然后image会显示在屏幕。当然,此刻尚未环进度指示器

  • 你用会见当这个科目被开创它!

而会在有限单步骤中开创是动画:

  1. 周进度。首先,你见面画画一个圆形进度指示器,然后根据下载快来更新她。
  2. 扩张圆形图片。第二,你晤面通过扩大的周窗口来发布下充斥图片。

紧跟着下面步骤来慢慢落实!

始建圆形指示器

思转有关进度指示器的为主计划。这个指示器一开始是空来展示0%进度,然后逐步填充满直到image完成下载。通过设置CAShapeLayerpath呢circle来实现是相当简单。

留神:如果您莫熟悉CAShapeLayer(或CALayers)的基本概念,可以查看Scott
Gardner的CALayer in iOS with
Swift文章。

君得由此CAShapeLayerstrokeStartstrokeEnd性来控制初步跟竣工位置的外观。通过变更strokeEnd的值在0到1之内,你可恰当地填充下充斥进度。

为我们尝试一下。通过iOS\Source\Cocoa Touch Class
template
来创造一个初的文书,文件称也CircularLoaderView。设置它吧UIView的子类。

图片 3

点击NextCreate。新的子类UIView将故来保存动画的代码。

打开CircularLoaderView.swift跟补偿加以下属性和常量到之类似:

let circlePathLayer = CAShapeLayer()
let circleRadius: CGFloat = 20.0

circlePathLayer代表此圈子路径,而circleRadius意味着是圈子路径的半径。

增补加以下初始化代码到CircularLoaderView.swift来配置是shape layer:

override init(frame: CGRect) {
  super.init(frame: frame)
  configure()
}

required init(coder aDecoder: NSCoder) {
  super.init(coder: aDecoder)
  configure()
}

func configure() {
  circlePathLayer.frame = bounds
  circlePathLayer.lineWidth = 2
  circlePathLayer.fillColor = UIColor.clearColor().CGColor
  circlePathLayer.strokeColor = UIColor.redColor().CGColor
  layer.addSublayer(circlePathLayer)
  backgroundColor = UIColor.whiteColor()
}

简单独初始化方法都调用configure方法,configure措施设置一个shape
layer的line width为2,fill color为clear,stroke
color为red。将添加circlePathLayer添加到view’s main layer。然后设置view的
backgroundColor 为white,那么当image加载时,屏幕的其余部分就大意掉。

加上路线

若晤面小心到公还从来不赋值一个path给layer。为了完成及时点,添加以下方法(还是于CircularLoaderView.swift文件):

func circleFrame() -> CGRect {
  var circleFrame = CGRect(x: 0, y: 0, width: 2 * circleRadius, height: 2 * circleRadius)
  circleFrame.origin.x = CGRectGetMidX(circlePathLayer.bounds) - CGRectGetMidX(circleFrame)
  circleFrame.origin.y = CGRectGetMidY(circlePathLayer.bounds) - CGRectGetMidY(circleFrame)
  return circleFrame
}

地方很方式返回一个CGRect的实例来界定指示器的途径。这个边框是*2circleRadius宽和2circleRadius\*大,放在这view的正中心。

每次这view的size改变时,你晤面用还再度计算circleFrame,所以若可能用她身处一个独的道。

兹增长以下方式来创造而的不二法门:

func circlePath() -> UIBezierPath {
  return UIBezierPath(ovalInRect: circleFrame())
}

立就是基于circleFrame限定来回到圆形的UIBezierPath。由于circleFrame()返一个正好方形,在这种状态下”椭圆“会最后变成一个圈。

由于layers没有autoresizingMask夫特性,你用以layoutSubviews办法创新circlePathLayer的frame来恰当地响应view的size变化。

下一步,覆盖layoutSubviews()方法:

override func layoutSubviews() {
  super.layoutSubviews()
  circlePathLayer.frame = bounds
  circlePathLayer.path = circlePath().CGPath
}

由于改变了frame,你如果于此间调用circlePath()法来点重新计算路径。

今打开CustomImageView.swift文本以及添加以下CircularLoaderView实例作为一个性:

let progressIndicatorView = CircularLoaderView(frame: CGRectZero)

生一样步,在前下充斥图片的代码添加这几执代码到init(coder:)方法:

addSubview(self.progressIndicatorView)
progressIndicatorView.frame = bounds
progressIndicatorView.autoresizingMask = .FlexibleWidth | .FlexibleHeight

面代码添加进度指示器作为一个subview添加到于定义的image
view。autoresizingMask确保速度指示器view保持同image view的size一样。

编译和运行而的类;你晤面盼一个开门红底、空心的圆形出现,就比如这样:

图片 4

吓之 –
你都出速度指示器画在屏幕及。你的下一个职责就是根据下载速度变化来stroke。

修改Stroke长度

回到CircularLoaderView.swift文件及当此文件的别性能直接上加以下代码:

var progress: CGFloat {
  get {
    return circlePathLayer.strokeEnd
  }
  set {
    if (newValue > 1) {
      circlePathLayer.strokeEnd = 1
    } else if (newValue < 0) {
      circlePathLayer.strokeEnd = 0
    } else {
      circlePathLayer.strokeEnd = newValue
    }
  }
}

上述代码创建一个computed property – 也就算是一个性质没有外后背的变量

它们发出一个自定义的setter和getter。这个getter只是返circlePathLayer.strokeEnd,setter验证输入值要在0到1之间,然后恰当地设置layer的strokeEnd属性。

于首先破运行的时刻,添加底下这行代码到configure()来初始化进度:

progress = 0

编译和周转工程;除了一个空的屏幕,你应当什么吧从没看出。相信我,这是一个吓信息。设置progress为0,反过来会装strokeEnd呢为0,这就算表示shape
layer什么也没有写。

唯一剩下要开的就是是公的指示器在image下载回调方法被更新progress

回到CustomImageView.swift文件与用来下代码来替注释Update progress
here

self!.progressIndicatorView.progress = CGFloat(receivedSize)/CGFloat(expectedSize)

这重要通过receivedSize除以expectedSize来测算进度。

在意:你见面注意到block使用weak self引用 – 这样能避免retain cycle。

编译和运转而的工;你晤面看进度指示器像这样开始倒:

图片 5

即你自己从未长其它动画代码,CALayer当layer轻松地发现任何animatable属性和当属性改变时平滑地animate。

点都好第一单等级。现在进入次同最终阶段。

创建Reveal动画

reveal阶段于window显示image然后逐年扩大圆形环的形状。如果您曾经读了前面教程,那个教程主要谈创建一个Ping风格的view
controller动画,你尽管会知晓这是一个挺好的关于CALayermask性之动案例。

长以下方法及CircularLoaderView.swift文件:

func reveal() {

  // 1
  backgroundColor = UIColor.clearColor()
  progress = 1
  // 2
  circlePathLayer.removeAnimationForKey("strokeEnd")
  // 3
  circlePathLayer.removeFromSuperlayer()
  superview?.layer.mask = circlePathLayer
}

及时是一个深关键的主意需要掌握,让咱们逐段看无异全体:

  1. 设置view的背景色为clear,那么以view后面的image不再隐藏,然后设置progress为1或100%。

  2. 使用strokeEnd特性来移除任何待定的implicit
    animations,否则干扰reveal animation。关于implicit
    animations的重新多信息,请查看iOS Animations by
    Tutorials.

  3. 从它的superLayer移除circlePathLayer,然后赋值给superView的layer
    maks,借助circular mask
    “hole”,image是可见的。这样给你复用已是的layer和免重新代码。

现在若待以某地方调用reveal()。在CustomImageView.swift文件用以下代码替换Reveal
image here
注释:

self!.progressIndicatorView.reveal()

编译和运转而的app;一旦image开始下载,你会映入眼帘一片段小的ring在显示。

图片 6

若可知在背景看到而的image – 但几乎什么啊从没!

扩展环

君的产一致步就是是以前后扩展这个盘绕。你可以少只分别之、同轴心的UIBezierPath来就,但你啊得一个进一步有效的主意,只是下一个Bezier
path来完成。

何以做吗?你只是多到之半径(path特性)来为外扩张,同时多line的肥瘦(lineWidth性)来如果环更加厚和为外扩展。最终,两只价值都增长及足够时就以下面显示所有image。

回到CircularLoaderView.swift文件和增长以下代码到reveal()方法的终极:

// 1
let center = CGPoint(x: CGRectGetMidX(bounds), y: CGRectGetMidY(bounds))
let finalRadius = sqrt((center.x*center.x) + (center.y*center.y))
let radiusInset = finalRadius - circleRadius
let outerRect = CGRectInset(circleFrame(), -radiusInset, -radiusInset)
let toPath = UIBezierPath(ovalInRect: outerRect).CGPath

// 2
let fromPath = circlePathLayer.path
let fromLineWidth = circlePathLayer.lineWidth

// 3
CATransaction.begin()
CATransaction.setValue(kCFBooleanTrue, forKey: kCATransactionDisableActions)
circlePathLayer.lineWidth = 2*finalRadius
circlePathLayer.path = toPath
CATransaction.commit()

// 4
let lineWidthAnimation = CABasicAnimation(keyPath: "lineWidth")
lineWidthAnimation.fromValue = fromLineWidth
lineWidthAnimation.toValue = 2*finalRadius
let pathAnimation = CABasicAnimation(keyPath: "path")
pathAnimation.fromValue = fromPath
pathAnimation.toValue = toPath

// 5
let groupAnimation = CAAnimationGroup()
groupAnimation.duration = 1
groupAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
groupAnimation.animations = [pathAnimation, lineWidthAnimation]
groupAnimation.delegate = self
circlePathLayer.addAnimation(groupAnimation, forKey: "strokeWidth")

而今逐段解释以上代码是究竟做了什么:

  1. 确定圆形的半径之后就会全限制image
    view。然后计算CGRect来了限制这个圈子。toPath表示CAShapeLayer
    mask的最后形象。

  2. 设置lineWidthpath初始值来配合当前layer的值。

  3. 设置lineWidthpath的末段价值;这样能够预防其当动画就时过回其的原始值。CATransaction设置kCATransactionDisableActions键对应的价值为true来禁用layer的implicit
    animations。

  4. 创办一个个别单CABasicAnimation的实例,一个凡是路径动画,一个凡是lineWidth动画,lineWidth总得加及少倍及半径增长速度一样快,这样圆形向内扩展及为外扩张一样。

  5. 拿两个animations添加到一个CAAnimationGroup,然后上加animation
    group到layer。将self赋值给delegate,等下而会利用及她。

编译和周转而的工程;你会看如image完成下载,reveal
animation就见面弹出来。但即便reveal
animation完成,部分圆形或会维持以屏幕及。

图片 7

为修补这种场面,添加以下实现animationDidStop(_:finished:)
CircularLoaderView.swift

override func animationDidStop(anim: CAAnimation!, finished flag: Bool) {
  superview?.layer.mask = nil
}

这些代码从super layer上移除mask,这会完全地移除圆形。

还编译和运行而的工,和公见面盼任何动画的效能:

图片 8

恭喜你,你早已好创建圆形图像加载动画!

下一步

你可以当此间下载整个工程。

根据本学科,你可以更加来微调动画的时刻、曲线与颜料来满足你的要求及个体计划美学。一个恐需要改善就是安装shape
layer的lineCap属性值为kCALineCapRound来四放弃五适合圆形进度指示器的尾部。你协调琢磨还有什么得改善的地方。

假如您嗜是科目以及愿意学习怎样创造更多像这么的卡通,请查看Marin
Todorov的书iOS Animations by
Tutorials。它是起基本的动画片开始,然后慢慢讲解layer
animations, animating constraints, view controller transitions和重多

万一您出啊有关此科目的问题或者臧否,请在下面与座谈。我万分乐意看到您当您的App中加上这么深的动画片。