gpt4 book ai didi

ios - 如何正确地为 UIBezierPath 设置动画以产生水/波浪效果?

转载 作者:行者123 更新时间:2023-12-01 18:35:07 27 4
gpt4 key购买 nike

我正在尝试制作一个像波浪或水一样动画的 UIBezierPath。类似于这样的东西。 https://dribbble.com/shots/3994990-Waves-Loading-Animation

我将此动画用作一种带有数据点 (0-100) 的折线图。我已经正确绘制了路径,但无法正确设置它的动画。

目前看起来像这样https://imgur.com/a/QQX4DGo随着运动 super 脊状/快速

let dataPoints: [Double]

var displayLink: CADisplayLink?
var startTime: CFAbsoluteTime?

let background: UIView = {
let view = UIView()
return view
}()

let shapeLayer: CAShapeLayer = {
let layer = CAShapeLayer()
return layer
}()

init(frame: CGRect, data: [Double], precip: [String]) {
self.dataPoints = data
super.init(frame: frame)
addSubview(background)
background.anchor(top: topAnchor, left: leftAnchor, bottom: bottomAnchor, right: rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)
}

override func layoutSubviews() {
super.layoutSubviews()
background.layer.addSublayer(shapeLayer)
shapeLayer.strokeColor = UIColor.waterColor.cgColor
shapeLayer.fillColor = UIColor.waterColor.cgColor
startDisplayLink()
}

func wave(at elapsed: Double) -> UIBezierPath {
let maxX = bounds.width
let maxY = bounds.height

func f(_ y: Double) -> CGFloat {
let random = CGFloat.random(in: 1.0...5.0)
return CGFloat(y) + sin(CGFloat(elapsed/2) * random * .pi)
}

func z(_ x: CGFloat) -> CGFloat {
let random = CGFloat.random(in: 1.0...2.0)

let position = Int.random(in: 0...1)
if(position == 0) {
return x + random
} else {
return x - random
}
}

let path = UIBezierPath()
path.move(to: CGPoint(x: 0, y: maxY))

let steps = bounds.width/CGFloat(24)
var start: CGFloat = steps
for i in 0..<24 {

let x = z(start)
let y = maxY - f(dataPoints[i]*100)

let point = CGPoint(x: x, y: y)
path.addLine(to: point)

start+=steps
}
path.close()
return path
}

func startDisplayLink() {
startTime = CFAbsoluteTimeGetCurrent()
displayLink?.invalidate()
displayLink = CADisplayLink(target: self, selector:#selector(handleDisplayLink(_:)))
displayLink?.add(to: .current, forMode: .common)
displayLink?.preferredFramesPerSecond = 11
}

func stopDisplayLink() {
displayLink?.invalidate()
displayLink = nil
}

@objc func handleDisplayLink(_ displayLink: CADisplayLink) {
let elapsed = CFAbsoluteTimeGetCurrent() - startTime!
shapeLayer.path = wave(at: elapsed).cgPath
}

最佳答案

一些观察:

  1. 您正在 f(_:)z(_:) 中调用 random。这意味着每次你调用其中任何一个,你每次都会得到一个不同的随机值。所以它会疯狂地跳来跳去。

    您想将它们移动到常量,预先定义一次随机参数,然后从那时起使用这些相同的因素。

  2. 您正在以每秒 11 帧 (fps) 的速度更新。如果您希望它流畅,请将其保留为设备默认值。

  3. 您正在渲染 24 个点。除非您开始使用贝塞尔曲线(如我的 answer to your other question 中所设想的那样),否则将产生非常多 block 的输出。我会提高它(例如,在 iPhone 上,200 会产生相当平滑的波浪)。

  4. 在关闭路径之前, View 右下角缺少一条线。

  5. 您的波函数不太正确,返回 x 加上正弦函数(将产生对角线波)。另外,如果你想让它感觉像波浪,我不仅会根据耗时改变波浪的振幅,还会改变整体潮汐高度。例如

    let maxAmplitude: CGFloat = 0.1
    let maxTidalVariation: CGFloat = 0.1
    let amplitudeOffset = CGFloat.random(in: -0.5 ... 0.5)
    let amplitudeChangeSpeedFactor = CGFloat.random(in: 4 ... 8)

    let defaultTidalHeight: CGFloat = 0.50
    let saveSpeedFactor = CGFloat.random(in: 4 ... 8)

    func wave(at elapsed: Double) -> UIBezierPath {
    func f(_ x: Double) -> CGFloat {
    let elapsed = CGFloat(elapsed)
    let amplitude = maxAmplitude * abs(fmod(CGFloat(elapsed/2), 3) - 1.5)
    let variation = sin((elapsed + amplitudeOffset) / amplitudeChangeSpeedFactor) * maxTidalVariation
    let value = sin((elapsed / saveSpeedFactor + CGFloat(x)) * 4 * .pi)
    return value * amplitude / 2 * bounds.height + (defaultTidalHeight + variation) * bounds.height
    }

    let path = UIBezierPath()
    path.move(to: CGPoint(x: bounds.minX, y: bounds.maxY))

    for dataPoint in dataPoints {
    let x = CGFloat(dataPoint) * bounds.width + bounds.minX
    let y = bounds.maxY - f(dataPoint)
    let point = CGPoint(x: x, y: y)
    path.addLine(to: point)
    }
    path.addLine(to: CGPoint(x: bounds.maxX, y: bounds.maxY))
    path.close()
    return path
    }
  6. 注意 the documentation CFAbsoluteTimeGetCurrent 警告我们

    Repeated calls to this function do not guarantee monotonically increasing results.

    我建议使用 CACurrentMediaTime相反。

  7. 我建议完全丢失 dataPoints。它没有任何值(value)。我只是继续从 View 的宽度计算 dataPoint

  8. 我会坚持使用标准的 init(frame:)。这样,您既可以通过编程方式添加 View ,也可以直接在 Interface Builder 中添加它。

  9. 记得在 deinit 中使您的显示链接无效。

因此:

@IBDesignable
class WavyView: UIView {

private weak var displayLink: CADisplayLink?
private var startTime: CFTimeInterval = 0
private let maxAmplitude: CGFloat = 0.1
private let maxTidalVariation: CGFloat = 0.1
private let amplitudeOffset = CGFloat.random(in: -0.5 ... 0.5)
private let amplitudeChangeSpeedFactor = CGFloat.random(in: 4 ... 8)

private let defaultTidalHeight: CGFloat = 0.50
private let saveSpeedFactor = CGFloat.random(in: 4 ... 8)

private lazy var background: UIView = {
let background = UIView()
background.translatesAutoresizingMaskIntoConstraints = false
background.layer.addSublayer(shapeLayer)
return background
}()

private let shapeLayer: CAShapeLayer = {
let shapeLayer = CAShapeLayer()
shapeLayer.strokeColor = UIColor.waterColor.cgColor
shapeLayer.fillColor = UIColor.waterColor.cgColor
return shapeLayer
}()

override init(frame: CGRect = .zero) {
super.init(frame: frame)

configure()
}

required init?(coder: NSCoder) {
super.init(coder: coder)

configure()
}

override func willMove(toSuperview newSuperview: UIView?) {
super.willMove(toSuperview: newSuperview)

if newSuperview == nil {
displayLink?.invalidate()
}
}

override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()

shapeLayer.path = wave(at: 0)?.cgPath
}
}

private extension WavyView {

func configure() {
addSubview(background)
background.anchor(top: topAnchor, left: leftAnchor, bottom: bottomAnchor, right: rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)

startDisplayLink()
}

func wave(at elapsed: Double) -> UIBezierPath? {
guard bounds.width > 0, bounds.height > 0 else { return nil }

func f(_ x: CGFloat) -> CGFloat {
let elapsed = CGFloat(elapsed)
let amplitude = maxAmplitude * abs(fmod(elapsed / 2, 3) - 1.5)
let variation = sin((elapsed + amplitudeOffset) / amplitudeChangeSpeedFactor) * maxTidalVariation
let value = sin((elapsed / saveSpeedFactor + x) * 4 * .pi)
return value * amplitude / 2 * bounds.height + (defaultTidalHeight + variation) * bounds.height
}

let path = UIBezierPath()
path.move(to: CGPoint(x: bounds.minX, y: bounds.maxY))

let count = Int(bounds.width / 10)

for step in 0 ... count {
let dataPoint = CGFloat(step) / CGFloat(count)
let x = dataPoint * bounds.width + bounds.minX
let y = bounds.maxY - f(dataPoint)
let point = CGPoint(x: x, y: y)
path.addLine(to: point)
}
path.addLine(to: CGPoint(x: bounds.maxX, y: bounds.maxY))
path.close()
return path
}

func startDisplayLink() {
startTime = CACurrentMediaTime()
displayLink?.invalidate()
let displayLink = CADisplayLink(target: self, selector: #selector(handleDisplayLink(_:)))
displayLink.add(to: .main, forMode: .common)
self.displayLink = displayLink
}

func stopDisplayLink() {
displayLink?.invalidate()
}

@objc func handleDisplayLink(_ displayLink: CADisplayLink) {
let elapsed = CACurrentMediaTime() - startTime
shapeLayer.path = wave(at: elapsed)?.cgPath
}
}

产生:

enter image description here

关于ios - 如何正确地为 UIBezierPath 设置动画以产生水/波浪效果?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60762757/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com