如何制作这个浮动视图动画
How to make this floating views animation
如何在 swift 带有 uiviews 的 uikit 中制作这个浮动视图动画
- 每个视图不会相互折叠。
- 每个视图位置随机到 16 像素并缓慢移动。
我试过这个代码
并多次调用推送功能
导入 UIKit
lazy var collision: UICollisionBehavior = {
let collision = UICollisionBehavior()
collision.translatesReferenceBoundsIntoBoundary = true
collision.collisionDelegate = self
collision.collisionMode = .everything
return collision
}()
lazy var itemBehaviour: UIDynamicItemBehavior = {
let behaviou = UIDynamicItemBehavior()
behaviou.allowsRotation = false
return behaviou
}()
func addingPushBehaviour(onView view: UIDynamicItem ,animationAdded: Bool = false) {
let push = UIPushBehavior(items: [view], mode: .instantaneous)
push.angle = CGFloat(arc4random())
push.magnitude = 0.005
push.action = { [weak self] in
self?.removeChildBehavior(push)
}
addChildBehavior(push)
}
func addItem(withItem item: UIDynamicItem) {
collision.addItem(item)
itemBehaviour.addItem(item)
addingPushBehaviour(onView: item)
}
override init() {
super.init()
addChildBehavior(collision)
addChildBehavior(itemBehaviour)
}
var mainView: UIView?
convenience init(animator: UIDynamicAnimator , onView: UIView) {
self.init()
self.mainView = onView
animator.addBehavior(self)
}
将您的视图设置为正方形,使用 backroundColor 和 cornerRadius = height/2 以获得示例中显示的圆形彩色视图外观。
使用 UIViewPropertyAnimator
创建持续时间为几秒的 UIView 动画,您可以将每个视图动画化到一个位置,该位置与其“锚点”位置的随机距离在每个维度上小于 16 像素。 (Google 使用 UIViewPropertyAnimator
创建动画。您应该可以在网上找到大量示例。)
如果我错了请纠正我,但听起来这里有两个任务:1) 用不重叠的球随机填充屏幕,以及 2) 让这些球四处漂浮,使它们相互反弹关于碰撞。
如果问题是动画不稳定,那为什么不放弃 UIDynamicAnimator
并从头开始编写物理呢?摆弄 Apple 库中的简陋功能可能会浪费无数时间,因此请采取万无一失的方式。数学很简单,而且您可以直接控制帧速率。
保留球及其速度的列表:
var balls = [UIView]()
var xvel = [CGFloat]()
var yvel = [CGFloat]()
let fps = 60.0
创建球时,随机生成一个不与任何其他球重叠的位置:
for _ in 0 ..< 6 {
let ball = UIView(frame: CGRect(x: 0, y: 0, width: ballSize, height: ballSize))
ball.layer.cornerRadius = ballSize / 2
ball.backgroundColor = .green
// Try random positions until we find something valid
while true {
ball.frame.origin = CGPoint(x: .random(in: 0 ... view.frame.width - ballSize),
y: .random(in: 0 ... view.frame.height - ballSize))
// Check for collisions
if balls.allSatisfy({ !doesCollide([=11=], ball) }) { break }
}
view.addSubview(ball)
balls.append(ball)
// Randomly generate a direction
let theta = CGFloat.random(in: -.pi ..< .pi)
let speed: CGFloat = 20 // Pixels per second
xvel.append(cos(theta) * speed)
yvel.append(sin(theta) * speed)
}
然后 运行 在后台线程上进行 while
循环,根据需要更新位置:
let timer = Timer(fire: .init(), interval: 1 / fps, repeats: true, block: { _ in
// Apply movement
for i in 0 ..< self.balls.count {
self.move(ball: i)
}
// Check for collisions
for i in 0 ..< self.balls.count {
for j in 0 ..< self.balls.count {
if i != j && self.doesCollide(self.balls[i], self.balls[j]) {
// Calculate the normal vector between the two balls
let nx = self.balls[j].center.x - self.balls[i].center.x
let ny = self.balls[j].center.y - self.balls[i].center.y
// Reflect both balls
self.reflect(ball: i, nx: nx, ny: ny)
self.reflect(ball: j, nx: -nx, ny: -ny)
// Move both balls out of each other's hitboxes
self.move(ball: i)
self.move(ball: j)
}
}
}
// Check for boundary collision
for (i, ball) in self.balls.enumerated() {
if ball.frame.minX < 0 { self.balls[i].frame.origin.x = 0; self.xvel[i] *= -1 }
if ball.frame.maxX > self.view.frame.width { self.balls[i].frame.origin.x = self.view.frame.width - ball.frame.width; self.xvel[i] *= -1 }
if ball.frame.minY < 0 { self.balls[i].frame.origin.y = 0; self.yvel[i] *= -1 }
if ball.frame.maxY > self.view.frame.height { self.balls[i].frame.origin.y = self.view.frame.height - ball.frame.height; self.yvel[i] *= -1 }
}
})
RunLoop.current.add(timer, forMode: .common)
以下是辅助函数:
func move(ball i: Int) {
balls[i].frame = balls[i].frame.offsetBy(dx: self.xvel[i] / CGFloat(fps), dy: self.yvel[i] / CGFloat(fps))
}
func reflect(ball: Int, nx: CGFloat, ny: CGFloat) {
// Normalize the normal
let normalMagnitude = sqrt(nx * nx + ny * ny)
let nx = nx / normalMagnitude
let ny = ny / normalMagnitude
// Calculate the dot product of the ball's velocity with the normal
let dot = xvel[ball] * nx + yvel[ball] * ny
// Use formula to calculate the reflection. Explanation: https://chicio.medium.com/how-to-calculate-the-reflection-vector-7f8cab12dc42
let rx = -(2 * dot * nx - xvel[ball])
let ry = -(2 * dot * ny - yvel[ball])
// Only apply the reflection if the ball is heading in the same direction as the normal
if xvel[ball] * nx + yvel[ball] * ny >= 0 {
xvel[ball] = rx
yvel[ball] = ry
}
}
func doesCollide(_ a: UIView, _ b: UIView) -> Bool {
let radius = a.frame.width / 2
let distance = sqrt(pow(a.center.x - b.center.x, 2) + pow(a.center.y - b.center.y, 2))
return distance <= 2 * radius
}
结果
如果有任何问题,请告诉我,我非常乐意提供帮助。
如何在 swift 带有 uiviews 的 uikit 中制作这个浮动视图动画
- 每个视图不会相互折叠。
- 每个视图位置随机到 16 像素并缓慢移动。
我试过这个代码 并多次调用推送功能
导入 UIKit
lazy var collision: UICollisionBehavior = {
let collision = UICollisionBehavior()
collision.translatesReferenceBoundsIntoBoundary = true
collision.collisionDelegate = self
collision.collisionMode = .everything
return collision
}()
lazy var itemBehaviour: UIDynamicItemBehavior = {
let behaviou = UIDynamicItemBehavior()
behaviou.allowsRotation = false
return behaviou
}()
func addingPushBehaviour(onView view: UIDynamicItem ,animationAdded: Bool = false) {
let push = UIPushBehavior(items: [view], mode: .instantaneous)
push.angle = CGFloat(arc4random())
push.magnitude = 0.005
push.action = { [weak self] in
self?.removeChildBehavior(push)
}
addChildBehavior(push)
}
func addItem(withItem item: UIDynamicItem) {
collision.addItem(item)
itemBehaviour.addItem(item)
addingPushBehaviour(onView: item)
}
override init() {
super.init()
addChildBehavior(collision)
addChildBehavior(itemBehaviour)
}
var mainView: UIView?
convenience init(animator: UIDynamicAnimator , onView: UIView) {
self.init()
self.mainView = onView
animator.addBehavior(self)
}
将您的视图设置为正方形,使用 backroundColor 和 cornerRadius = height/2 以获得示例中显示的圆形彩色视图外观。
使用 UIViewPropertyAnimator
创建持续时间为几秒的 UIView 动画,您可以将每个视图动画化到一个位置,该位置与其“锚点”位置的随机距离在每个维度上小于 16 像素。 (Google 使用 UIViewPropertyAnimator
创建动画。您应该可以在网上找到大量示例。)
如果我错了请纠正我,但听起来这里有两个任务:1) 用不重叠的球随机填充屏幕,以及 2) 让这些球四处漂浮,使它们相互反弹关于碰撞。
如果问题是动画不稳定,那为什么不放弃 UIDynamicAnimator
并从头开始编写物理呢?摆弄 Apple 库中的简陋功能可能会浪费无数时间,因此请采取万无一失的方式。数学很简单,而且您可以直接控制帧速率。
保留球及其速度的列表:
var balls = [UIView]()
var xvel = [CGFloat]()
var yvel = [CGFloat]()
let fps = 60.0
创建球时,随机生成一个不与任何其他球重叠的位置:
for _ in 0 ..< 6 {
let ball = UIView(frame: CGRect(x: 0, y: 0, width: ballSize, height: ballSize))
ball.layer.cornerRadius = ballSize / 2
ball.backgroundColor = .green
// Try random positions until we find something valid
while true {
ball.frame.origin = CGPoint(x: .random(in: 0 ... view.frame.width - ballSize),
y: .random(in: 0 ... view.frame.height - ballSize))
// Check for collisions
if balls.allSatisfy({ !doesCollide([=11=], ball) }) { break }
}
view.addSubview(ball)
balls.append(ball)
// Randomly generate a direction
let theta = CGFloat.random(in: -.pi ..< .pi)
let speed: CGFloat = 20 // Pixels per second
xvel.append(cos(theta) * speed)
yvel.append(sin(theta) * speed)
}
然后 运行 在后台线程上进行 while
循环,根据需要更新位置:
let timer = Timer(fire: .init(), interval: 1 / fps, repeats: true, block: { _ in
// Apply movement
for i in 0 ..< self.balls.count {
self.move(ball: i)
}
// Check for collisions
for i in 0 ..< self.balls.count {
for j in 0 ..< self.balls.count {
if i != j && self.doesCollide(self.balls[i], self.balls[j]) {
// Calculate the normal vector between the two balls
let nx = self.balls[j].center.x - self.balls[i].center.x
let ny = self.balls[j].center.y - self.balls[i].center.y
// Reflect both balls
self.reflect(ball: i, nx: nx, ny: ny)
self.reflect(ball: j, nx: -nx, ny: -ny)
// Move both balls out of each other's hitboxes
self.move(ball: i)
self.move(ball: j)
}
}
}
// Check for boundary collision
for (i, ball) in self.balls.enumerated() {
if ball.frame.minX < 0 { self.balls[i].frame.origin.x = 0; self.xvel[i] *= -1 }
if ball.frame.maxX > self.view.frame.width { self.balls[i].frame.origin.x = self.view.frame.width - ball.frame.width; self.xvel[i] *= -1 }
if ball.frame.minY < 0 { self.balls[i].frame.origin.y = 0; self.yvel[i] *= -1 }
if ball.frame.maxY > self.view.frame.height { self.balls[i].frame.origin.y = self.view.frame.height - ball.frame.height; self.yvel[i] *= -1 }
}
})
RunLoop.current.add(timer, forMode: .common)
以下是辅助函数:
func move(ball i: Int) {
balls[i].frame = balls[i].frame.offsetBy(dx: self.xvel[i] / CGFloat(fps), dy: self.yvel[i] / CGFloat(fps))
}
func reflect(ball: Int, nx: CGFloat, ny: CGFloat) {
// Normalize the normal
let normalMagnitude = sqrt(nx * nx + ny * ny)
let nx = nx / normalMagnitude
let ny = ny / normalMagnitude
// Calculate the dot product of the ball's velocity with the normal
let dot = xvel[ball] * nx + yvel[ball] * ny
// Use formula to calculate the reflection. Explanation: https://chicio.medium.com/how-to-calculate-the-reflection-vector-7f8cab12dc42
let rx = -(2 * dot * nx - xvel[ball])
let ry = -(2 * dot * ny - yvel[ball])
// Only apply the reflection if the ball is heading in the same direction as the normal
if xvel[ball] * nx + yvel[ball] * ny >= 0 {
xvel[ball] = rx
yvel[ball] = ry
}
}
func doesCollide(_ a: UIView, _ b: UIView) -> Bool {
let radius = a.frame.width / 2
let distance = sqrt(pow(a.center.x - b.center.x, 2) + pow(a.center.y - b.center.y, 2))
return distance <= 2 * radius
}
结果
如果有任何问题,请告诉我,我非常乐意提供帮助。