简体   繁体   English

如何制作这个浮动视图 animation

[英]How to make this floating views animation

How to make this floating views animation in swift uikit with uiviews如何使用 uiviews 在 swift uikit 中制作此浮动视图 animation

  • each views doesn't collapse each other.每个视图不会相互折叠。
  • each view position is random to 16 px and slow moving.每个视图 position 随机到 16 像素并且移动缓慢。

I have tried with this code and calling push function several times我已经尝试使用此代码并多次调用 push function

import UIKit进口 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)
    
}

Set up your views as square, using backroundColor and cornerRadius = height/2 to get the circular colored view look you show in your example.将您的视图设置为正方形,使用 backroundColor 和cornerRadius = height/2 以获得您在示例中显示的圆形彩色视图外观。

Create UIView animations using UIViewPropertyAnimator with a several second duration, where you animate each of your views to a location that is a random distance from it's "anchor" location by < 16 pixels in each dimension.使用具有几秒钟持续时间的UIViewPropertyAnimator创建 UIView 动画,其中您将每个视图动画到一个位置,该位置距其“锚”位置的随机距离在每个维度中 < 16 个像素。 (Google creating animations using UIViewPropertyAnimator . You should be able to find plenty of examples online.) (谷歌使用UIViewPropertyAnimator创建动画。你应该可以在网上找到很多例子。)

Correct me if I'm wrong, but it sounds like there are two tasks here: 1) randomly populate the screen with non-overlapping balls, and 2) let those balls float around such that they bounce off each other on collision.如果我错了,请纠正我,但听起来这里有两个任务:1)用不重叠的球随机填充屏幕,2)让这些球漂浮在周围,以便它们在碰撞时相互反弹。

If the problem is that the animations are jerky, then why not abandon UIDynamicAnimator and write the physics from scratch?如果问题是动画很生涩,那么为什么不放弃UIDynamicAnimator并从头开始编写物理呢? Fiddling with janky features in Apple's libraries can waste countless hours, so just take the sure-fire route.摆弄 Apple 库中的 janky 功能可能会浪费无数时间,所以请采取可靠的方法。 The math is simple enough, plus you can have direct control over frame rate.数学很简单,而且您可以直接控制帧速率。

Keep a list of the balls and their velocities:保留球及其速度的列表:

var balls = [UIView]()
var xvel = [CGFloat]()
var yvel = [CGFloat]()
let fps = 60.0

When creating a ball, randomly generate a position that does not overlap with any other ball:创建球时,随机生成一个不与任何其他球重叠的 position:

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($0, 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)
}

Then run a while loop on a background thread that updates the positions however often you want:然后在后台线程上运行一个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)

Here are the helper functions:以下是辅助函数:

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
}

Result结果

https://imgur.com/a/SoJ0mcL https://imgur.com/a/SoJ0mcL

Let me know if anything goes wrong, I'm more than happy to help.如果出现任何问题,请告诉我,我非常乐意提供帮助。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM