Как заставить Spring Animations с ограничениями вести себя правильно?

Пытаюсь заставить весеннюю анимацию работать правильно. Я сделал репозиторий git. В основном красный вид ограничен следующим образом:

let ac = brokenView.widthAnchor.constraint(equalTo: brokenView.heightAnchor, multiplier: 9/16)
let xc = brokenView.centerXAnchor.constraint(equalTo: animView.centerXAnchor)
let yc = brokenView.centerYAnchor.constraint(equalTo: animView.centerYAnchor)
let widthC = brokenView.widthAnchor.constraint(equalTo: animView.widthAnchor)
widthC.priority = .defaultLow
let gewc = brokenView.widthAnchor.constraint(greaterThanOrEqualTo: animView.widthAnchor)
let geHC = brokenView.heightAnchor.constraint(greaterThanOrEqualTo: animView.heightAnchor)
geHC.priority = .required

Синий вид начинается с соотношения сторон != 9/16 -> анимируется до 9/16. Я хотел бы видеть, что когда синий вид становится слишком высоким, красный начинает толстеть. Вместо этого он просто придерживается привязки ширины с более низким приоритетом. Любой совет приветствуется.

Репо здесь

Код, который не так полезен, как мог бы быть без файла раскадровки:

import UIKit


class MyViewController: UIViewController {

    @IBOutlet weak var animView: UIView!
    @IBOutlet weak var brokenView: UIView!
    
    var isBig = false
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        brokenView.border(3, color: .red)
        animView.border(3, color: .blue)
        
        animView.frame = CGRect(x: view.frame.midX - 120, y: view.frame.midY - 180, width: 240, height: 360)
        
        brokenView.translatesAutoresizingMaskIntoConstraints = false
        
        let ac = brokenView.widthAnchor.constraint(equalTo: brokenView.heightAnchor, multiplier: 9/16)
        let xc = brokenView.centerXAnchor.constraint(equalTo: animView.centerXAnchor)
        let yc = brokenView.centerYAnchor.constraint(equalTo: animView.centerYAnchor)
        let widthC = brokenView.widthAnchor.constraint(equalTo: animView.widthAnchor)
        widthC.priority = .defaultLow
        let gewc = brokenView.widthAnchor.constraint(greaterThanOrEqualTo: animView.widthAnchor)
        let geHC = brokenView.heightAnchor.constraint(greaterThanOrEqualTo: animView.heightAnchor)
        geHC.priority = .required
        
        NSLayoutConstraint.activate([
            ac,
            xc,
            yc,
            widthC,
            gewc,
            geHC
        ])
        
        let gr = UITapGestureRecognizer(target: self, action: #selector(MyViewController.handleTap(_:)))
        view.addGestureRecognizer(gr)
    }

    
    @objc func handleTap(_ gr: UITapGestureRecognizer) {
        let newFrame: CGRect
        if isBig {
            newFrame = CGRect(x: view.frame.midX - 120, y: view.frame.midY - 180, width: 240, height: 360)
        } else {
            newFrame = CGRect(x: view.frame.midX - (270/2), y: view.frame.midY - (480/2), width: 270, height: 480)
        }
        let timing = UISpringTimingParameters.init(dampingRatio: 0.01, frequencyResponse: 10)
        let anim = UIViewPropertyAnimator(duration: 10, timingParameters: timing)
        anim.addAnimations {
            self.animView.frame = newFrame
        }
        anim.addCompletion { _ in
            self.isBig = !self.isBig
        }
        anim.startAnimation()
    }

}

extension UIView {
    
    func border(_ width: Float, color: UIColor) {
        layer.borderColor = color.cgColor
        layer.borderWidth = CGFloat(width)
    }
}

extension UISpringTimingParameters {
    public convenience init(dampingRatio: CGFloat, frequencyResponse: CGFloat) {
        
        let mass = 1 as CGFloat
        let stiffness = pow(2 * .pi / frequencyResponse, 2) * mass
        let damping = 4 * .pi * dampingRatio * mass / frequencyResponse
        
        self.init(mass: mass, stiffness: stiffness, damping: damping, initialVelocity: .zero)
    }
}

??? Почему проголосовали за закрытие из-за отсутствия ясности?? Проблема четко сформулирована (ограничения при анимации игнорируются), есть и репозиторий кода, и изображение.

nickneedsaname 26.06.2024 20:30

Весь соответствующий код должен быть указан в самом вопросе, а не в ссылке на репозиторий git.

Paulw11 26.06.2024 23:45

