Кольцо таймера анимации погоня за хвостом с помощью CoreAnimation


После того, как изрядное количество проб и ошибок, я получил анимацию постановлен в порядке, что я хочу его, как видно здесь. Тем не менее, я всегда чувствую, что я тоже пишу так много и слишком небрежно при попытке использовать ядро анимации.

Анимация выглядит следующим образом:

  1. Представление начинается с точечного света в верхней части петли.
  2. Когда пользователь запускает таймер, светящиеся линии исполняет предписанное количество времени, чтобы заполнить петли.
  3. После завершения цикла будет гореть до тех пор, пока пользователь.
  4. Когда признал, хвоста светящейся линии гонится за голову и вернется на одну точку в верхней части экрана.
  5. Повторить

Код контролируя анимация все завернутый в подклассе UIView:

class LoopView: UIView {
  //  ...

  lazy var path: UIBezierPath = initializedPath()
  lazy var animationLayer: CAShapeLayer = initializedAnimationLayer()

  override func draw(_ rect: CGRect) {
    layer.addSublayer(animationLayer)
    //  ...
  }

  //  ...
}

Здесь представлены пути и анимации слой инициализации:

  private func intializedPath() -> UIBezierPath {
    return UIBezierPath(ovalIn: bounds)
  }

  private func initializedAnimationLayer() -> CAShapeLayer {
    let layer = CAShapeLayer()
    let transform = CGAffineTransform(rotationAngle: 1.5 * CGFloat.pi).translatedBy(x: -bounds.width, y: 0)

    layer.path = path.cgPath
    layer.setAffineTransform(transform)

    layer.strokeColor = timerColor.cgColor
    layer.fillColor = UIColor.clear.cgColor
    layer.lineCap = kCALineCapRound
    layer.lineWidth = lineWidth

    layer.shadowColor = timerColor.cgColor
    layer.shadowRadius = 5.0
    layer.shadowOpacity = 1
    layer.shadowOffset = CGSize(width: 0, height: 0)

    layer.strokeEnd = CGFloat.leastNormalMagnitude

    return layer
  }

И, наконец, мясо анимации:

func animateTimer(over duration: TimeInterval) {
    let head = CABasicAnimation(keyPath: "strokeEnd")
    head.fromValue = CGFloat.leastNormalMagnitude
    head.toValue = 1
    head.duration = duration
    head.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
    head.fillMode = kCAFillModeForwards
    head.isRemovedOnCompletion = false

    animationLayer.add(head, forKey: "strokeEnd")
}

func animateTimerReset() {
    UIView.animate(withDuration: 0, animations: { self.animationLayer.strokeStart = 1 }) { (_) in
        self.animationLayer = self.initializedAnimationLayer()
        self.layer.addSublayer(self.animationLayer)
    }
}

Где я провел большую часть времени на выяснение, что я не хочу, чтобы выбросить animationLayer каждый раз, когда я хотел сбросить анимацию. Я также должен был использовать различные методы определения анимации для заполнения и коллапса, который чувствует неладное, но пытается использовать те же стратегии, не получаю результаты, которые я хотел.

Для справки, полный код класса в этом суть



205
3
задан 28 марта 2018 в 06:03 Источник Поделиться
Комментарии
1 ответ

Есть одна проблема с ваш подход: добавлены дополнительные подслои
в draw() и в animateTimerReset() и никогда не удаляются. Что может
причиной проблем с памятью и медленнее рисунок в конце концов.

Я бы предложил создать и добавить только один экземпляр из слоя анимации, это может быть, например, сделано в

override func awakeFromNib() {
layer.addSublayer(animationLayer)
}

Затем анимировать это один слоя анимации:

func animateTimer(over duration: TimeInterval) {
animationLayer.removeAllAnimations()
let head = CABasicAnimation(keyPath: "strokeEnd")
head.toValue = 1
head.duration = duration
head.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
head.fillMode = kCAFillModeForwards
head.isRemovedOnCompletion = false
animationLayer.add(head, forKey: "strokeEnd")
}

func animateTimerReset() {
let tail = CABasicAnimation(keyPath: "strokeStart")
tail.toValue = 1
tail.duration = 0.1
tail.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
tail.fillMode = kCAFillModeForwards
tail.isRemovedOnCompletion = false
animationLayer.add(tail, forKey: "strokeStart")
}

Некоторые дополнительные замечания: в нескольких местах явного типа
аннотации могут быть удалены, например

var trackColor: UIColor = UIColor(red: 0.3, green: 0.3, blue: 0.3, alpha: 1)
var trackShade: UIColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.6)
var trackLight: UIColor = UIColor(red: 1, green: 1, blue: 1, alpha: 0.3)

короче написано как

var trackColor = UIColor(red: 0.3, green: 0.3, blue: 0.3, alpha: 1)
var trackShade = UIColor(red: 0, green: 0, blue: 0, alpha: 0.6)
var trackLight = UIColor(red: 1, green: 1, blue: 1, alpha: 0.3)

Также “косвенные выражения членов“ может использоваться, если тип выводится из контекста, например

layer.strokeEnd = CGFloat.leastNormalMagnitude

становится

layer.strokeEnd = .leastNormalMagnitude

4
ответ дан 28 марта 2018 в 10:03 Источник Поделиться