Basic CAShapeLayer iOS (P3)

by Hoang Anh Tuan
323 views

Lâu k gặp chủ đề gì hay ho để viết, ngồi viết nốt bài trong series basic CAShapeLayer đang bỏ dở từ lâu vậy =)) Bài viết này sẽ nói về cách sử animation cơ bản với CAShapeLayer.

Content

  • Khởi tạo 1 animation
  • Các thuộc tính của animation
  • Lưu í

Khởi tạo 1 animation

Khởi tạo 1 animation. 1 animation có dạng CABasicAnimation.

let animation = CABasicAnimation(keyPath: )

Bởi vì 1 shapeLayer có rất nhiều thuộc tính khác nhau để áp dụng animation, nên khi khởi tạo 1 CABasicAnimation thì ta phải truyền keyPath vào để xác định xem sẽ thực hiện animation trên thuộc tính nào.
Có vẻ API này đã rất lâu rồi nên Apple không define keyPath bằng enum 🙁 Nên ta phải truyền keyPath bằng 1 string 🙁
Tìm trên mạng thì đây là các thuộc tính mà CAShapeLayer support để animate:

Sau khi khởi tạo 1 animation, chỉ việc add animation đó vào shapeLayer của bạn.

shapeLayer.add(animation, forKey: nil)

Các thuộc tính của animation:

Ta có thể cấu hình thêm cho animation 1 chút bằng các thuộc tính của CABasicAnimation như: duration, fromValue, toValue, timingFunction, fillMode, isRemovedOnCompletion, …

duration, timingFunction, repeatCount, autoReverse

  • duration: set thời gian chạy animation
  • timingFunction: set gia tốc
  • repeatCount: số lần lặp lại animation
  • autoReverse: có reverse lại sau khi kết thúc animation hay không
  • beginTime: Thời gian bắt đầu chạy animation
func createSquare() {
    let shapeLayer = CAShapeLayer()
        
    shapeLayer.lineWidth = 2.0
    shapeLayer.fillColor = UIColor.clear.cgColor
    shapeLayer.strokeColor = UIColor.red.cgColor
    let openCirclePath = UIBezierPath(arcCenter: CGPoint(x: 100, y: 200),
                                      radius: 60.0,
                                      startAngle: 0.0,
                                      endAngle: CGFloat.pi * 2,
                                      clockwise: true)
        
    shapeLayer.path = openCirclePath.cgPath
    view.layer.addSublayer(shapeLayer)
        
    let animation = CABasicAnimation(keyPath: "strokeEnd")
    animation.beginTime = CACurrentMediaTime() + 0.3
    animation.fromValue = 0.0
    animation.toValue = 1.0
    animation.duration = 2
    animation.repeatCount = HUGE
    animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
    shapeLayer.add(animation, forKey: nil)
}

fromValue, toValue

Tương tự như giải thích ở bài trước, nó sẽ quyết định xem animation chạy % nào. Dưới đây là lần lượt 2 trường hợp chạy animation với fromValue = 0 và fromValue = 0.5

fillMode

Trước hết, hay thay đoạn animation ở trên bằng đoạn animation này và quan sát kết quả:

let animation = CABasicAnimation(keyPath: "strokeColor")
animation.beginTime = CACurrentMediaTime() + 2
animation.fromValue = UIColor.blue.cgColor
animation.toValue = UIColor.cyan.cgColor
animation.duration = 2
animation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut)
shapeLayer.add(animation, forKey: nil)

Ở ví dụ trên, mình chạy animation đổi màu và bắt đầu sau 3s. Màu của layer lần lượt biến đổi như sau:

  • Đầu tiên shapeLayer có strokeColor là màu đỏ
  • Khi bắt đầu animation thì được set là màu blue, animate đến màu cyan
  • Khi kết thúc animation thì strokeColor lại thành màu đỏ

Thuộc tính fillMode cho phép bạn kiểm soát behavior của animation tại thời điểm bắt đầu và kết thúc của animation. fillMode gồm: forward, backward, both và removed. Có các tác dụng như sau:

  • removed: giá trị default của fillMode. Effect của animation sẽ bị remove khi animation kết thúc -> Lí do đầu tiên khiến strokeColor quay về thành màu đỏ
  • backward: Hiển thị khung hình đầu tiên của animation ngay lập tức. Ở trường hợp này sẽ hiển thị màu xanh ngay lập tức.
  • forward: Giữ lại khung hình cuối cùng của animation cho đến khi bạn remove animation.
  • both: kết hợp forware và backward.

set fillMode cho animation = .both và quan sát kết quả:

isRemovedOnCompletion

Wait? Tại sao set fillMode = .both hoặc .forward mà vẫn bị reset về màu đỏ ban đầu vậy?

Việc set fillMode = forward chỉ có tác dụng giữ EFFECT cuối cùng vẫn được giữ ở animation, nhưng animation đã bị remove khỏi layer khi thực hiện xong rồi =))

Vì vậy để giữ cho animation không bị remove thì bạn sẽ phải set đồng thời cả 2 thuộc tính:

 animation.fillMode = .forward
 animation.isRemovedOnCompletion = false
Uy tín luôn

Lưu ý

#1
Khi app xuống background thì animation sẽ bị remove khỏi layer -> Các animation sẽ bị mất. Để tránh tình trạng này thì chỉ cần set thuộc tính sau cho animation:

animation.isRemovedOnCompletion = false

#2

  • Có rất nhiều keyPath để set cho CABasicAnimation, bạn có thể tham khảo thêm ở đây.
  • Tuy nhiên, chỉ những thuộc tính mình để cập ở trên mới được dùng cho CAShapeLayer.
    Việc dùng các keyPath khác để dùng cho CAShapeLayer có thể đem lại những animation không được như ý muốn: ví dụ như 1 vài animation rotate, …
  • Nếu muốn kết hợp các animation không support CAShapeLayer với các shapeLayer thì có thể trick bằng cách add shapeLayer lên 1 UIView, rồi add animation lên layer của view đó.
loadingView.layer.addSublayer(shapeLayer)
let rotationAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
loadingView.layer.add(rotationAnimation, forKey: nil)

#3
CABasicAnimation có các delegate animationDidStart và animationDidStop để handle thêm nếu muốn nhưng sẽ k đề cập ở bài viết này.

#4
CABasicAnimation thì có 1 vài thuộc tính để custom, và cũng chỉ animate đơn giản được bằng fromValue và endValue.
Để có thể custom được nhiều hơn thì có thể sử dụng thêm keyFrame Animation cho ShapeLayer. // Có thể mình sẽ viết ở 1 bài khác

Leave a Comment

* By using this form you agree with the storage and handling of your data by this website.