@matt, это буквально не так....

nickneedsaname 27.06.2024 01:55

@Paulw11, как это работает с файлами раскадровки? Разве SO просто не решает проблемы, более сложные, чем один файл?

nickneedsaname 27.06.2024 02:06

Мы можем сделать некоторые выводы из файла раскадровки. В этом случае вы можете легко создавать представления и ограничения полностью в коде. Это было бы лучше, чем раскадровка, потому что тогда мы могли бы легко увидеть, какие ограничения у вас есть. Репозиторий GitHub не поможет, если мы не захотим загрузить его и открыть проект.

Paulw11 27.06.2024 02:57
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
5
71
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Не совсем понятно, какова ваша конечная цель, но...

UIViewPropertyAnimator с UISpringTimingParameters — это визуальный эффект... он колеблется «пружинкой», пока не достигнет своего «конечного пункта назначения».

Вот как это может выглядеть:

он не меняет постоянно рамку обзора.

Вот ваш код, измененный, чтобы мы могли его объяснить:

class SomeViewController: UIViewController {
    
    let blueAnimView: UIView = UIView()
    let redFollowView: UIView = UIView()
    
    let smallFrameView: UIView = UIView()
    let bigFrameView: UIView = UIView()
    
    var bigRect: CGRect = .zero
    var smallRect: CGRect = .zero
    
    var isBig = false
    
    override func viewDidLoad() {
        super.viewDidLoad()

        // define the small and big frames (rects)
        smallRect = CGRect(x: view.frame.midX - 120, y: view.frame.midY - 180, width: 240, height: 360)
        bigRect = CGRect(x: view.frame.midX - (270/2), y: view.frame.midY - (480/2), width: 270, height: 480)
        
        // 3-point red border
        redFollowView.border(3, color: .red)

        // let's make blue view's border thicker to make it easier to see what's going on
        blueAnimView.border(8, color: .blue)

        view.addSubview(bigFrameView)
        view.addSubview(smallFrameView)
        view.addSubview(blueAnimView)
        view.addSubview(redFollowView)
        
        // set the "start" frame of the blue view
        blueAnimView.frame = smallRect

        redFollowView.translatesAutoresizingMaskIntoConstraints = false

        // for simplicity, let's constrain redView to blueView on all 4 sides
        NSLayoutConstraint.activate([
            redFollowView.topAnchor.constraint(equalTo: blueAnimView.topAnchor),
            redFollowView.leadingAnchor.constraint(equalTo: blueAnimView.leadingAnchor),
            redFollowView.trailingAnchor.constraint(equalTo: blueAnimView.trailingAnchor),
            redFollowView.bottomAnchor.constraint(equalTo: blueAnimView.bottomAnchor),
        ])
        
        let gr = UITapGestureRecognizer(target: self, action: #selector(MyViewController.handleTap(_:)))
        view.addGestureRecognizer(gr)

        // so we can see the small and big frames
        bigFrameView.backgroundColor = .systemYellow
        smallFrameView.backgroundColor = .yellow
        bigFrameView.frame = bigRect
        smallFrameView.frame = smallRect
    }
    
    @objc func handleTap(_ gr: UITapGestureRecognizer) {

        let newFrame: CGRect
        if isBig {
            newFrame = smallRect
        } else {
            newFrame = bigRect
        }
        
        // slow spring
        let timing = UISpringTimingParameters.init(dampingRatio: 0.01, frequencyResponse: 10)
        
        // quick spring
        //let timing = UISpringTimingParameters.init(dampingRatio: 0.1, frequencyResponse: 0.5)
        
        let anim = UIViewPropertyAnimator(duration: 10, timingParameters: timing)
        anim.addAnimations {
            self.blueAnimView.frame = newFrame
        }
        anim.addCompletion { _ in
            print("completion")
            self.isBig = !self.isBig
        }
        
        anim.startAnimation()
    }
    
}

extension UIView {
    
    func border(_ width: Float, color: UIColor) {
        layer.borderColor = color.cgColor
        layer.borderWidth = CGFloat(width)
    }
}

extension UISpringTimingParameters {
    public convenience init(dampingRatio: CGFloat, frequencyResponse: CGFloat) {
        
        let mass = 1 as CGFloat
        let stiffness = pow(2 * .pi / frequencyResponse, 2) * mass
        let damping = 4 * .pi * dampingRatio * mass / frequencyResponse
        
        self.init(mass: mass, stiffness: stiffness, damping: damping, initialVelocity: .zero)
    }
}

Первый,

  • давайте назовем виды blueAnimView и redFollowView
  • давайте объявим маленькие и большие рамки (прямоугольники)
  • давайте пока забудем о соотношении сторон 9:16 и ограничим redFollowView всеми 4 сторонами blueAnimView

Давайте также добавим представления Yellow и SystemYellow, чтобы мы могли видеть маленькие и большие рамки:

Когда мы добавим красный и синий виды, поначалу это будет выглядеть так:

Теперь, как только мы вызываем anim.startAnimation(), мы видим, что ограничения redFollowView заставляют его соответствовать кадру «конечного пункта назначения» blueAnimView:

когда происходит пружинная анимация, redFollowView не меняет размер, потому что рамка blueAnimView не меняет размер во время анимации:

Здесь замедлено для ясности:

Если вы хотите, чтобы красный вид следовал за синим во время анимации, вам придется применить другой подход.


Редактировать

Чтобы получить желаемый эффект, синий вид использует анимацию Spring для изменения размера, а красный (подвид) соответствует либо ширине, либо высоте, сохраняя при этом соотношение 9:16 (фактически «подгонка с обратным соотношением сторон»):

Нам нужно отказаться от идеи ограничения взглядов (см. обсуждение выше).

Вместо этого мы:

  • используйте явные рамки
  • реализовать CADisplayLink
  • в обратном вызове ссылки на дисплей вычислите новый кадр 9:16 для красного представления на основе кадра уровня представления синего представления.

Пример кода (без соединений @IBOutlet или @IBAction)... просто назначьте новый пустой контроллер представления UsingDisplayLinkVC:

class UsingDisplayLinkVC: UIViewController {
    
    let blueAnimView: UIView = UIView()
    let redFollowView: UIView = UIView()
    
    let smallFrameView: UIView = UIView()
    let bigFrameView: UIView = UIView()
    
    var bigRect: CGRect = .zero
    var smallRect: CGRect = .zero
    
    var isBig = false
    
    var displayLink: CADisplayLink!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        view.backgroundColor = .systemBackground
        
        // define the small and big frames (rects)
        smallRect = CGRect(x: view.frame.midX - 120, y: view.frame.midY - 180, width: 240, height: 360)
        bigRect = CGRect(x: view.frame.midX - (270/2), y: view.frame.midY - (480/2), width: 270, height: 480)
        
        // 3-point red border
        redFollowView.border(8, color: .red)
        
        // let's make blue view's border thicker to make it easier to see what's going on
        blueAnimView.border(3, color: .blue)
        
        view.addSubview(bigFrameView)
        view.addSubview(smallFrameView)
        
        // add redFollowView as subview of blueAnimView
        blueAnimView.addSubview(redFollowView)
        
        view.addSubview(blueAnimView)
        
        // set the "start" frame of the blueAnimView
        blueAnimView.frame = smallRect
        
        // calculate and set the redFollowView frame
        let h: CGFloat = blueAnimView.frame.width * 16.0 / 9.0
        redFollowView.frame = .init(x: 0.0, y: 0.0, width: blueAnimView.frame.width, height: h)
        redFollowView.center.x = blueAnimView.bounds.midX
        redFollowView.center.y = blueAnimView.bounds.midY
        
        
        let gr = UITapGestureRecognizer(target: self, action: #selector(MyViewController.handleTap(_:)))
        view.addGestureRecognizer(gr)
        
        // so we can see the small and big frames
        bigFrameView.backgroundColor = .systemYellow
        smallFrameView.backgroundColor = .yellow
        bigFrameView.frame = bigRect
        smallFrameView.frame = smallRect
        
        // init displayLink
        displayLink = CADisplayLink(target: self, selector: #selector(update))

        // start displayLink paused
        displayLink.isPaused = true
        
        // add to run loop
        displayLink.add(to: .current, forMode: .common)

    }
    
    @objc func handleTap(_ gr: UITapGestureRecognizer) {
        
        // if animation is running, return
        if !displayLink.isPaused { return }
        
        let newFrame: CGRect
        if isBig {
            newFrame = smallRect
        } else {
            newFrame = bigRect
        }
        
        // slow spring
        let timing = UISpringTimingParameters.init(dampingRatio: 0.01, frequencyResponse: 10)
        
        // quick spring
        //let timing = UISpringTimingParameters.init(dampingRatio: 0.1, frequencyResponse: 0.5)
        
        let anim = UIViewPropertyAnimator(duration: 10, timingParameters: timing)
        anim.addAnimations {
            self.blueAnimView.frame = newFrame
        }
        
        anim.addCompletion { _ in
            print("completion")
            self.isBig = !self.isBig
            // pause displayLink
            self.displayLink.isPaused = true
        }
        
        // un-pause displayLink
        displayLink.isPaused = false
        
        anim.startAnimation()
        
    }
    
    @objc func update() {

        // get the current frame of blueAnimView's presentation layer
        if let curBlueAnimFrame = blueAnimView.layer.presentation()?.frame {
            let aspect: CGFloat = curBlueAnimFrame.width / curBlueAnimFrame.height
            var w: CGFloat = 0.0
            var h: CGFloat = 0.0
            
            // if curBlueAnimFrame has aspect ratio greater than 9:16
            //  use curBlueAnimFrame width and calculate the new redFollowView height
            // else
            //  use curBlueAnimFrame height and calculate the new redFollowView width
            if aspect > 9.0 / 16.0 {
                w = curBlueAnimFrame.width
                h = w * 16.0 / 9.0
            } else {
                h = curBlueAnimFrame.height
                w = h * 9.0 / 16.0
            }
            // keep redFollowView centered
            let x: CGFloat = curBlueAnimFrame.midX - curBlueAnimFrame.origin.x
            let y: CGFloat = curBlueAnimFrame.midY - curBlueAnimFrame.origin.y
            redFollowView.frame = .init(x: 0.0, y: 0.0, width: w, height: h)
            redFollowView.center = .init(x: x, y: y)
        }
        
    }
    
}

Примечание. Это только пример кода! Он не предназначен и не должен считаться «готовым к производству».


Редактировать 2

Как обсуждалось в комментариях, сказанное выше не совсем верно. При обратном звонке update() мы:

  • получить кадр анимированного изображения presentation layer
  • вычислить кадр 9:16 для просмотра «следить»
  • установить рамку этого вида

К сожалению, когда мы устанавливаем рамку, она визуально не меняется до следующего цикла отрисовки... когда анимированное представление уже имеет следующий размер. Таким образом, следующий вид всегда находится на один кадр позади анимированного представления.

Одним из вариантов было бы написать собственный (или найти) код Spring Animation, а затем вычислить и установить оба кадра при каждом обновлении Display Link.

Или мы можем «обмануть»…

  • добавьте ясность UIView
  • оживить этот вид
  • при каждом обратном вызове обновления получите кадр уровня представления этого представления, установите соответствующий кадр синего представления, вычислите кадр следующего представления и установите его кадр.

Теперь рамки обоих контурных представлений будут установлены одновременно. Мы по-прежнему будем находиться «на один кадр позади» кадра анимации, но, поскольку он очевиден, мы его не увидим.

Быстрый пример кода для демонстрации:

class CheatVC: UIViewController {
    
    let blueView = UIView()
    let redView = UIView()
    let cheatAnimView = UIView()
    
    let smallFrameView: UIView = UIView()
    let bigFrameView: UIView = UIView()
    
    var bigRect: CGRect = .zero
    var smallRect: CGRect = .zero
    
    var isBig = false
    
    var displayLink: CADisplayLink!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        view.backgroundColor = .systemBackground
        
        // define the small and big frames (rects)
        smallRect = CGRect(x: view.frame.midX - 120, y: view.frame.midY - 180, width: 240, height: 360)
        //smallRect = CGRect(x: view.frame.midX - 120, y: view.frame.midY - 80, width: 240, height: 160)
        bigRect = CGRect(x: view.frame.midX - (270/2), y: view.frame.midY - (480/2), width: 270, height: 480)
        
        // 3-point red border
        redView.border(3, color: .red)
        
        // let's make blueView's border thicker to make it easier to see what's going on
        blueView.border(8, color: .blue)
        
        view.addSubview(bigFrameView)
        view.addSubview(smallFrameView)
        
        // add blueView and redView as siblings
        view.addSubview(blueView)
        view.addSubview(redView)
        
        // set the "start" frame of the blueAnimView
        blueView.frame = smallRect
        
        // calculate and set the redView frame
        let h: CGFloat = blueView.frame.width * 16.0 / 9.0
        redView.frame = .init(x: 0.0, y: 0.0, width: blueView.frame.width, height: h)
        redView.center = blueView.center
        
        cheatAnimView.frame = smallRect
        view.addSubview(cheatAnimView)
        cheatAnimView.backgroundColor = .clear
        
        let gr = UITapGestureRecognizer(target: self, action: #selector(MyViewController.handleTap(_:)))
        view.addGestureRecognizer(gr)
        
        // so we can see the small and big frames
        bigFrameView.backgroundColor = .systemYellow
        smallFrameView.backgroundColor = .yellow
        bigFrameView.frame = bigRect
        smallFrameView.frame = smallRect
        
        // init displayLink
        displayLink = CADisplayLink(target: self, selector: #selector(update))
        
        // start displayLink paused
        displayLink.isPaused = true
        
        // add to run loop
        displayLink.add(to: .current, forMode: .common)
        
    }
    
    @objc func handleTap(_ gr: UITapGestureRecognizer) {
        
        // if animation is running, return
        if !displayLink.isPaused { return }
        
        let newFrame: CGRect
        if isBig {
            newFrame = smallRect
        } else {
            newFrame = bigRect
        }
        
        // slow spring
        var timing = UISpringTimingParameters.init(dampingRatio: 0.01, frequencyResponse: 10)
        
        // quick spring
        timing = UISpringTimingParameters.init(dampingRatio: 0.1, frequencyResponse: 0.15)
        
        let anim = UIViewPropertyAnimator(duration: 2, timingParameters: timing)
        anim.addAnimations {
            self.cheatAnimView.frame = newFrame
        }
        
        anim.addCompletion { _ in
            print("completion")
            self.isBig = !self.isBig
            // pause displayLink
            self.displayLink.isPaused = true
        }
        
        // un-pause displayLink
        displayLink.isPaused = false
        
        anim.startAnimation()
        
    }
    
    @objc func update() {
        // get the current frame of cheatAnimView's presentation layer
        if let curAnimFrame = cheatAnimView.layer.presentation()?.frame {
            let aspect: CGFloat = curAnimFrame.width / curAnimFrame.height
            var w: CGFloat = 0.0
            var h: CGFloat = 0.0
            
            // if blueView's frame has aspect ratio greater than 9:16
            //  use blueView width and calculate the new redView height
            // else
            //  use blueView height and calculate the new redView width
            if aspect > 9.0 / 16.0 {
                w = curAnimFrame.width
                h = w * 16.0 / 9.0
            } else {
                h = curAnimFrame.height
                w = h * 9.0 / 16.0
            }
            
            blueView.frame = curAnimFrame
            
            redView.frame = .init(x: 0.0, y: 0.0, width: w, height: h)
            redView.center = blueView.center
        }
        
    }
    
}

Объяснение Дона по-прежнему верно. Процесс анимации не применяет ограничения повторно. Он просто берет разницу между начальным и конечным состояниями, а затем интерполирует ключевые кадры. В случае с пружинящей анимацией он также экстраполирует некоторые ключевые кадры, но эта экстраполяция основана на предположении, что параметр будет продолжать меняться так же, как и между начальной и конечной точками. В вашем случае это не то, чего вы хотите. Я не думаю, что есть простой способ сделать это.

Paulw11 27.06.2024 08:43

@nickneedsaname — да, анимация будет другой, если представления будут одноуровневыми, а не родительско-дочерними. Однако я думал, что вы ищете объяснение того, почему ограничения не следуют за анимацией. Пока не совсем понятно, какова ваша цель... Взгляните на это: i.imgur.com/P1LwtdK.mp4 «Вид с красной рамкой» — это подвид «Вид с синей рамкой» и «Вид с синей рамкой». анимируется. Это то, чего ты добиваешься?

DonMag 27.06.2024 14:30

@DonMag ДА, КАК ВЫ ЭТО СДЕЛАЛИ Это именно то желаемое поведение. «Когда синий вид становится слишком высоким, красный начинает толстеть».

nickneedsaname 27.06.2024 18:56

@nickneedsaname - см. Редактирование моего ответа.

DonMag 27.06.2024 20:47

🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳

nickneedsaname 28.06.2024 19:24

@DonMag, так что это работает очень хорошо. Если вы заставите пружину двигаться быстро и с большой упругостью, это не будет работать так хорошо, поскольку мы вычисляем наш кадр на основе текущей позиции, которая будет сильно отличаться от следующей позиции, поэтому следующий вид будет выглядеть так, как будто это всегда немного отстает.

nickneedsaname 01.07.2024 22:00

@DonMag На практике это не проблема (без сумасшедших весенних анимаций), но меня беспокоит возможность возникновения проблем в некоторых случаях (CADisplayLink не гарантируется с постоянной скоростью, зависит от системных проблем, я могу видите, что это вызывает потенциальные сбои, которые будет трудно отследить/диагностировать/воспроизвести) Знаете ли вы, есть ли способ синхронизировать ссылку на дисплей с анимацией? Или получить следующие кадры презентации? Или заставить redFollowView.frame = .init(x: 0.0, y: 0.0, width: w, height: h) отрисовываться немедленно?

nickneedsaname 01.07.2024 22:01

@nickneedsaname — Я думаю, это максимально близко к тому, что вы можете сделать... используя подпредставления. В зависимости от вашей конечной цели вы можете использовать одно представление и нарисовать контурные рамки, но я не знаю, будет ли это работать с вашей фактической реализацией.

DonMag 02.07.2024 14:36

@DonMag, как выглядит рисунок? Я не могу поверить, что нет способа сказать движку анимации: «Подожди, позволь мне сделать что-нибудь очень быстро» перед каждым рендерингом.

nickneedsaname 02.07.2024 18:28

Например, я склоняюсь к тому, чтобы каким-то образом попытаться перепроектировать кадры пружинной анимации, а затем использовать информацию о синхронизации displaylink, чтобы «угадать» (поскольку время будет почти, но не совсем идеальным), где будет следующий кадр. и используйте это (по сравнению с презентацией) в ссылке на дисплей

nickneedsaname 02.07.2024 18:36

@DonMag Вот сумасшедшая мысль: поскольку начальный размер синего прямоугольника фиксирован, я мог бы просто построить большую таблицу значений blueRect на каждом этапе анимации и использовать ее для вычисления красного кадра в ссылке на дисплей. По сути, это «обратное проектирование» без необходимости выполнения какого-либо обратного проектирования.

nickneedsaname 02.07.2024 18:39

@nickneedsaname, какова твоя настоящая цель? Вам нужна только эта прямоугольная анимация? Или у этих двух представлений будут подпредставления (метки, представления изображений и т. д.)?

DonMag 03.07.2024 14:28

@nickneedsaname — вы можете применить «читский» подход… анимировать четкое представление и установить обе рамки контурного представления при каждом обновлении ссылки на дисплей. По-прежнему будет «на один кадр позади»… но анимированный вид не будет виден, и два контура останутся синхронизированными. Или... Я взял какой-то ручной код весенней анимации, который, кажется, работает очень хорошо. Если вы хотите продолжить обсуждение этого, нам, вероятно, следует перейти в чат.

DonMag 10.07.2024 14:39

Ладно, не буду утруждать себя редактированием, так как это не работает. Моя математика настолько близка к идеальной, насколько это возможно. Ошибки — это самые большие первые ~2 такта DLink, которые не всегда гарантированы; ~0,1 при максимальном разрешении, что меньше пикселя при разрешении 3x, так что я думаю, что это нормально. Проблема в том, что что бы я ни делал, все не выравнивается должным образом, я вижу визуальные ошибки порядка 10+ пикселей, что просто не имеет никакого смысла. «Анимировать четкую рамку» отлично работает, поэтому я просто сделаю это. Еще раз спасибо!!!

nickneedsaname 12.07.2024 20:06

@nickneedsaname — да, анимация четкого изображения и обновление красной и синей рамок кажется разумным вариантом. Я дал свой ответ Edit 2, описывающий проблему, а также пример кода для опции «обман».

DonMag 12.07.2024 22:29

@DonMag, если вы хотите покопаться в математике: desmos.com/calculator/sxlgip2prq -- g(x) — это «наивная» весенняя анимация, которую вы найдете по всему Интернету. f(x) — это тот, где вычисляется фи, поэтому у нас есть скорость 0 @ t=0, а B вычисляется так, что f(x) = A_init @ t=0. Очень доволен тем, насколько хорошо f(x) соответствует x_actual.

nickneedsaname 15.07.2024 19:05

Единственное, чего я не смог понять, это то, как рассчитывается длительность... Для этого и существуют все 1-я, 2-я и 3-я производные. Не особо задумываясь, анимация заканчивается, когда она сильно меняется, так что, может быть, когда производная (1-я или 2-я?) находится ниже порогового значения? или что-то?

nickneedsaname 15.07.2024 19:08

@DonMag Кроме того, я думаю, что вы правы, правильный способ сделать это - запустить нашу собственную анимацию поверх CADisplayLink, чтобы мы могли делать что-то вручную, но я не хочу иметь с этим дело прямо сейчас. Чит работает достаточно хорошо.

nickneedsaname 15.07.2024 19:20

Другие вопросы по теме