CGAffineTransform масштаб и перевод - переход до анимации

Я борюсь с проблемой, связанной с масштабированием и трансляцией CGAffineTransform, когда при установке преобразования в анимационном блоке для представления, в котором уже есть преобразование, представление немного скачет перед анимацией.

Пример:

// somewhere in view did load or during initialization
var view = UIView()
view.frame = CGRectMake(0,0,100,100)
var scale = CGAffineTransformMakeScale(0.8,0.8)
var translation = CGAffineTransformMakeTranslation(100,100)
var concat = CGAffineTransformConcat(translation, scale)
view.transform = transform

// called sometime later
func buttonPressed() {
    var secondScale = CGAffineTransformMakeScale(0.6,0.6)
    var secondTranslation = CGAffineTransformMakeTranslation(150,300)
    var secondConcat = CGAffineTransformConcat(secondTranslation, secondScale)
    UIView.animateWithDuration(0.5, animations: { () -> Void in 
         view.transform = secondConcat
    })

}

Теперь при вызове buttonPressed() вид переходит на верхний левый угол примерно на 10 пикселей, прежде чем начать анимацию. Я был свидетелем этой проблемы только с помощью преобразования concat, отлично работает только преобразование перевода.

Изменить: Поскольку я провел много исследований по этому вопросу, я думаю, я должен упомянуть, что эта проблема появляется независимо от того, включена ли автоматическая разметка

5 ответов

Решение

Я столкнулся с той же самой проблемой, но не мог найти точный источник проблемы. Похоже, что скачок появляется только в очень специфических условиях: если вид анимируется из преобразования t1 преобразовать t2 и оба преобразования представляют собой комбинацию масштаба и перевода (это как раз ваш случай). Учитывая следующий обходной путь, который не имеет смысла для меня, я предполагаю, что это ошибка в Core Animation.

Сначала я попробовал использовать CATransform3D вместо CGAffineTransform,

Старый код:

var transform = CGAffineTransformIdentity
transform = CGAffineTransformScale(transform, 1.1, 1.1)
transform = CGAffineTransformTranslate(transform, 10, 10)
view.layer.setAffineTransform(transform)

Новый код:

var transform = CATransform3DIdentity
transform = CATransform3DScale(transform, 1.1, 1.1, 1.0)
transform = CATransform3DTranslate(transform, 10, 10, 0)
view.layer.transform = transform

Новый код должен быть эквивалентен старому (четвертый параметр установлен в 1.0 или же 0 чтобы не было масштабирования / перевода в z направление), и на самом деле это показывает тот же прыжок. Тем не менее, вот чёрная магия: в трансформации масштаба измените z параметр к чему-либо отличному от 1.0, как это:

transform = CATransform3DScale(transform, 1.1, 1.1, 1.01)

Этот параметр не должен иметь никакого эффекта, но теперь скачок исчез.

Выглядит как внутренняя ошибка анимации Apple UIView. Когда Apple интерполирует CGAffineTransform Для изменения анимации между двумя значениями необходимо выполнить следующие шаги:

  • Извлечь перевод, масштаб и вращение
  • Интерполировать извлеченные значения от начала до конца
  • собирать CGAffineTransform за каждый шаг интерполяции

Сборка должна быть в следующем порядке:

  • Перевод
  • пересчет
  • вращение

Но похоже, что Apple делает перевод после масштабирования и поворота. Эта ошибка должна быть исправлена ​​Apple.

Я не знаю почему, но этот код может работать

Обновить:

Я успешно комбинирую масштабирование, преобразование и вращение вместе из любого состояния преобразования в любое новое состояние преобразования.

Я думаю, что преобразование переосмыслено в начале анимации.

Якорь начала преобразования рассматривается в новом преобразовании, а затем мы преобразуем его в старое преобразование.

self.v  = UIView(frame: CGRect(x: 50, y: 50, width: 50, height: 50))
self.v?.backgroundColor = .blue
self.view.addSubview(v!)

