【问题标题】:Add subview later to animate alongside superview稍后添加子视图以与超级视图一起制作动画
【发布时间】:2016-01-28 17:20:28
【问题描述】:

我有一个容器视图——我们称之为 socket 视图——它有一个子视图,它是内容视图——我们称之为 plug 视图。此插头视图可以为 nil,即套接字视图当前为空。如果它确实包含一个插头视图,它会占用整个套接字的空间,即它的框架是套接字的边界。从外部的角度来看,你甚至不应该知道实际上有两个视图,因为插头视图总是正好在插座所在的位置。

我正在努力让动画正常工作:如果插件视图存在并且在动画之前布局,那么一切都会按预期工作。但是,如果我仅在动画已经运行时设置套接字的插头视图,我会得到不希望的效果:

插头视图布局到动画结束时的位置,并且不会在其插座旁边进行动画处理。我希望它看起来一直存在,但只是现在才可见,即插头视图(及其子视图)应该与套接字一起动画,即使我在动画进行时添加它也是如此。

我怎样才能实现这种行为?

我的想法:显然插头视图必须布局两次:一次用于其最终位置,另一次用于套接字视图开始动画的位置或添加位置。我可以计算这个帧,在没有动画的情况下应用它,并在一个新的动画块中为最后一帧设置动画。为了使动画时间保持一致,我需要具有相同的曲线和持续时间,但在过去开始动画或以某种方式向前。这可能吗?是否有其他方法可以让插件视图始终保持全宽和全高?


作为 Rob 回答的后续行动,以下是我正在寻找的更多详细信息:

  • 套接字视图正在动画,因为它的所有者的绑定大小已更改。您可以将其视为表格视图中的全角单元格。

  • 插件视图可能包含其自己的子视图,例如图像视图、标签等。这些也应该加入到套接字视图的动画中,就好像它们从动画开始就一直存在一样。

  • 虽然理论上可以在一个已经运行的动画中启动一个新动画,但我并不介意这种极端情况下的行为。

  • 在动画运行时,用户不必与插件视图交互;无论如何,这很可能发生在界面方向更改期间。

  • 插件视图可能会因为动画时的异步模型更新而决定更改其内容,但这又是一个极端情况,我不介意在这种情况下动画看起来不完美。但是,它的大小不会改变 - 它始终与插头视图的大小相同。

【问题讨论】:

  • 你能把你的动画代码贴在这里吗...
  • @KusalShrestha 我只是在动画块内将套接字视图的框架设置为不同的大小,没有什么特别的。
  • 界面方向变化的动画持续时间为 0.3 秒。为什么你这么关心在动画中间插入一个视图?
  • 我并不想在这个问题上成为一个混蛋,顺便说一句。甚至 Apple 也不会尝试在旋转期间为每个 UI 元素设置动画。尝试在 Safari 中调出位置/搜索框的键盘,然后旋转。它隐藏了一堆东西,旋转,然后重新显示隐藏的东西。
  • @robmayoff 我是一个完美主义者。这没什么大不了的,这是真的,但我仍然希望它看起来很棒。我认为如果在旋转过程中出现的单元格比现有单元格更大或更小,这看起来很奇怪。

标签: ios uiview calayer uiviewanimation


【解决方案1】:

You can find my test project here.

您说您的插头视图应该完全覆盖插座视图。我们需要担心两件事:插件的层位置(layer.position)和层大小(layer.bounds.size)。

默认情况下,position 控制图层(和视图)的中心,因为默认 anchorPoint 为 (0.5, 0.5)。如果我们将anchorPoint 设置为(0, 0),那么position 控制图层的左上角,并且我们总是希望它位于套接字坐标系中的(0, 0) 处。因此,通过将anchorPoint 和position 都设置为CGPointZero,我们可以避免担心动画layer.position。

这让我们为layer.bounds.size制作动画。

当您使用 UIKit 动画为视图设置动画时,它会在后台创建 CABasicAnimation 的实例。 CABasicAnimation 是 CAAnimation 的子类,它添加(除其他外)fromValue 和 toValue 属性,指定动画的开始和结束值。

CAAnimation 符合CAMediaTiming 协议,这意味着它具有beginTime 属性。当您创建 CAAnimation 时,它的默认 beginTime 为零。 Core Animation 在提交当前事务时将其更改为当前时间(参见CACurrentMediaTime)。

