为两点之间的线设置动画 - swift

Animate a line between two points - swift

我有一个名为 CategoryCell 的 class,它使用 UICollectionViewCell。

关于 CellForItemAt 函数:

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {

drawfunction.draw_Footing(withView: cell.view)

绘图将在 cell.view。

draw_Footing functions 是一个绘制一些线条的函数,它位于 drawfunction class which it NSObject. 在同一个 class 中,我有函数调用 animateShape,它可以为两点 (CGpoint) 之间的单条线设置动画。

    func animateShape(view: UIView, p1: CGpoint, p2: CGPoint) {
        
        let shapeLayer = CAShapeLayer()
        shapeLayer.removeFromSuperlayer()
                
            // create whatever path you want
        shapeLayer.fillColor = #colorLiteral(red: 0, green: 0, blue: 0, alpha: 0).cgColor
        shapeLayer.strokeColor = color.cgColor
        shapeLayer.lineWidth = linewidth//CGFloat(1.5)
        shapeLayer.path = path.cgPath
                
            // animate it
        view.layer.addSublayer(shapeLayer)
        let animation = CABasicAnimation(keyPath: "strokeEnd")
        animation.fromValue = 0
        animation.duration = duration//0.5
        shapeLayer.add(animation, forKey: "MyAnimation")

}

我有4个点G1、G2、G3、G4。 我需要在这 4 个点之间设置一条线。 所以,如果我这样做:

animateShape(view, p1: G1, p2: G2)
animateShape(view, p1: G2, p2: G3)
animateShape(view, p1: G3, p2: G4)

所有的线条将同时动画。 我需要首先为 G1 和 G2 之间的线制作动画,完成后,需要为 G2 和 G3 之间的线制作动画,而不是同时。 我试图包括 dispatchQueue,但我不确定,我不知道如何。

有什么建议吗?

问题是,我看不出 path 是如何使用您的积分 p1p2

创建的

无论如何,我假设你的最终目标是在 UICollectionViewCell 中做一个绘图线路径动画,这就是我根据你问题中的给定描述试图实现的。

先上图class:

class DrawFunction: NSObject
{
    weak var shapeLayer: CAShapeLayer?
    
    // Change as you wish
    let duration = 2.0
    
    // Responsible for drawing the lines from any number of points
    func drawFooting(points: [CGPoint])
    {
        guard !points.isEmpty else { return }
        
        // Remove old drawings
        shapeLayer?.removeFromSuperlayer()
        
        let path = UIBezierPath()
        
        // This is actual drawing path using the points
        // I don't see this in your code
        for (index, point) in points.enumerated()
        {
            // first pair of points
            if index == 0
            {
                // Move to the starting point
                path.move(to: point)
                continue
            }
            
            // Draw a line from the previous point to the current
            path.addLine(to: point)
        }
        
        // Create a shape layer to visualize the path
        let shapeLayer = CAShapeLayer()
        shapeLayer.fillColor = UIColor.clear.cgColor
        shapeLayer.strokeColor = randomColor().cgColor
        shapeLayer.lineWidth = 5
        shapeLayer.path = path.cgPath
        
        self.shapeLayer = shapeLayer
    }
    
    // Animate function to be called after shape has been drawn
    // by specifying the view to show this animation in
    func animateShape(in view: UIView)
    {
        if let shapeLayer = shapeLayer
        {
            view.layer.addSublayer(shapeLayer)
            let animation = CABasicAnimation(keyPath: "strokeEnd")
            animation.fromValue = 0
            animation.duration = duration
            shapeLayer.add(animation, forKey: "MyAnimation")
        }
    }
    
    // You can ignore this function, just for convenience
    private func randomColor() -> UIColor
    {
        let red = CGFloat(arc4random_uniform(256)) / 255.0
        let blue = CGFloat(arc4random_uniform(256)) / 255.0
        let green = CGFloat(arc4random_uniform(256)) / 255.0
        
        return UIColor(red: red, green: green, blue: blue, alpha: 1.0)
    }
}

然后设置基本的自定义单元格,没什么特别的,只是为了完整性添加的

// Basic Cell, nothing unique here
class CategoryCell: UICollectionViewCell
{
    static let identifier = "cell"
    
    override init(frame: CGRect)
    {
        super.init(frame: frame)
        configure()
    }
    
    required init?(coder: NSCoder)
    {
        fatalError("init(coder:) has not been implemented")
    }
    
    private func configure()
    {
        contentView.backgroundColor = .lightGray
        contentView.layer.cornerRadius = 5.0
        contentView.clipsToBounds = true
    }
}

视图控制器设置,其中最有趣的部分在 willDisplay cell 函数中

class LineAnimateVC: UICollectionViewController
{
    // Random points to draw lines
    let points = [[CGPoint(x: 0.0, y: 10),
                   CGPoint(x: UIScreen.main.bounds.midX, y: 10),
                   CGPoint(x: UIScreen.main.bounds.midX, y: 50)],
                  
                  [CGPoint(x: 0.0, y: 10),
                   CGPoint(x: UIScreen.main.bounds.midX, y: 10),
                   CGPoint(x: UIScreen.main.bounds.midX, y: 50),
                   CGPoint(x: UIScreen.main.bounds.maxX, y: 50)],
                  
                  [CGPoint(x: 0.0, y: 10),
                   CGPoint(x: UIScreen.main.bounds.midX, y: 10),
                   CGPoint(x: UIScreen.main.bounds.midX, y: 50),
                   CGPoint(x: UIScreen.main.bounds.midX + 40, y: 50),
                   CGPoint(x: UIScreen.main.bounds.midX + 40, y: UIScreen.main.bounds.maxY),
                   CGPoint(x: UIScreen.main.bounds.maxX, y: UIScreen.main.bounds.maxY)],
                  
                  [CGPoint(x: 0.0, y: 10),
                   CGPoint(x: UIScreen.main.bounds.midX, y: 10),
                   CGPoint(x: UIScreen.main.bounds.midX, y: 50)],
                  
                  [CGPoint(x: 0.0, y: 10),
                   CGPoint(x: UIScreen.main.bounds.midX, y: 10),
                   CGPoint(x: UIScreen.main.bounds.midX, y: 50),
                   CGPoint(x: UIScreen.main.bounds.midX + 40, y: 50),
                   CGPoint(x: UIScreen.main.bounds.midX + 40, y: UIScreen.main.bounds.maxY),
                   CGPoint(x: UIScreen.main.bounds.maxX, y: UIScreen.main.bounds.maxY)]
    ]
    
    override func viewDidLoad()
    {
        super.viewDidLoad()
        
        view.backgroundColor = .white
        title = "Line animate"
        
        collectionView.register(CategoryCell.self,
                                forCellWithReuseIdentifier: CategoryCell.identifier)
        
        collectionView.backgroundColor = .white
    }
    
    // Number of cells equals to points we have
    override func collectionView(_ collectionView: UICollectionView,
                                 numberOfItemsInSection section: Int) -> Int
    {
        return points.count
    }
    
    override func collectionView(_ collectionView: UICollectionView,
                                 cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
    {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CategoryCell.identifier,
                                                      for: indexPath) as! CategoryCell
        
        return cell
    }
    
    // Add animation when cell is about to be displayed
    override func collectionView(_ collectionView: UICollectionView,
                                 willDisplay cell: UICollectionViewCell,
                                 forItemAt indexPath: IndexPath)
    {
        let cell = cell as! CategoryCell
        
        // Draw the path and perform the animation
        let drawingFunction = DrawFunction()
        drawingFunction.drawFooting(points: points[indexPath.row])
        drawingFunction.animateShape(in: cell.contentView)
    }
}

为了完整起见,我的流程布局设置

// Basic set up stuff
extension LineAnimateVC: UICollectionViewDelegateFlowLayout
{
    func collectionView(_ collectionView: UICollectionView,
                        layout collectionViewLayout: UICollectionViewLayout,
                        sizeForItemAt indexPath: IndexPath) -> CGSize
    {
        return CGSize(width: collectionView.frame.width, height: 300)
    }
    
    func collectionView(_ collectionView: UICollectionView,
                        layout collectionViewLayout: UICollectionViewLayout,
                        minimumLineSpacingForSectionAt section: Int) -> CGFloat
    {
        return 20
    }
}

这为我提供了集合视图单元格中的动画路径

希望这能给您一些完成任务的想法

更新

基于OP,Xin Lok的评论:

However still did not get what I want, lets say I have path1 = [p1,p2,p3,p4,p5] and path2 = [m1,m2,m3], if I run drawFooting(points: path1) and drawFooting(path2), both of the 2 paths will be animated in the same time , and this what I don't want, I need to complete animation for Path1, and then after finish to proceed with animation of Path2. I tried to insert sleep, but it did not work

根据该评论,我能想到的一种实现方法是我认为关键是 重用和坚持 形状层和路径。

以下是我根据该结论所做的一些更新

首先,我只是做了一个简单的结构,这样我们就可以轻松地创建行

struct Line
{
    var points: [CGPoint] = []
    
    init(_ points: [CGPoint])
    {
        self.points = points
    }
}

然后我创建一些随机线并将它们分组到一个数组中

// Random lines
// Random lines
let line1 = Line([CGPoint(x: 0, y: 10),
                  CGPoint(x: UIScreen.main.bounds.midX, y: 10)])

let line2 = Line([CGPoint(x: 0, y: 70),
                  CGPoint(x: UIScreen.main.bounds.midX, y: 70),
                  CGPoint(x: UIScreen.main.bounds.midX, y: 100)])

let line3 = Line([CGPoint(x: 0, y: 150),
                 CGPoint(x: UIScreen.main.bounds.midX, y: 110),
                 CGPoint(x: UIScreen.main.bounds.maxX, y: 190)])

let line4 = Line([CGPoint(x: 0, y: 210),
                  CGPoint(x: UIScreen.main.bounds.maxX / 4, y: 235),
                  CGPoint(x: UIScreen.main.bounds.maxX * 0.75, y: 220),
                  CGPoint(x: UIScreen.main.bounds.maxX,
                          y: UIScreen.main.bounds.maxY)])

var lineGroups: [[Line]] = []

private func setLines()
{
    // First cell, it should draw lines in the order 3 -> 1 -> 2
    // Second cell, in the order 4 -> 3 -> 2 -> 1
    lineGroups = [[line3, line1, line2],
                  [line4, line3, line2, line1]]
}

重要的是注意每个数组中的线条顺序,因为这是绘制它们的顺序

在绘图 class 中,我做了一些更改以保留 CAShapeLayerpath

在评论中特别提及 jrturton,以建议 CGMutablePath 并简化路径创建。

class DrawFunction: NSObject
{
    weak var shapeLayer: CAShapeLayer?
    var path: CGMutablePath?
    
    // Change as you wish
    let duration = 5.0
    
    // Responsible for drawing the lines from any number of points
    func drawFooting(line: Line)
    {
        var shapeLayer = CAShapeLayer()
        
        if self.shapeLayer != nil
        {
            shapeLayer = self.shapeLayer!
        }
        
        if path == nil
        {
            path = CGMutablePath()
        }
        
        // Thank you @jrturton for this
        path?.addLines(between: line.points)
        
        shapeLayer.fillColor = UIColor.clear.cgColor
        shapeLayer.strokeColor = randomColor().cgColor
        shapeLayer.lineWidth = 5
        shapeLayer.path = path
        
        self.shapeLayer = shapeLayer
    }
    
    // Animate function to be called after shape has been drawn
    // by specifying the view to show this animation in
    func animateShape(in view: UIView)
    {
        if let shapeLayer = shapeLayer
        {
            view.layer.addSublayer(shapeLayer)
            let animation = CABasicAnimation(keyPath: "strokeEnd")
            animation.fromValue = 0
            animation.duration = duration
            shapeLayer.add(animation, forKey: "MyAnimation")
        }
    }
    
    // You can ignore this function, just for convenience
    private func randomColor() -> UIColor
    {
        let red = CGFloat(arc4random_uniform(256)) / 255.0
        let blue = CGFloat(arc4random_uniform(256)) / 255.0
        let green = CGFloat(arc4random_uniform(256)) / 255.0
        
        return UIColor(red: red, green: green, blue: blue, alpha: 1.0)
    }
}

然后对 collectionview 单元格配置进行一些小改动

// Number of cells equals to lines we have
override func collectionView(_ collectionView: UICollectionView,
                             numberOfItemsInSection section: Int) -> Int
{
    return lineGroups.count
}

override func collectionView(_ collectionView: UICollectionView,
                             cellForItemAt indexPath: IndexPath) -> UICollectionViewCell
{
    let cell
        = collectionView.dequeueReusableCell(withReuseIdentifier: CategoryCell.identifier,
                                                  for: indexPath) as! CategoryCell
    
    return cell
}

// Add animation when cell is about to be displayed
override func collectionView(_ collectionView: UICollectionView,
                             willDisplay cell: UICollectionViewCell,
                             forItemAt indexPath: IndexPath)
{
    let cell = cell as! CategoryCell
    
    let lines = lineGroups[indexPath.item]
    
    // Draw the path and perform the animation
    let drawingFunction = DrawFunction()
    
    for line in lines
    {
        drawingFunction.drawFooting(line: line)
    }
    
    drawingFunction.animateShape(in: cell.contentView)
}

现在,为了方便起见,请记住它们的绘制顺序:

First cell, it should draw lines in the order 3 -> 1 -> 2
Second cell, in the order 4 -> 3 -> 2 -> 1

最终结果: