- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
我正在尝试制作一个像波浪或水一样动画的 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
}
最佳答案
一些观察:
您正在 f(_:)
和 z(_:)
中调用 random
。这意味着每次你调用其中任何一个,你每次都会得到一个不同的随机值。所以它会疯狂地跳来跳去。
您想将它们移动到常量,预先定义一次随机参数,然后从那时起使用这些相同的因素。
您正在以每秒 11 帧 (fps) 的速度更新。如果您希望它流畅,请将其保留为设备默认值。
您正在渲染 24 个点。除非您开始使用贝塞尔曲线(如我的 answer to your other question 中所设想的那样),否则将产生非常多 block 的输出。我会提高它(例如,在 iPhone 上,200 会产生相当平滑的波浪)。
在关闭路径之前, View 右下角缺少一条线。
您的波函数不太正确,返回 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
}
注意 the documentation CFAbsoluteTimeGetCurrent
警告我们
Repeated calls to this function do not guarantee monotonically increasing results.
我建议使用 CACurrentMediaTime
相反。
我建议完全丢失 dataPoints
。它没有任何值(value)。我只是继续从 View 的宽度计算 dataPoint
。
我会坚持使用标准的 init(frame:)
。这样,您既可以通过编程方式添加 View ,也可以直接在 Interface Builder 中添加它。
记得在 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
}
}
产生:
关于ios - 如何正确地为 UIBezierPath 设置动画以产生水/波浪效果?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60762757/
我是一名优秀的程序员,十分优秀!