創(chuàng)建自定義動畫按鈕(獨家翻譯)


layout: post
title: 創(chuàng)建自定義動畫按鈕
description: 獨家翻譯
image: assets/images/pic02.jpg



被追波設計的啟發(fā),我們將要去創(chuàng)造一個自定義的動畫按鈕。我希望通過這個教程,當說到去編碼動效,你能至少理解一些重要的步驟。

理解動效

首先,在我們打開Xcode,跟變換(transform),圖層(CALayer)的子類們等等玩耍之前,我們需要弄懂這個我們將要去構造實際動畫模型是什么,這是非常重要的。盯著這個模型看一會兒,然后思考你將提供說明來簡單的描述發(fā)生了什么。EZGIF提供一些非常好用,用于gif圖的工具,例如,改變動畫速度,然后在慢速度中查看動效。
下面的慢速度的圖可能有幫助:


讓我們開始打破動效圖層,然后命名他們來獲得一個更方便的引用:
1:star 2: Fill 3: Ring
1:star 2: Fill 3: Ring

好了,現(xiàn)在我們已經(jīng)標記了這些圖層,我們可以用幀分解來描述每一層的發(fā)生情況。下面是一個動效步驟的簡單描述:
1.星圖層和環(huán)圖層幾乎同步增長。
2.星圖層和環(huán)圖層停止增長,然后用比第一步更快的速度開始縮小到星圖層的中心。
3.填充圓從星圖層中心開始增長。
4.星圖層增長到初始大小,并且填充色改變了。
5.填充圓增長到步驟二里的大小,然后縮小到初始大小。

創(chuàng)建路徑

在iOS中CGPaths代表的路徑并在代碼中創(chuàng)建他們是一個痛苦而且耗時的任務。幸運的是的這里有個工具叫PaintCode使這個任務變得很容易。
所有你需要做的就是創(chuàng)建基于軟件Adobe lllustrator或者Sketch的矢量圖形,導出為SVG文件,導入PaintCode,然后PaintCode會提供你這個圖形OC和Swift的代碼。

這下面的代碼代表星圖層:

    var star = UIBezierPath()
    star.moveToPoint(CGPointMake(112.79, 119))
    star.addCurveToPoint(CGPointMake(107.75, 122.6),controlPoint1: CGPointMake(113.41, 122.8), controlPoint2:       CGPointMake(111.14, 124.42))
    star.addLineToPoint(CGPointMake(96.53, 116.58))
    star.addCurveToPoint(CGPointMake(84.14, 116.47), controlPoint1: CGPointMake(93.14, 114.76), controlPoint2: CGPointMake(87.56, 114.71))
    star.addLineToPoint(CGPointMake(72.82, 122.3))
    star.addCurveToPoint(CGPointMake(67.84, 118.62), controlPoint1: CGPointMake(69.4, 124.06), controlPoint2: CGPointMake(67.15, 122.41))
    star.addLineToPoint(CGPointMake(70.1, 106.09))
    star.addCurveToPoint(CGPointMake(66.37, 94.27), controlPoint1: CGPointMake(70.78, 102.3), controlPoint2: CGPointMake(69.1, 96.98))
    star.addLineToPoint(CGPointMake(57.33, 85.31))
    star.addCurveToPoint(CGPointMake(59.29, 79.43), controlPoint1: CGPointMake(54.6, 82.6), controlPoint2: CGPointMake(55.48, 79.95))
    star.addLineToPoint(CGPointMake(71.91, 77.71))
    star.addCurveToPoint(CGPointMake(81.99, 70.51), controlPoint1: CGPointMake(75.72, 77.19), controlPoint2: CGPointMake(80.26, 73.95))
    star.addLineToPoint(CGPointMake(87.72, 59.14))
    star.addCurveToPoint(CGPointMake(93.92, 59.2), controlPoint1: CGPointMake(89.46, 55.71), controlPoint2: CGPointMake(92.25, 55.73))
    star.addLineToPoint(CGPointMake(99.46, 70.66))
    star.addCurveToPoint(CGPointMake(109.42, 78.03), controlPoint1: CGPointMake(101.13, 74.13), controlPoint2: CGPointMake(105.62, 77.44))
    star.addLineToPoint(CGPointMake(122, 79.96))
    star.addCurveToPoint(CGPointMake(123.87, 85.87), controlPoint1: CGPointMake(125.81, 80.55), controlPoint2: CGPointMake(126.64, 83.21))
    star.addLineToPoint(CGPointMake(114.67, 94.68))
    star.addCurveToPoint(CGPointMake(110.75, 106.43), controlPoint1: CGPointMake(111.89, 97.34), controlPoint2: CGPointMake(110.13, 102.63))
    star.addLineToPoint(CGPointMake(112.79, 119))

我們可以在iOS中手動創(chuàng)建簡單的圖形,例如圓圖層:

    let circle = UIBezierPath(ovalInRect: inFrame)

現(xiàn)在我們可以創(chuàng)建自定義的按鈕了。

連接圖層

Xcode6 帶來一個叫做活動視圖(live views)的新科技,這使得處理自定義布局代碼更容易,并且不用運行或者構建視圖就能提供立即的可視化的反饋。所以讓我們使用活動視圖!為你的類使用活動視圖非常簡單:
1.放置關鍵詞@IBDesignable 在類聲明之上
2.放置關鍵詞@IBInspectable 在一個你想在故事版的屬性檢查器上改變的變量上。
3.重寫 layoutSubviews().這是一個你將添加子視圖和子圖層的地方。
為了我們的星星按鈕,我們將創(chuàng)建一個UIButton 的子類并且遵循下面的步驟:

    @IBDesignable
    class StarButton: UIButton
    {
      private var starShape: CAShapeLayer!
      private var outerRingShape: CAShapeLayer!
      private var fillRingShape: CAShapeLayer!

      @IBInspectable
      var lineWidth: CGFloat = 1 {
        didSet {
        updateLayerProperties()
       }
    }

    @IBInspectable
    var favoriteColor: UIColor = UIColor(hex:"eecd34") {
      didSet {
        updateLayerProperties()
      }
    }

    @IBInspectable
    var notFavoriteColor: UIColor = UIColor(hex:"9e9b9b") {
      didSet {
        updateLayerProperties()
      }
    }

    @IBInspectable
    var starFavoriteColor: UIColor = UIColor(hex:"9e9b9b") {
      didSet {
        updateLayerProperties()
      }
    }

    var isFavorite: Bool = false {
      didSet {
        return self.isFavorite ? favorite() : notFavorite()
      }
    }

    private func updateLayerProperties()
    {
      if fillRingShape != nil
      {
        fillRingShape.fillColor = favoriteColor.CGColor
      }

      if outerRingShape != nil
      {
        outerRingShape.lineWidth = lineWidth
        outerRingShape.strokeColor = notFavoriteColor.CGColor
      }

      if starShape != nil
      {
        starShape.fillColor = isFavorite ? starFavoriteColor.CGColor :   notFavoriteColor.CGColor
      }
    }

    override func layoutSubviews()
    {
      super.layoutSubviews()
      updateLayerProperties()
     }
    }

轉(zhuǎn)到故事版,創(chuàng)建一個按鈕,把它設置成自定義,去掉上面默認的文本,然后設置成我們的StarButton類。



在屬性檢查器上設置可視化屬性。



然后看!

動效

創(chuàng)建動畫總是一點點嘗試和失敗。當然,即使你有動畫背景,你也不會流暢的構建動效。為了節(jié)約時間,我會直接跳向代碼。為了創(chuàng)建動效,我已經(jīng)把這個動效分成五個步驟。我們要處理的主要屬性是用來轉(zhuǎn)換大小的CATransform3D,用來改變阿爾法值的opacity,用來改變顏色的fillColor.
我們假定我們用這些按鈕來點贊,然后這樣命名函數(shù)。


    private func favorite()
    {
    // 1. Star grows
    var starGoUp = CATransform3DIdentity
    starGoUp = CATransform3DScale(starGoUp, 1.5, 1.5, 1.5)

    // 2. Star stop growing and starts shrinking
    var starGoDown = CATransform3DIdentity
    starGoDown = CATransform3DScale(starGoDown, 0.01, 0.01, 0.01)

    // Configure a keyframe animation with both transforms (grow and shrink)
    let starKeyFrames = CAKeyframeAnimation(keyPath: "transform")
    starKeyFrames.values = [
      NSValue(CATransform3D:CATransform3DIdentity),
      NSValue(CATransform3D:starGoUp),
      NSValue(CATransform3D:starGoDown)
    ]
    starKeyFrames.keyTimes = [0.0,0.4,0.6]
    starKeyFrames.duration = 0.4
    starKeyFrames.beginTime = CACurrentMediaTime() + 0.05
    starKeyFrames.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn)

    // This is VERY important when you're working with relative time, remove and odd things will happen
    starKeyFrames.fillMode =  kCAFillModeBackwards
    starKeyFrames.setValue(favoriteKey, forKey: starKey)

    // Let the notification tell us when it's over
    starKeyFrames.delegate = self
    starShape.addAnimation(starKeyFrames, forKey: favoriteKey)
    starShape.transform = starGoDown

    // 1. Ring grows
    var grayGoUp = CATransform3DIdentity
    grayGoUp = CATransform3DScale(grayGoUp, 1.5, 1.5, 1.5)

    // 2. Ring stop growing and starts shrinking
    var grayGoDown = CATransform3DIdentity
    grayGoDown = CATransform3DScale(grayGoDown, 0.01, 0.01, 0.01)

    let outerCircleAnimation = CAKeyframeAnimation(keyPath: "transform")
    outerCircleAnimation.values = [
      NSValue(CATransform3D:CATransform3DIdentity),
      NSValue(CATransform3D:grayGoUp),
      NSValue(CATransform3D:grayGoDown)
    ]
    outerCircleAnimation.keyTimes = [0.0,0.4,0.6]
    outerCircleAnimation.duration = 0.4
    outerCircleAnimation.beginTime = CACurrentMediaTime() + 0.01
    outerCircleAnimation.fillMode =  kCAFillModeBackwards
    outerCircleAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn)

    outerRingShape.addAnimation(outerCircleAnimation, forKey: "Gray circle Animation")
    outerRingShape.transform = grayGoDown

    // 3. Fill Circle grows from Star's center.
    var favoriteFillGrow = CATransform3DIdentity
    favoriteFillGrow = CATransform3DScale(favoriteFillGrow, 1.5, 1.5, 1.5)

    // 5. Fill Circle grows until reach the size of step 2 and shrink back to the initial size.
    let fillCircleAnimation = CAKeyframeAnimation(keyPath: "transform")

    fillCircleAnimation.values = [
      NSValue(CATransform3D:fillRingShape.transform),
      NSValue(CATransform3D:favoriteFillGrow),
      NSValue(CATransform3D:CATransform3DIdentity)
    ]
    fillCircleAnimation.keyTimes = [0.0,0.4,0.6]
    fillCircleAnimation.duration = 0.4
    fillCircleAnimation.beginTime = CACurrentMediaTime() + 0.22
    fillCircleAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn)
    fillCircleAnimation.fillMode =  kCAFillModeBackwards

    let favoriteFillOpacity = CABasicAnimation(keyPath: "opacity")
    favoriteFillOpacity.toValue = 1
    favoriteFillOpacity.duration = 1
    favoriteFillOpacity.beginTime = CACurrentMediaTime()
    favoriteFillOpacity.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn)
    favoriteFillOpacity.fillMode =  kCAFillModeBackwards

    fillRingShape.addAnimation(favoriteFillOpacity, forKey: "Show fill circle")
    fillRingShape.addAnimation(fillCircleAnimation, forKey: "fill circle Animation")
    fillRingShape.transform = CATransform3DIdentity
    }

當?shù)谝徊糠值膭有ЫY束,第二部分的動效會觸發(fā)

    private func endFavorite()
    {
    // just a helper to run this piece of code with default actions disabled
    executeWithoutActions {
    self.starShape.fillColor = self.starFavoriteColor.CGColor
    self.starShape.opacity = 1
    self.fillRingShape.opacity = 1
    self.outerRingShape.transform = CATransform3DIdentity
    self.outerRingShape.opacity = 0
    }

    // 4. Star grows to it's initial size, and the filling color is changed.
    let starAnimations = CAAnimationGroup()
    var starGoUp = CATransform3DIdentity
    starGoUp = CATransform3DScale(starGoUp, 2, 2, 2)

    let starKeyFrames = CAKeyframeAnimation(keyPath: "transform")
    starKeyFrames.values = [
    NSValue(CATransform3D: starShape.transform),
    NSValue(CATransform3D:starGoUp),
    NSValue(CATransform3D:CATransform3DIdentity)
    ]
    starKeyFrames.keyTimes = [0.0,0.4,0.6]
    starKeyFrames.duration = 0.2
    starKeyFrames.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)

    starShape.addAnimation(starKeyFrames, forKey: nil)
    starShape.transform = CATransform3DIdentity
    }  

對于取消收藏,這個動效沒有秘密:

    private func notFavorite()
    {
    let starFillColor = CABasicAnimation(keyPath: "fillColor")
    starFillColor.toValue = notFavoriteColor.CGColor
    starFillColor.duration = 0.3

    let starOpacity = CABasicAnimation(keyPath: "opacity")
    starOpacity.toValue = 0.5
    starOpacity.duration = 0.3

    let starGroup = CAAnimationGroup()
    starGroup.animations = [starFillColor, starOpacity]

    starShape.addAnimation(starGroup, forKey: nil)
    starShape.fillColor = notFavoriteColor.CGColor
    starShape.opacity = 0.5

    let fillCircle = CABasicAnimation(keyPath: "opacity")
    fillCircle.toValue = 0
    fillCircle.duration = 0.3
    fillCircle.setValue(notFavoriteKey, forKey: starKey)
    fillCircle.delegate = self

    fillRingShape.addAnimation(fillCircle, forKey: nil)
    fillRingShape.opacity = 0

    let outerCircle = CABasicAnimation(keyPath: "opacity")
    outerCircle.toValue = 0.5
    outerCircle.duration = 0.3

    outerRingShape.addAnimation(outerCircle, forKey: nil)
    outerRingShape.opacity = 0.5
    }

一個附贈品

因為Swift在iOS社區(qū)是一個真正的明星,我們慶祝一下!
按照下面這個CGPath修改這個星圖層路徑:

    var swiftPath = UIBezierPath()
    swiftPath.moveToPoint(CGPointMake(376.2, 283.2))
    swiftPath.addCurveToPoint(CGPointMake(349.8, 238.4),controlPoint1: CGPointMake(367.4, 258.4), controlPoint2: CGPointMake(349.8, 238.4))
    swiftPath.addCurveToPoint(CGPointMake(236.5, 0), controlPoint1: CGPointMake(349.8, 238.4), controlPoint2: CGPointMake(399.7, 105.6))
    swiftPath.addCurveToPoint(CGPointMake(269, 180.8), controlPoint1: CGPointMake(303.7, 101.6), controlPoint2: CGPointMake(269, 180.8))
    swiftPath.addCurveToPoint(CGPointMake(181.29, 117.07), controlPoint1: CGPointMake(269, 180.8), controlPoint2: CGPointMake(211.4, 140.8))
    swiftPath.addCurveToPoint(CGPointMake(85, 33.6), controlPoint1: CGPointMake(151.18, 93.35), controlPoint2: CGPointMake(85, 33.6))
    swiftPath.addCurveToPoint(CGPointMake(145, 117.07), controlPoint1: CGPointMake(85, 33.6), controlPoint2: CGPointMake(128.15, 96.31))
    swiftPath.addCurveToPoint(CGPointMake(185.78, 163.66), controlPoint1: CGPointMake(161.85, 137.84), controlPoint2: CGPointMake(185.78, 163.66))
    swiftPath.addCurveToPoint(CGPointMake(136.36, 129.42), controlPoint1: CGPointMake(185.78, 163.66), controlPoint2: CGPointMake(161.07, 147.39))
    swiftPath.addCurveToPoint(CGPointMake(34.6, 50.4), controlPoint1: CGPointMake(111.65, 111.46), controlPoint2: CGPointMake(34.6, 50.4))
    swiftPath.addCurveToPoint(CGPointMake(133.8, 169.2), controlPoint1: CGPointMake(34.6, 50.4), controlPoint2: CGPointMake(82.69, 119.24))
    swiftPath.addCurveToPoint(CGPointMake(214.6, 244), controlPoint1: CGPointMake(184.91, 219.16), controlPoint2: CGPointMake(214.6, 244))
    swiftPath.addCurveToPoint(CGPointMake(129.8, 264.8), controlPoint1: CGPointMake(214.6, 244), controlPoint2: CGPointMake(196.2, 263.2))
    swiftPath.addCurveToPoint(CGPointMake(0, 221), controlPoint1: CGPointMake(63.4, 266.4), controlPoint2: CGPointMake(0, 221))
    swiftPath.addCurveToPoint(CGPointMake(206.6, 339.2), controlPoint1: CGPointMake(0, 221), controlPoint2: CGPointMake(62.5, 339.2))
    swiftPath.addCurveToPoint(CGPointMake(325, 304.8), controlPoint1: CGPointMake(270.6, 339.2), controlPoint2: CGPointMake(288.93, 304.8))
    swiftPath.addCurveToPoint(CGPointMake(383.3, 339.2), controlPoint1: CGPointMake(361.07, 304.8), controlPoint2: CGPointMake(381.7, 340))
    swiftPath.addCurveToPoint(CGPointMake(376.2, 283.2), controlPoint1: CGPointMake(384.9, 338.4), controlPoint2: CGPointMake(385, 308))
    return swiftPath.CGPath

這就可以了!偶也!Swift!


就是這個!我希望你能理解多一點關于創(chuàng)建動效。去我的Github找到完整的工程。當我添加一些新圖形的時候,我會升級代碼,所以注意這個倉庫!如果你喜歡這個項目,請分享并且粉一下這個git倉庫!

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

  • 發(fā)現(xiàn) 關注 消息 iOS 第三方庫、插件、知名博客總結 作者大灰狼的小綿羊哥哥關注 2017.06.26 09:4...
    肇東周閱讀 15,186評論 4 61
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,939評論 25 709
  • 如何你想某事正確,自己動手做吧?!狢harles-Guillaume étienne 前一章介紹了隱式動畫的概念...
    liril閱讀 1,404評論 0 3
  • 一,https://stackoverflow.com/questions/41275442/testing-vo...
    LX2014閱讀 342評論 0 0
  • 櫻桃產(chǎn)業(yè)研究。
    樂樂827閱讀 254評論 0 0

友情鏈接更多精彩內(nèi)容