[iOS 動(dòng)畫(huà)]徒手做一個(gè)會(huì)動(dòng)的icon

難度:??
最終效果:

最終效果
最終效果

在平時(shí)堆UI的時(shí)候,避免不了要碰到UED要給我們出一點(diǎn)點(diǎn)難題,比如這次又叫我抄一下淘寶的下拉刷新了(手動(dòng)斜眼)。
說(shuō)干就干,首先新建一個(gè)View封裝所有的代碼:

import UIKit

class AnimatedArrow: UIView {  
  override init(frame: CGRect) {
    super.init(frame: frame)
    commonInit()
  }
}

init中調(diào)用我們添加子視圖的代碼。這時(shí)候來(lái)分析一下我們需要添加點(diǎn)什么:

  • 一個(gè)畫(huà)圓圈的layer
  • 一個(gè)包含箭頭的layer

其中箭頭這個(gè)圖層還要分為左中右三根直線。
我們先來(lái)畫(huà)這個(gè)圓圈, 我們需要一個(gè)CAShapeLayer,把這個(gè)layer的frame設(shè)置一個(gè)正方形,然后利用貝塞爾曲線在這個(gè)正方形的Rect中畫(huà)一個(gè)圓,把這個(gè)layer的路徑設(shè)置為圓的路徑。
然后設(shè)置一下layer基本的填充色、描邊色、線帽(線的起點(diǎn)和終點(diǎn)兩端的形狀)。
為了做圓形的填充動(dòng)畫(huà)(其實(shí)就是描邊),我們需要把描邊的起點(diǎn)和終點(diǎn)設(shè)置一下。起點(diǎn)為0,終點(diǎn)由代碼控制,設(shè)置為self.progress.

稍微注意一下UIKit Graphics 方法和 Core Graphics方法的區(qū)別,Core Graphics是從macOS繼承下來(lái)的,UIGraphics是iOS獨(dú)有的一套。所以貝塞爾曲線類UIBezierPath是在UIKit里面的。

var circleLayer:CAShapeLayer?
  func loadCircleLayer() {
    let layer = CAShapeLayer()

    //incase self.bounds.size is not a square
    let radius = min(self.bounds.width, self.bounds.height)
    let frame = CGRect(x: 0, y: 0, width: radius, height: radius)
    layer.frame = frame
    layer.strokeColor = UIColor.black.cgColor
    layer.fillColor = UIColor.clear.cgColor
    
    let path = UIBezierPath(ovalIn: frame)
    layer.path = path.cgPath
    layer.lineWidth = 1
    layer.lineCap = kCALineCapRound
    layer.strokeStart = 0
    layer.strokeEnd = self.progress
    
    self.layer.addSublayer(layer)
    self.circleLayer = layer
  }

再來(lái)看一下self.progress,復(fù)寫(xiě)了一下它的set方法,在set的時(shí)候賦值并且改變circleLayer的描邊終點(diǎn)。至于最大值是0.95,那是為了留一個(gè)缺口,好看出旋轉(zhuǎn)動(dòng)畫(huà)。

private var _progress:CGFloat = 0  
var progress:CGFloat {
    get {
      return _progress
    }
    set {
      _progress = newValue
      self.circleLayer?.strokeEnd = min(0.95, newValue)
    }
 }

現(xiàn)在我們來(lái)畫(huà)中間的這個(gè)箭頭,首先判斷一下比例:

可以看出來(lái)中間這根線大概是邊長(zhǎng)的二分之一,然后水平垂直都居中。我們來(lái)創(chuàng)建一下它的路徑:

為什么有個(gè) 0.5 ? 因?yàn)榫€寬1,為了居中,就要左偏移0.5

func middlePath() -> CGPath {
    let width = self.bounds.size.width / 2;
    let path = UIBezierPath()
    path.move(to: CGPoint(x: width - 0.5, y: width / 2))
    path.addLine(to: CGPoint(x: width - 0.5, y: 3 * width / 2))
    return path.cgPath
}

