iOS变形播放/暂停动画

sur*_*heW 0 cabasicanimation cashapelayer ios swift

我想在我正在开发的应用程序中使用像变形播放/暂停动画这样的YouTube.它看起来像这里的动画.我不知道该怎么做.我知道我需要一个CABasicAnimationCAShapeLayer.我知道我需要两个(或者更多?)- UIBezierPaths.一个将是动画shapeLayer的另一个路径toValue.就像是:

shape.path = pausePath().cgPath

let animation = CABasicAnimation(keyPath: "path")
animation.toValue = trainglePath().cgPath
animation.duration = 1
shape.add(animation, forKey: animation.keyPath)
Run Code Online (Sandbox Code Playgroud)

但我不知道如何走这条道路.我有以下问题:

  1. 道路的重要方式是什么?例如,从左到右绘制的线是否与从右到左制作的线不同?我的意思是path.move(to: startPoint); path.addLine(to: endPoint1)反对path.move(to: endPoint1);path.addLine(to: startPoint)
  2. 两条路是否足够?一个用于启动按钮,一个用于暂停按钮?如果是的话 - 绘制它们的正确方法是什么,以便它们能够"正确"地制作动画?我不在这里要求代码(不是我介意将代码作为答案)但是一些一般的解释对于初学者来说已经足够了.

Dun*_*n C 7

使路径动画起作用的关键是使起始路径和结束路径具有相同数量的控制点.如果你看一下这个动画,我看起来像初始的"游戏"三角形被绘制成两个接触的四边形,其中右四边形有两个点在一起,所以它看起来像一个三角形.然后动画将这些四边形的控制点分开,并将它们都转换为暂停符号的矩形.

如果您将其映射到方格纸上,那么创建前后控制点应该非常容易.

考虑下面的(非常粗略的)插图.顶部显示分为2个四边形的"游戏"三角形,并将右侧部分(黄色)显示为四边形,右侧的点稍微分开以显示该想法.在实践中,您将设置四边形,这两个控制点具有完全相同的坐标.

我使用数字标记每个四边形的控制点以显示绘制它们的顺序(对于每个四边形:moveTo第一点,lineTo第2 /第3 /第4点)

底部插图显示了移动的点以给出暂停符号.

如果你创建一个CAShapeLayer的CABasicAnimation,从顶部插图这样的控制点开始,最后一个控制点就像底部插图一样,你应该得到一个非常类似你链接的动画.

在此输入图像描述


San*_*eep 5

这是一个基于的小型实现CAShapeLayer,它基本上克隆了链接中描述的行为。

class PlayPauseButton: UIControl {

    // public function can be called from out interface
    func setPlaying(_ playing: Bool) {
        self.playing = playing
        animateLayer()
    }

    private (set) var playing: Bool = false
    private let leftLayer: CAShapeLayer
    private let rightLayer: CAShapeLayer

    override init(frame: CGRect) {
        leftLayer = CAShapeLayer()
        rightLayer = CAShapeLayer()
        super.init(frame: frame)

        backgroundColor = UIColor.white
        setupLayers()
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("Not implemented")
    }

    private func setupLayers() {
        layer.addSublayer(leftLayer)
        layer.addSublayer(rightLayer)

        leftLayer.fillColor = UIColor.black.cgColor
        rightLayer.fillColor = UIColor.black.cgColor
        addTarget(self,
                  action: #selector(pressed),
                  for: .touchUpInside)
    }

    @objc private func pressed() {
        setPlaying(!playing)
    }

    private func animateLayer() {
        let fromLeftPath = leftLayer.path
        let toLeftPath = leftPath()
        leftLayer.path = toLeftPath

        let fromRightPath = rightLayer.path
        let toRightPath = rightPath()
        rightLayer.path = toRightPath

        let leftPathAnimation = pathAnimation(fromPath: fromLeftPath,
                                              toPath: toLeftPath)
        let rightPathAnimation = pathAnimation(fromPath: fromRightPath,
                                               toPath: toRightPath)

        leftLayer.add(leftPathAnimation,
                      forKey: nil)
        rightLayer.add(rightPathAnimation,
                       forKey: nil)
    }

    private func pathAnimation(fromPath: CGPath?,
                               toPath: CGPath) -> CAAnimation {
        let animation = CABasicAnimation(keyPath: "path")
        animation.duration = 0.33
        animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseIn)
        animation.fromValue = fromPath
        animation.toValue = toPath
        return animation
    }

    override func layoutSubviews() {
        leftLayer.frame = leftLayerFrame
        rightLayer.frame = rightLayerFrame

        leftLayer.path = leftPath()
        rightLayer.path = rightPath()
    }

    private let pauseButtonLineSpacing: CGFloat = 10

    private var leftLayerFrame: CGRect {
        return CGRect(x: 0,
                      y: 0,
                      width: bounds.width * 0.5,
                      height: bounds.height)
    }

    private var rightLayerFrame: CGRect {
        return leftLayerFrame.offsetBy(dx: bounds.width * 0.5,
                                       dy: 0)
    }

    private func leftPath() -> CGPath {
        if playing {
            let bound = leftLayer.bounds
                                 .insetBy(dx: pauseButtonLineSpacing,
                                          dy: 0)
                                 .offsetBy(dx: -pauseButtonLineSpacing,
                                           dy: 0)

            return UIBezierPath(rect: bound).cgPath
        }

        return leftLayerPausedPath()
    }

    private func rightPath() -> CGPath {
        if playing {
            let bound = rightLayer.bounds
                                 .insetBy(dx: pauseButtonLineSpacing,
                                          dy: 0)
                                .offsetBy(dx: pauseButtonLineSpacing,
                                          dy: 0)
            return UIBezierPath(rect: bound).cgPath
        }

        return rightLayerPausedPath()
    }

    private func leftLayerPausedPath() -> CGPath {
        let y1 = leftLayerFrame.width * 0.5
        let y2 = leftLayerFrame.height - leftLayerFrame.width * 0.5

        let path = UIBezierPath()
        path.move(to:CGPoint(x: 0, y: 0))
        path.addLine(to: CGPoint(x: leftLayerFrame.width,
                                 y: y1))
        path.addLine(to: CGPoint(x: leftLayerFrame.width,
                                 y: y2))
        path.addLine(to: CGPoint(x: 0,
                                 y: leftLayerFrame.height))
        path.close()

        return path.cgPath
    }

    private func rightLayerPausedPath() -> CGPath {
        let y1 = rightLayerFrame.width * 0.5
        let y2 = rightLayerFrame.height - leftLayerFrame.width * 0.5
        let path = UIBezierPath()

        path.move(to:CGPoint(x: 0, y: y1))
        path.addLine(to: CGPoint(x: rightLayerFrame.width,
                                 y: rightLayerFrame.height * 0.5))
        path.addLine(to: CGPoint(x: rightLayerFrame.width,
                                 y: rightLayerFrame.height * 0.5))
        path.addLine(to: CGPoint(x: 0,
                                 y: y2))
        path.close()
        return path.cgPath
    }
}
Run Code Online (Sandbox Code Playgroud)

代码应该很简单。这是您可以使用的方式,

let playButton = PlayPauseButton(frame: CGRect(x: 0,
                                               y: 0,
                                               width: 200,
                                               height: 200))
view.addSubview(playButton)
Run Code Online (Sandbox Code Playgroud)

您不需要从外部处理任何东西。该按钮本身可以处理动画。您可以使用目标/动作来更改检测按钮的单击。另外,您可以使用公共方法从外部添加动画状态,

playButton.setPlaying(true) // animate to playing
playButton.setPlaying(false) // animate to paused state
Run Code Online (Sandbox Code Playgroud)

这是它的样子,

在此处输入图片说明