添加新项目时为CAShapelayer内容制作动画 [英] Animate CAShapelayer content when new item is added

查看:21
本文介绍了添加新项目时为CAShapelayer内容制作动画的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我需要像iOS语音备忘录应用程序一样呈现移动的音频波形。这里我维持波形:[INT]均方根波幅。 现在,当新波到来时,将其添加到波形[Int]中,我在CAShapeLayer右侧添加新的UIBezierPath行,并将整个CAShapeLayer平移5个点。

但翻译动画并不是那么流畅。您能建议更好的解决方法吗?

我当前的实现:

override func draw(_ rect: CGRect) {

    shiftWaveform()
    let path: UIBezierPath!
    
    if let ppath = caLayer.path {
        path = UIBezierPath(cgPath: ppath)
    } else {
        path = UIBezierPath()
    }

    guard var wave = waveforms.last else { return }
    
    if (Int(wave) <= 2) {
        wave = 2
    }
    
    count += 1
    let startX = Int(self.bounds.width) + 5*count
    let startY = Int(self.bounds.origin.y) + Int(self.bounds.height)/2
    
    path.move(to: CGPoint(x: startX, y: startY + min(wave, Int(bounds.height/2))))
    path.addLine(to: CGPoint(x: startX, y: startY - min(wave, Int(bounds.height/2))))
    caLayer.path = path.cgPath
}

推荐答案

不要尝试在draw(rect:)中制作动画。这将所有工作放在CPU上,而不是GPU上,并且没有利用iOS中硬件加速的动画。

我建议改用CAShapeLayerCABasicAnimation来为您的路径添加动画。

UIBezierPath中的CGPath安装到CAShapeLayer中,然后创建更改形状层路径属性的CABasicAnimation

获得平滑形状动画的诀窍是使动画中的每一步都有相同数量的控制点。因此,您不应向路径中添加越来越多的点,而应创建包含波形的最后n个点的图形的新路径。

我建议为要绘制图形的n个点保留一个环形缓冲区,并在该环形缓冲区之外构建一个GCPath/UIBezierPath。当您添加更多的点时,较早的点将从环形缓冲区中消失,并且您将始终绘制相同数量的点。

编辑:

好的,您需要比环形缓冲区更简单的东西:让我们将其称为lastNElementsBuffer。它应该允许您添加项目,丢弃最旧的元素,然后始终返回最近添加的元素。

下面是一个简单的实现:

public struct LastNItemsBuffer<T> {
    fileprivate var array: [T?]
    fileprivate var index = 0

    public init(count: Int) {
        array = [T?](repeating: nil, count: count)
    }

    public mutating func clear() {
        forceToValue(value: nil)
    }

    public mutating func forceToValue(value: T?) {
        let count = array.count
        array = [T?](repeating: value, count: count)
    }

    public mutating func write(_ element: T) {
        array[index % array.count] = element
        index += 1
    }
    public func lastNItems() -> [T] {
        var result = [T?]()
        for loop in 0..<array.count {
            result.append(array[(loop+index) % array.count])
        }
        return result.compactMap { $0 }
    }
}

如果您创建了这样一个CGFloat值缓冲区,并用全零填充它,则可以在读取新的波形值时开始将它们保存到其中。

然后您将创建一个动画,该动画将使用值缓冲区和一个新值创建一条路径,然后创建一个将新路径向左移动的动画,从而显示新的点。

我在Github上创建了一个演示项目,展示了这项技术。您可以在此处下载:https://github.com/DuncanMC/LastNItemsBufferGraph.git

以下是一个动画示例:

编辑#2:

听起来您需要的动画风格与我在样例应用程序中所做的略有不同。您应该修改GraphView.swift中的方法buildPath(plusValue:),以从样本值的数组(加上一个可选的新值)中绘制所需的图形样式。示例应用程序的其余部分应该可以按编写的方式工作。

我更新了应用程序,也提供了类似于苹果语音备忘录应用程序的条形图样式:

编辑#3:

another thread中,您说您希望能够允许用户在图表中前后滚动,并建议使用滚动视图来管理该过程。

存在的问题是,您的音频样本可能会持续很长时间间隔,因此整个波形图的图像可能太大,无法保存在内存中。(想象一下一个3分钟的记录曲线图,每1/20秒有一个数据点,每个数据点的图形宽10点,高200点。即3*60*20=3600个数据点。如果你水平使用10个点,在3X Retina显示屏上,每个数据点30个像素宽或10.8万个像素宽,·200个点·3X=600个像素高,或6480万个像素。在3字节/像素(8位/颜色,不含字母)的情况下,这是1.944亿字节的数据,仅用于3分钟的记录。现在让我们假设这是一段2小时的录音。内存不足并崩溃的时间。)

相反,我建议您应该为整个录制保存一个数据点缓冲区,按比例缩小到可以提供1点精度的最小数据类型。您可能会为每个数据点使用单字节。将这些点保存在也包含每秒样本数的结构中。

编写一个函数,该函数将图形数据结构和时间偏移量作为输入,并为该时间偏移量生成一个CGPath,加上或减去足够的数据点,使图形的宽度超过两边的显示窗口。然后,您可以在任一方向上设置图形动画,以便向前或向后播放。您可以实现一个点击手势识别器,让用户来回拖动图形,或滑块,或任何您需要的东西。当您到达当前图形的末尾时,您只需调用您的函数来生成图形的新部分,并将该新部分偏移量显示到正确的屏幕位置。

这篇关于添加新项目时为CAShapelayer内容制作动画的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