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(), начиная с существующего () преобразования.