【问题标题】: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) 处。因此,通过将anchorPointposition 都设置为CGPointZero,我们可以避免担心动画layer.position

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

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

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

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

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

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

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

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

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

这对我们来说意味着我们必须查找具有keyPathposition.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 = NOalpha = 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
    相关资源
    最近更新 更多