CGAffineTransform缩放和平移-动画前跳转

CGAffineTransform scale and translation - jump before animation

我正在努力解决有关 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 转换时遇到了这个问题,仅使用转换转换就可以正常工作。

编辑:由于我对此事做了很多研究,所以我想我应该提一下,无论是否打开自动布局,都会出现此问题

您想要使用 CGAffineTransformScale() 和 CGAffineTransformTranslate() 根据视图的当前变换缩放和平移,而不是基于 CGAffineTransformIdentity(基本上没有变换)创建变换的 CGAffineTransformMakeScale() 和 CGAffineTransformMakeTranslation(),从现有转换开始。

我运行遇到了同样的问题,但找不到问题的确切根源。跳转似乎只出现在非常特定的条件下:如果视图从 t运行sform t1 动画到 t运行sform t2 并且 t运行sforms是比例和 t运行slation 的组合(这正是你的情况)。鉴于以下解决方法,这对我来说没有意义,我认为这是 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.00这样就没有z方向的scaling/translation ), 事实上它显示了相同的跳跃。然而,黑魔法来了:在 scale t运行sformation 中,将 z 参数更改为不同于 1.0 的任何参数,如下所示:

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

这个参数应该没有作用,但是现在跳转没有了。

问题的根源在于缺少转换的透视信息。

您可以添加透视信息修改您的 3d 转换m34属性

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

看起来像 Apple UIView 动画内部错误。当 Apple 在两个值之间插入 CGAffineTransform 变化以创建动画时,它应该执行以下步骤:

  • 提取平移、缩放和旋转
  • 从头到尾插入提取的值
  • Assemble CGAffineTransform 每个插值步骤

组装顺序如下:

  • 翻译
  • 缩放
  • 旋转

但看起来 Apple 在缩放和旋转后进行平移。这个错误应该由 Apple 修复。

我不知道为什么,但是这个代码可以工作

更新:

我成功地将缩放、平移和旋转结合在一起,从任何变换状态到任何新的变换状态。

我认为变换在动画开始时被重新解释。

start transform的anchor在new transform中考虑,然后我们将其转换为old transform。

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
    }