如何在单独的动画中将 CGAffineTransform 链接在一起?

How to chain CGAffineTransform together in separate animations?

我需要一个 UIView 上的两个动画:

  1. 使视图向下移动并略微增大。
  2. 使视图围绕其新中心变得更大。

当我尝试这样做时,第二个动画在一个奇怪的位置开始,但在正确的位置和大小结束。如何让第二个动画从第一个动画结束的位置开始?

#import "ViewController.h"

static const CGFloat kStartX = 100.0;
static const CGFloat kStartY = 20.0;
static const CGFloat kStartSize = 30.0;
static const CGFloat kEndCenterY = 200.0;

@interface ViewController ()

@property (nonatomic, strong) UIView *box;

@end

@implementation ViewController

- (void)viewDidLoad
{
  [super viewDidLoad];

  self.box = [[UIView alloc] initWithFrame:CGRectMake(kStartX, kStartY, kStartSize, kStartSize)];
  self.box.backgroundColor = [UIColor brownColor];
  [self.view addSubview:self.box];

  [UIView animateWithDuration:2.0
                        delay:1.0
       usingSpringWithDamping:1.0
        initialSpringVelocity:0.0
                      options:0
                   animations:^{
                     self.box.transform = [self _transformForSize:50.0 centerY:kEndCenterY];
                   }
                   completion:^(BOOL finished) {
                     [UIView animateWithDuration:2.0
                                           delay:1.0
                          usingSpringWithDamping:1.0
                           initialSpringVelocity:0.0
                                         options:0
                                      animations:^{
                                        self.box.transform = [self _transformForSize:100.0 centerY:kEndCenterY];
                                      }
                                      completion:^(BOOL finished) {
                                      }];
                   }];
}

- (CGAffineTransform)_transformForSize:(CGFloat)newSize centerY:(CGFloat)newCenterY
{
  CGFloat newScale = newSize / kStartSize;
  CGFloat startCenterY = kStartY + kStartSize / 2.0;
  CGFloat deltaY = newCenterY - startCenterY;
  CGAffineTransform translation = CGAffineTransformMakeTranslation(0.0, deltaY);
  CGAffineTransform scaling = CGAffineTransformMakeScale(newScale, newScale);
  return CGAffineTransformConcat(scaling, translation);
}

@end

有一个警告:我不得不使用 setTransform 而不是 setFrame。我没有在我的真实代码中使用棕色框。我的真实代码使用的是复杂的 UIView 子类,当我使用 setFrame 时,它​​无法顺利缩放。

这看起来 可能 是一个 UIKit 错误,涉及当您在现有视图之上应用转换时 UIView 如何解析其布局。通过在第二个完成块的最开始执行以下操作,我至少能够获得第二个动画的起始坐标:

  [UIView animateWithDuration:2.0
                        delay:1.0
       usingSpringWithDamping:1.0
        initialSpringVelocity:0.0
                      options:0
                   animations:^{
                     self.box.transform = [self _transformForSize:50.0 centerY:kEndCenterY];
                   }
                   completion:^(BOOL finished) {
                     // <new code here>
                     CGRect newFrame = self.box.frame;
                     self.box.transform = CGAffineTransformIdentity;
                     self.box.frame = newFrame;
                     // </new code>
                     [UIView animateWithDuration:2.0
                                           delay:1.0
                          usingSpringWithDamping:1.0
                           initialSpringVelocity:0.0
                                         options:0
                                      animations:^{
                                        self.box.transform = [self _transformForSize:100.0 centerY:kEndCenterY];
                                      }
                                      completion:^(BOOL finished) {
                                      }];
                   }];

使用对 -_transformForSize:centerY: 的相同调用会导致在第二个动画中执行相同的 Y 平移,但是,当一切都说完之后,框最终在视图中比您想要的更靠下。

要解决这个问题,您需要根据盒子在第一个动画的 end 处的起始 Y 坐标而不是原始的常量 Y 坐标来计算 deltaY:

- (CGAffineTransform)_transformForSize:(CGFloat)newSize centerY:(CGFloat)newCenterY
{
  CGFloat newScale = newSize / kStartSize;
  // Replace this line:
  CGFloat startCenterY = kStartY + kStartSize / 2.0;
  // With this one:
  CGFloat startCenterY = self.box.frame.origin.y + self.box.frame.size.height / 2.0;
  // </replace>
  CGFloat deltaY = newCenterY - startCenterY;
  CGAffineTransform translation = CGAffineTransformMakeTranslation(0.0, deltaY);
  CGAffineTransform scaling = CGAffineTransformMakeScale(newScale, newScale);
  return CGAffineTransformConcat(scaling, translation);
}

它应该可以解决问题。

更新

我应该说,我认为这 "trick" 更像是一种解决方案,而不是实际解决方案,但是盒子的框架和变换在动画管道的每个阶段看起来都是正确的,所以如果有一个真正的 "solution" 此刻它在躲避我。这个技巧至少为你解决了翻译问题,所以你可以尝试更复杂的视图层次结构,看看你能走多远。