行使CAShapeLayer来实现圆形图片加载动画[www.316.net亚洲必赢译]

几个礼拜以前,迈克尔(Michael)(Michael) Villar在Motion试验中开创一个异常幽默的加载动画。

上面的GIF图片展示这么些加载动画,它将一个圆形进度指示器和圆形渐现动画组成。那多少个组成的机能有趣,独一无二和不怎么可爱。

www.316.net亚洲必赢 1

本条课程将会教你什么使用Swift(Swift)和Core
Animatoin来再次创建这一个效应。让大家起始吧!

基础

先是下载这一个科目标开行项目,然后编译和周转。过一会从此,你应有看到一个简单易行的image彰显:

www.316.net亚洲必赢 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的子类。

www.316.net亚洲必赢 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一样。

编译和运行你的体系;你相会到一个红的、空心的圈子出现,就像这么:

www.316.net亚洲必赢 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。

编译和周转你的工程;你会看出进度指示器像这样先导运动:

www.316.net亚洲必赢 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在体现。

www.316.net亚洲必赢 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完成,部分圆形依旧会保持在屏幕上。

www.316.net亚洲必赢 7

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

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

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

再度编译和运作你的工程,和您会合到所有动画的效益:

www.316.net亚洲必赢 8

恭贺您,你早已完成创立圆形图像加载动画!

下一步

你可以在此间下载整个工程

基于本学科,你可以更进一步来微调动画的岁月、曲线和颜料来满意你的需求和私家计划美学。一个也许需要改正就是安装shape
layer的lineCap属性值为kCALineCapRound来四舍五入圆形进度提示器的尾巴。你协调思考还有什么样可以改善的地点。

假若你欢喜那个课程和乐于上学怎么着成立更多像这样的动画片,请查看Marin
Todorov的书iOS Animations by
Tutorials
。它是从基本的卡通片开端,然后逐步讲解layer
animations, animating constraints, view controller transitions和更多

一经你有什么有关这些课程的题目或臧否,请在上面加入琢磨。我很情愿看到您在您的App中加上这么酷的动画。