但是如果动画已经有一个非零的beginTime,Core Animation 会按原样使用它。如果那个beginTime 是过去的,那么当它第一次出现在屏幕上时,动画已经部分(或什至完全)完成,并且已经完成了适当的进度。我们基本上可以“回溯”动画。

因此,如果我们挖掘控制插座的bounds.size 的CABasicAnimation,并将其添加到插头,插头应该以我们想要的方式进行动画处理。当我们将动画附加到插头时,它的beginTime 是它开始为套接字设置动画的时间。因此,即使我们稍后将其连接到插头上,它也会与插座完美同步。而且由于我们希望插头和插座的尺寸相同,所以fromValue和toValue也已经是正确的了。

在我的测试应用程序中,我将插座设为粉红色,将插头设为蓝色。每个都是UIImageView,显示边缘带有线条的图像,因此我们可以确保视图始终具有正确的大小。以下是实际效果:

还有进一步的转折。如果你为一个已经动画的视图设置动画,UIKit 不会停止之前的动画。它添加了第二个动画,两个动画同时运行。

这是因为 UIKit 使用 additive 动画。当您将视图的宽度从 320 设置为 160 时,UIKit 立即将宽度设置为 160,并添加一个从 160 到 0 的附加动画。在动画开始时,表观宽度为 160+160=320,并且在最后,表观宽度为 160+0=160。

当 UIKit 在第一个动画运行时添加第二个动画时,两个动画的值都会添加到用于在屏幕上绘制视图的表观值中。效果如下:

这对我们来说意味着我们必须查找具有keyPath 或position.size 的所有套接字动画,并将它们全部复制到插头。这是我的测试应用程序中的代码:

- (IBAction)plugWasTapped:(id)sender {
    if (self.plugView.superview) {
        [self.plugView removeFromSuperview];
        return;
    }

    self.plugView.frame = self.socketView.bounds;
    [self.socketView addSubview:self.plugView];

    for (NSString *key in self.socketView.layer.animationKeys) {
        CAAnimation *rawAnimation = [self.socketView.layer animationForKey:key];
        if (![rawAnimation isKindOfClass:[CABasicAnimation class]]) {
            continue;
        }

        CABasicAnimation *animation = (CABasicAnimation *)rawAnimation;
        if ([animation.keyPath isEqualToString:@"bounds.size"]) {
            [self.plugView.layer addAnimation:animation forKey:key];
        }
    }
}

这是多个同时动画的结果:

更新

让插件视图的完整视图层次结构像一开始就存在一样进行动画处理,坦率地说,工作量太大。

这是另一种选择:

  1. 以插座的原始尺寸布置插头视图并创建它的图像(“开始图像”)。
  2. 以插座的最终尺寸布置插头视图并创建它的图像(“最终图像”)。
  3. 将占位符图像视图放入套接字中。
  4. 将尺寸动画从套接字复制到占位符。
  5. 使用大小动画在占位符上创建内容动画,将其内容从开始图像交叉淡入到结束图像。
  6. 动画结束时,用插件视图替换占位符。

它是这样的:

除了交叉淡入淡出的不完善之外,这个版本不能处理同时运行的多个动画。您可以在我的存储库中的 crossfade 标签下找到它(链接在顶部)。

另一种方法是仅以最终大小渲染插件视图,并将其放在占位符中而不使用淡入淡出。它看起来像这样:

我觉得这样更好看,而且这个版本可以处理堆叠动画。您可以在我的存储库中的 stretch-end-image 标记下找到它。

最后,我没有实施一种完全不同的方法。这仅适用于您自己创建的调整大小动画 - 它不适用于系统在方向更改时创建的旋转动画。您可以简单地使用计时器自己为套接字视图的边界设置动画,而不是使用 UIKit 动画。 WWDC 2012 Session 228: Best Practices for Mastering Auto Layout 将在最后讨论这个问题。他建议使用NSTimer,但我认为你最好使用CADisplayLink。如果你采用这种方法,插件视图的子视图都将完美地动画化。这比使用 UIKit 动画要多一些工作,但应该很容易实现。

【讨论】:

  • 感谢您抽出宝贵时间制作了如此出色的演示项目!动画行为正是我对插件视图本身的想法,但不是它的子视图。如果这不是很明显,我深表歉意,但我想让插件视图有机会布局其子视图并为其设置动画。当前,当动画已经运行时,将插件视图插入层次结构时,任何子视图的布局都没有动画。请看我提交的pull request。关于如何让它发挥作用的任何想法?
  • 编辑您的问题以包含有关视图层次结构的更多详细信息。为什么套接字视图动画?可以在现有动画运行时开始新动画吗?用户能否在动画期间与插件视图交互,或者其内容是否可以更改?
  • 我回答了您的问题并添加了更多信息。感谢您的宝贵时间。
  • 这是一个很好的答案!