左邊和右邊這兩條線起點(diǎn)是中間線的下端點(diǎn),終點(diǎn)是左右一半的水平終點(diǎn)和垂直居中處,那就好辦了,畫(huà)一下他們倆的路徑:

  func leftPath() -> CGPath {
    let width = self.bounds.size.width / 2;
    let path = UIBezierPath()
    path.move(to: CGPoint(x: width / 2, y: width))
    path.addLine(to: CGPoint(x: width - 0.5, y: 3 * width / 2))
    return path.cgPath
  }
  
  func rightPath() -> CGPath {
    let width = self.bounds.size.width / 2;
    let path = UIBezierPath()
    path.move(to: CGPoint(x: 3 * width / 2, y: width))
    path.addLine(to: CGPoint(x: width - 0.5, y: 3 * width / 2))
    return path.cgPath
  }
  

現(xiàn)在,根據(jù)這三條路徑創(chuàng)建圖層:

  func templateLayer(path:CGPath) -> CAShapeLayer {
    let layer = CAShapeLayer()
    layer.frame = self.bounds
    layer.strokeColor = UIColor.black.cgColor
    layer.path = path
    layer.lineWidth = 1
    layer.lineCap = kCALineCapRound
    layer.fillColor = UIColor.clear.cgColor
    return layer
  }
  
  var containerLayer:CALayer?
  func loadArrowLayer() {
    self.containerLayer = CALayer()
    self.containerLayer?.frame = self.bounds
    self.containerLayer?.addSublayer(templateLayer(path: middlePath()))
    self.containerLayer?.addSublayer(templateLayer(path: leftPath()))
    self.containerLayer?.addSublayer(templateLayer(path: rightPath()))
    self.layer.addSublayer(self.containerLayer!)
  }  

然后把它們組合起來(lái):

func commonInit() {
    loadCircleLayer()
    loadArrowLayer()
}

創(chuàng)建動(dòng)畫(huà):
圓形的填充progress是根據(jù)操作進(jìn)行的,這個(gè)我們可以通過(guò)監(jiān)聽(tīng)UIScrollView的contentOffset實(shí)現(xiàn)。還有一個(gè)就是箭頭的隱藏和轉(zhuǎn)圈,幾行代碼就可以了:

  func startAnimation() {
    self.containerLayer?.isHidden = true
    let animation = CABasicAnimation(keyPath: "transform.rotation.z")
    animation.fromValue = 0
    animation.toValue = M_PI * 2
    animation.duration = 1
    animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)
    animation.repeatCount = Float.infinity
    self.circleLayer?.add(animation, forKey: "infinity_rotate")
  }
  
  func stopAnimation() {
    self.containerLayer?.isHidden = false
    self.circleLayer?.removeAnimation(forKey: "infinity_rotate")
  }

剩下要做的事情就是把這些操作組合一下,比如只在 progress 為0.95的時(shí)候才能觸發(fā) startAnimation() 這樣可以避免視圖顯示不全。
最后,我把這個(gè)View添加到視圖中,通過(guò) set progress 來(lái)進(jìn)行填充,然后調(diào)用動(dòng)畫(huà)就可以了。

- EOF -

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

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

  • 在iOS中隨處都可以看到絢麗的動(dòng)畫(huà)效果,實(shí)現(xiàn)這些動(dòng)畫(huà)的過(guò)程并不復(fù)雜,今天將帶大家一窺ios動(dòng)畫(huà)全貌。在這里你可以看...
    每天刷兩次牙閱讀 8,687評(píng)論 6 30
  • 轉(zhuǎn)載:http://www.itdecent.cn/p/32fcadd12108 每個(gè)UIView有一個(gè)伙伴稱為l...
    F麥子閱讀 6,567評(píng)論 0 13
  • 前言 本文只要描述了iOS中的Core Animation(核心動(dòng)畫(huà):隱式動(dòng)畫(huà)、顯示動(dòng)畫(huà))、貝塞爾曲線、UIVie...
    GitHubPorter閱讀 3,736評(píng)論 7 11
  • 每個(gè)UIView有一個(gè)伙伴稱為layer,一個(gè)CALayer。UIView實(shí)際上并沒(méi)有把自己畫(huà)到屏幕上;它繪制本身...
    shenzhenboy閱讀 3,252評(píng)論 0 17
  • 子線程實(shí)現(xiàn)方式: 繼承Thread類; 實(shí)現(xiàn)Runnable接口。 什么情況下需要同步? 當(dāng)多線程并發(fā),有多段代碼...
    and2long閱讀 611評(píng)論 0 1

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