如何使 UIBezierPath 中的两条线以不同的速度进行动画处理

Posted

技术标签:

【中文标题】如何使 UIBezierPath 中的两条线以不同的速度进行动画处理【英文标题】:How to make two lines in UIBezierPath to animate at different speed 【发布时间】:2015-01-09 00:07:27 【问题描述】:

说出以下场景: 我画了一个四边形,它是一个 UIView 的蒙版。我将形状层表示为maskLayermaskLayer 不对称裁剪 UIView 的底部。

但是我想在动画中完全展示我的 UIView。动画应该是 maskLayer 的左侧下降到 UIView 的底部,0.2 秒后我的 maskLayer 的右侧也下降到 UIView 的底部,从而完全显示 UIView 的实体。

我的做法是先下拉左行,再下拉右行,如下代码:

  //this quadrilateral will put down left corner to the bottom of screen
  var path2 = UIBezierPath()
  path2.moveToPoint(CGPointZero)
  path2.addLineToPoint(CGPoint(x: 0, y: frame.height))
  path2.addLineToPoint(CGPoint(x: frame.width, y: frame.height / goldRatio / goldRatio))
  path2.addLineToPoint(CGPoint(x: frame.width, y: 0))
  path2.closePath()

  //this rectangle path will put down both corner to the bottom of screen
  //thus fix the view to its original shape
  var path3 = UIBezierPath()
  path3.moveToPoint(CGPointZero)
  path3.addLineToPoint(CGPoint(x: 0, y: frame.height))
  path3.addLineToPoint(CGPoint(x: frame.width, y: frame.height))
  path3.addLineToPoint(CGPoint(x: frame.width, y: 0))
  path3.closePath()

我花了 2 个小时试图弄清楚它无济于事。请您给我一些关于如何实现这一目标的说明。

初始状态如下:

结束状态如下:

非常感谢您的帮助!

【问题讨论】:

【参考方案1】:

最简单的方法...作弊。

不要尝试为它确实不需要它的形状的路径设置动画。

你应该做的是这样的。

创建您的最终状态视图。矩形,在屏幕底部,上面有 UI 等等...

这个最终状态视图不会移动。它会一直在这里。

现在创建另一个视图并将其作为子视图插入到最终状态视图下方。在此您可以添加一个切掉角角的形状图层。

现在您需要做的就是将该角度视图的位置向下设置动画,直到它完全低于最终状态视图。

如果它们是相同的颜色,这将产生动画形状路径的效果。

要获得不同的速度,您可以将矩形图层旋转 45 度。然后在视图向下滑动时将其设置为 0 度?

事实上,您可以使用旋转和移动的单个形状图层来做到这一点。

【讨论】:

【参考方案2】:

要制作这种动画,您通常会使用CADisplayLink(有点像计时器,但与显示更新而不是某个任意时间间隔相关联)来重复更改与所需形状关联的path。我认为如果您使用 CAShapeLayer 并更改此形状的 path 属性,这是最简单的。

要完成这项工作,您需要一个函数来表示给定时间点的路径(或者更简单,沿着动画持续时间的某个时刻percentageComplete 的路径)。由于您有一个规则的形状(点数始终相同),您可以简单地在 startPointsendPoints 的一些数组之间进行插值。

所以,创建形状层,捕获开始时间,启动显示链接,然后对于显示链接的每个“滴答”,计算animationDuration已经过去的百分比,并更新形状层的路径相应地:

class ViewController: UIViewController 

    let animationDuration = 2.0
    var displayLink: CADisplayLink!
    var startTime: CFAbsoluteTime!
    var shapeLayer: CAShapeLayer!

    let goldRatio: CGFloat = 1.6180339887

    var startPoints:[CGPoint]!
    var endPoints:[CGPoint]!

    override func viewDidLoad() 
        super.viewDidLoad()

        self.startAnimation()
    

    func startAnimation() 
        startPoints = [
            CGPoint(x: 0, y: view.bounds.size.height),
            CGPoint(x: 0, y: view.bounds.size.height - view.bounds.size.height / goldRatio),
            CGPoint(x: view.bounds.size.width, y: 0),
            CGPoint(x: view.bounds.size.width, y: view.bounds.size.height)
        ]

        endPoints = [
            CGPoint(x: 0, y: view.bounds.size.height),
            CGPoint(x: 0, y: view.bounds.size.height * 0.75),
            CGPoint(x: view.bounds.size.width, y: view.bounds.size.height * 0.75),
            CGPoint(x: view.bounds.size.width, y: view.bounds.size.height)
        ]

        assert(startPoints.count == endPoints.count, "Point counts don't match")

        createShape()
        startDisplayLink()
    

    func startDisplayLink() 
        displayLink = CADisplayLink(target: self, selector: "handleDisplayLink:")
        startTime = CFAbsoluteTimeGetCurrent()
        displayLink.addToRunLoop(NSRunLoop.mainRunLoop(), forMode: NSRunLoopCommonModes)
    

    func stopDisplayLink() 
        displayLink.invalidate()
        displayLink = nil
    

    func handleDisplayLink(displayLink: CADisplayLink) 
        var percent = CGFloat((CFAbsoluteTimeGetCurrent() - startTime) / animationDuration)

        if percent >= 1.0 
            percent = 1.0
            stopDisplayLink()
        

        updatePathBasedUponPercentComplete(percent)
    

    func createShape() 
        shapeLayer = CAShapeLayer()
        shapeLayer.fillColor = UIColor.blueColor().CGColor
        shapeLayer.strokeColor = UIColor.clearColor().CGColor
        updatePathBasedUponPercentComplete(0.0)

        view.layer.addSublayer(shapeLayer)
    

    func updatePathBasedUponPercentComplete(percentComplete: CGFloat) 
        shapeLayer.path = pathBasedUponPercentComplete(percentComplete, frame: view.frame).CGPath
    

    func pathBasedUponPercentComplete(percentComplete: CGFloat, frame: CGRect) -> UIBezierPath 
        var path = UIBezierPath()

        for i in 0 ..< startPoints.count 
            let point = CGPoint(
                x: startPoints[i].x + (endPoints[i].x - startPoints[i].x) * percentComplete,
                y: startPoints[i].y + (endPoints[i].y - startPoints[i].y) * percentComplete
            )

            if i == 0 
                path.moveToPoint(point)
             else 
                path.addLineToPoint(point)
            
        
        path.closePath()

        return path
    


【讨论】:

以上是关于如何使 UIBezierPath 中的两条线以不同的速度进行动画处理的主要内容,如果未能解决你的问题,请参考以下文章

来自seaborn facetgrid中不同数据帧的两行

获取每个产品最新的两条线并获取价格和日期

查找表示 GPS 路线的两条线之间的距离(MATLAB、Java、C++ 或 Python)

iOS Swift - 在 UIImage 上画线以保存到文件

CAD2012怎么用多线命令沿一条中线画对称的两条线

居中的两行按钮标题