func buttonPressed() {
    let view = self.v!

    let m1 = view.transform
    let tempScale = CGFloat(arc4random()%10)/10 + 1.0
    let tempRotae:CGFloat = 1
    let m2 = m1.translatedBy(x: CGFloat(arc4random()%30), y: CGFloat(arc4random()%30)).scaledBy(x: tempScale, y: tempScale).rotated(by:tempRotae)
    self.animationViewToNewTransform(view: view, newTranform: m2)
}    


func animationViewToNewTransform(view: UIView, newTranform: CGAffineTransform) {
    // 1. pointInView.apply(view.transform) is not correct point.
    // the real matrix is mAnchorToOrigin.inverted().concatenating(m1).concatenating(mAnchorToOrigin)
    // 2. animation begin trasform is relative to final transform in final transform coordinate

    // anchor and mAnchor
    let normalizedAnchor0 = view.layer.anchorPoint
    let anchor0 = CGPoint(x: normalizedAnchor0.x * view.bounds.width, y: normalizedAnchor0.y * view.bounds.height)
    let mAnchor0 = CGAffineTransform.identity.translatedBy(x: anchor0.x, y: anchor0.y)

    // 0->1->2
    //let origin = CGPoint(x: 0, y: 0)
    //let m0 = CGAffineTransform.identity
    let m1 = view.transform
    let m2 = newTranform

    // rotate and scale relative to anchor, not to origin
    let matrix1 = mAnchor0.inverted().concatenating(m1).concatenating(mAnchor0)
    let matrix2 = mAnchor0.inverted().concatenating(m2).concatenating(mAnchor0)
    let anchor1 = anchor0.applying(matrix1)
    let mAnchor1 = CGAffineTransform.identity.translatedBy(x: anchor1.x, y: anchor1.y)
    let anchor2 = anchor0.applying(matrix2)
    let txty2 = CGPoint(x: anchor2.x - anchor0.x, y: anchor2.y - anchor0.y)
    let txty2plusAnchor2 = CGPoint(x: txty2.x + anchor2.x, y: txty2.y + anchor2.y)
    let anchor1InM2System = anchor1.applying(matrix2.inverted()).applying(mAnchor0.inverted())
    let txty2ToM0System = txty2plusAnchor2.applying(matrix2.inverted()).applying(mAnchor0.inverted())
    let txty2ToM1System = txty2ToM0System.applying(mAnchor0).applying(matrix1).applying(mAnchor1.inverted())

    var m1New = m1
    m1New.tx = txty2ToM1System.x + anchor1InM2System.x
    m1New.ty = txty2ToM1System.y + anchor1InM2System.y

    view.transform = m1New
    UIView.animate(withDuration: 1.4) {
        view.transform = m2
    }
}

Я также пробую решение zScale, оно также работает, если установить zScale не равным 1 при первом преобразовании или при каждом преобразовании.

    let oldTransform = view.layer.transform
    let tempScale = CGFloat(arc4random()%10)/10 + 1.0
    var newTransform = CATransform3DScale(oldTransform, tempScale, tempScale, 1.01)
    newTransform = CATransform3DTranslate(newTransform, CGFloat(arc4random()%30), CGFloat(arc4random()%30), 0)
    newTransform = CATransform3DRotate(newTransform, 1, 0, 0, 1)

    UIView.animate(withDuration: 1.4) {
        view.layer.transform = newTransform
    }

Источником проблемы является отсутствие перспективной информации для преобразования.

Вы можете добавить перспективную информацию, изменяя m34 свойство вашего 3d преобразования

var transform = CATransform3DIdentity
transform.m34 = 1.0 / 200 //your own perspective value here
transform = CATransform3DScale(transform, 1.1, 1.1, 1.0)
transform = CATransform3DTranslate(transform, 10, 10, 0)
view.layer.transform = transform

Вместо CGAffineTransformMakeScale() и CGAffineTransformMakeTranslation(), которые создают преобразование на основе CGAffineTransformIdentity (в основном без преобразования), вы хотите масштабировать и преобразовывать на основе текущего преобразования представления с использованием CGAffineTransformScale() и CGAffineTransformTranslate(), начиная с существующего () преобразования.

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