如何在没有变换相互冲突的情况下翻译和缩放视图?

Pwn*_*ner 5 cocoa-touch matrix linear-algebra ios

我需要将UIView从屏幕中心翻译并缩放到右上角.

 _____________________________
|                            |
|                            |
|         _________          |
|        |         |         |    START
|        |         |         |
|        |_________|         |
|                            |
|                            |
|____________________________|


 _____________________________
|                       __   |
|                      |__|  |
|                            |
|                            |    END
|                            |
|                            |
|                            |
|                            |
|____________________________|
Run Code Online (Sandbox Code Playgroud)

我验证了我对比例和翻译的个人计算是准确的.但是一旦它们组合在一起,它们就会相互冲突.以下代码使视图最终太靠近中心.

CGFloat finalPadding = 10.0f;
CGFloat finalScale = 46.0f / 170.0f;
CGFloat finalX = self.view.frame.size.width 
    - self.platformProgressView.frame.size.width * finalScale 
    - finalPadding;
CGFloat finalY = finalPadding;
CGFloat deltaX = finalX - self.platformProgressView.frame.origin.x;
CGFloat deltaY = finalY - self.platformProgressView.frame.origin.y;

[UIView
    animateWithDuration:1.0
    delay:1.0
    options:UIViewAnimationOptionCurveEaseOut
    animations:^(void){
        self.platformProgressView.transform = CGAffineTransformConcat(
            CGAffineTransformMakeTranslation(deltaX, deltaY),
            CGAffineTransformMakeScale(finalScale, finalScale)
        );            
    }
    completion:^(BOOL finished) {
    }
];
Run Code Online (Sandbox Code Playgroud)

最终效果:

 _____________________________
|                            |
|                            |
|                __          |
|               |__|         |
|                            |
|                            |
|                            |
|                            |
|____________________________|
Run Code Online (Sandbox Code Playgroud)

更改乘法的顺序会导致视图偏离屏幕的右边缘.

self.platformProgressView.transform = CGAffineTransformConcat(
     CGAffineTransformMakeScale(finalScale, finalScale),
    CGAffineTransformMakeTranslation(deltaX, deltaY)
);
Run Code Online (Sandbox Code Playgroud)

最终效果(预计):

 _____________________________
|                            |
|                            |    __
|                            |   |__|
|                            |
|                            |
|                            |
|                            |
|                            |
|____________________________|
Run Code Online (Sandbox Code Playgroud)

单独使用它们会导致突然跳跃,最终位置更加糟糕.

self.platformProgressView.transform = CGAffineTransformMakeTranslation(deltaX, deltaY);
self.platformProgressView.transform = CGAffineTransformMakeScale(finalScale, finalScale);
Run Code Online (Sandbox Code Playgroud)

最终效果:

 _____________________________
|                            |
|                            | 
|                            |  
|                            |
|       __                   |
|      |__|                  |
|                            |
|                            |
|____________________________|
Run Code Online (Sandbox Code Playgroud)

her*_*ube 17

了解变换

要实现的主要是变换的原点是视图矩形的中心点.最好用一个例子来说明.

首先,我们翻译视图.v1是原始位置的视图,v2是其平移位置的视图.p是你想要的填充(finalPadding在你的代码中).c标记视图的中心点.

+--------------------------------+
|                        ^       |
|                        | p     |
|                        v       |
|              +- v2 --------+   |
|              |             |   |
|              |      c      |<->|
|              |             | p |
|              +-------------+   |
|                                |
|                                |
|    +- v1 --------+             |
|    |             |             |
|    |      c      |             |
|    |             |             |
|    +-------------+             |
|                                |
+--------------------------------+
Run Code Online (Sandbox Code Playgroud)

接下来我们缩放视图.v3是其缩放位置的视图.请注意v3的中心点如何保持不变,而周围视图的尺寸缩小.虽然尺寸是正确的,但视图的定位和产生的填充p'是错误的.

+--------------------------------+
|                       ^        |
|                       | p'     |
|                       |        |
|                       v        |
|                 +- v3 --+      |
|                 |   c   |<---->|
|                 +-------+  p'  |
|                                |
|                                |
|    +- v1 --------+             |
|    |             |             |
|    |      c      |             |
|    |             |             |
|    +-------------+             |
|                                |
+--------------------------------+
Run Code Online (Sandbox Code Playgroud)

修正增量计算

现在我们知道缩放是如何工作的,我们需要修改计算翻译增量的代码.以下是如何做到正确的:

CGRect windowFrame = self.window.frame;
CGRect viewFrame = self.platformProgressView.frame;
CGPoint finalCenter = CGPointZero;
finalCenter.x = (windowFrame.size.width
                 - (viewFrame.size.width * finalScale) / 2.0f
                 - finalPadding);
finalCenter.y = (finalPadding
                 + (viewFrame.size.height * finalScale) / 2.0f);
CGPoint viewCenter = self.platformProgressView.center;
CGFloat deltaX = finalCenter.x - viewCenter.x;
CGFloat deltaY = finalCenter.y - viewCenter.y;
Run Code Online (Sandbox Code Playgroud)

变换顺序

最后,正如您已经注意到的那样,转换与CGAffineTransformConcat事物连接的顺序.在你的第一次尝试中你有序列1)变换+ 2)比例.结果是,缩放变换(后面出现在序列中)会影响为translate变换指定的增量.

这里有两个解决方案:您自己的第二次尝试,在这里您可以反转序列,使其变为1)缩放+ 2)变换.或者您使用我在答案的第一个修订版中建议的辅助函数.所以这

self.platformProgressView.transform = CGAffineTransformConcat(
    CGAffineTransformMakeScale(finalScale, finalScale),
    CGAffineTransformMakeTranslation(deltaX, deltaY)
);
Run Code Online (Sandbox Code Playgroud)

相当于

CGAffineTransform CGAffineTransformMakeScaleTranslate(CGFloat sx, CGFloat sy, CGFloat dx, CGFloat dy)
{
  return CGAffineTransformMake(sx, 0.0f, 0.0f, sy, dx, dy);
}

self.platformProgressView.transform = CGAffineTransformMakeScaleTranslate(finalScale, finalScale, deltaX, deltaY);
Run Code Online (Sandbox Code Playgroud)

另一种解决方案:设置锚点

如果您对中心点是视图变换的原点感到不满意,可以通过设置anchorPoint视图CALayer 的属性来更改它.默认锚点为0.5/0.5,表示视图矩形的中心.显然,锚点不是坐标而是一种倍增因子.

如果我们假设锚点位于视图的左上角,则您对翻译增量的原始计算是正确的.所以,如果你这样做

self.platformProgressView.layer.anchorPoint = CGPointMake(0.0f, 0.0f)
Run Code Online (Sandbox Code Playgroud)

你可以保留原来的计算.

参考

可能有更多的信息材料,但WWDC 2011视频理解UIKit渲染是我最近观看的内容,这极大地帮助我提高了我对边界,中心,变换和框架之间关系的理解.

此外,如果要更改CALayer anchorPoint属性,则应首先阅读CALayer类引用中的属性文档.