【解决方案2】:

我以这种方式修改了 Rob mayoff 代码。这对您有帮助吗?

重要的变化在于实际使用 CADisplayLink 来更新 plugView 的框架,并且我向 plugView 添加了一个带有约束的子视图。还更改了插件视图的添加方式和框架更新方式。

只需检查这是否适用于您。您应该能够毫无问题地在项目中替换此代码。

#import "ViewController.h"

@interface UIView (recursiveDescription)
- (NSString *)recursiveDescription;
@end

@interface ViewController ()

@property (strong, nonatomic) IBOutlet NSLayoutConstraint *socketWidthConstraint;
@property (strong, nonatomic) IBOutlet NSLayoutConstraint *socketHeightConstraint;
@property (strong, nonatomic) IBOutlet UIView *socketView;
@property (strong, nonatomic) IBOutlet UIImageView *plugView;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    UIView *extraView = [[UIView alloc] init];
    extraView.translatesAutoresizingMaskIntoConstraints = NO;
    extraView.backgroundColor = [UIColor blackColor];
    [self.plugView addSubview:extraView];
    NSLayoutConstraint *c1 = [NSLayoutConstraint constraintWithItem:extraView attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.plugView attribute:NSLayoutAttributeTop multiplier:1.0 constant:0];
    NSLayoutConstraint *c2 = [NSLayoutConstraint constraintWithItem:extraView attribute:NSLayoutAttributeLeading relatedBy:NSLayoutRelationEqual toItem:self.plugView attribute:NSLayoutAttributeLeading multiplier:1.0 constant:0];
    NSLayoutConstraint *c3 = [NSLayoutConstraint constraintWithItem:extraView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:self.plugView attribute:NSLayoutAttributeWidth multiplier:0.5 constant:0];
    NSLayoutConstraint *c4 = [NSLayoutConstraint constraintWithItem:extraView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:self.plugView attribute:NSLayoutAttributeHeight multiplier:0.5 constant:0];
    [self.plugView addConstraints:@[c1, c2, c3, c4]];

}

- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];

    CADisplayLink *link = [CADisplayLink displayLinkWithTarget:self selector:@selector(displayLinkDidFire:)];
    [link addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
}

- (void)displayLinkDidFire:(CADisplayLink *)link {
    CGSize size = [self.socketView.layer.presentationLayer frame].size;
    self.plugView.frame = CGRectMake(0, 0, size.width, size.height);
    [self.plugView layoutIfNeeded];
}

- (IBAction)plugWasTapped:(id)sender {
    if (self.plugView.superview) {
        [self.plugView removeFromSuperview];
        return;
    }

    CGSize size = [self.socketView.layer.presentationLayer frame].size;
    self.plugView.frame = CGRectMake(0, 0, size.width, size.height);
    [self.socketView addSubview:self.plugView];
}
- (IBAction)bigButtonWasTapped:(id)sender {
    [UIView animateWithDuration:10 animations:^{
        self.socketWidthConstraint.constant = 320;
        self.socketHeightConstraint.constant = 320;
        [self.view layoutIfNeeded];
    }];
}

- (IBAction)smallButtonWasTapped:(id)sender {
    [UIView animateWithDuration:5 animations:^{
        self.socketWidthConstraint.constant = 160;
        self.socketHeightConstraint.constant = 160;
        [self.view layoutIfNeeded];
    }];
}


@end

【讨论】:

    【解决方案3】:

    为什么不让插头视图始终作为套接字视图的子视图存在,而是设置hidden = YES?或者你可以使用alpha = 0。然后当你想显示它时,只需将其设置为 hidden = hidden = NO 或 alpha = 1。

    这样,当您为套接字视图设置动画时,您的插头视图将始终“顺其自然”。

    顺便说一句,您的术语让我们这些使用 TCP 套接字的人感到迷惑。 (“套接字?什么?”)

    【讨论】:

    • 这是不可能的,我正在尝试像 UITableView 一样重用插件视图,只有套接字视图应该始终保持活动状态。
    猜你喜欢
    • 1970-01-01
    • 2011-01-21
    • 1970-01-01
    • 2012-11-28
    • 1970-01-01
    • 2020-01-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多