【问题标题】:Is there an issue with this use of kCAMediaTimingFunctionEaseIn?使用 kCAMediaTimingFunctionEaseIn 有问题吗?
【发布时间】:2014-06-25 15:46:35
【问题描述】:

我正在使用CALayerCAReplicatorLayerCABasicAnimation 将条形图设置为动画。条形图由“段”组成,我将其渲染为带有CALayer 的小矩形。然后我将它们作为子层添加到CAReplicatorLayer 循环中。最后,我为每个小矩形添加了一个动画,将矩形放到图表中的位置。

这是动画的最终状态:

这是动画中间的样子:

下面是实现它的代码:

#define BLOCK_HEIGHT 6.0f
#define BLOCK_WIDTH 8.0f

@interface TCViewController ()

@property (nonatomic, strong) NSMutableArray * blockLayers;         // blocks that fall from the sky
@property (nonatomic, strong) NSMutableArray * replicatorLayers;    // replicator layers that will replicate the blocks into position

@end

@implementation TCViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor lightGrayColor];

    self.blockLayers = [NSMutableArray array];
    self.replicatorLayers = [NSMutableArray array];

    NSArray * dataBlocksData = @[@1,@2,@3,@1,@5,@2,@11,@14,@5,@12,@10,@3,@7,@6,@3,@4,@1];

    __block CGFloat currentXPosition = 0.0f;

    [dataBlocksData enumerateObjectsUsingBlock:^(NSNumber * obj, NSUInteger idx, BOOL *stop) {

        currentXPosition += BLOCK_WIDTH + 2.0f;

        if (obj.integerValue == 0) return;

        // replicator layer for data blocks in a column
        CAReplicatorLayer * replicatorLayer = [[CAReplicatorLayer alloc] init];
        replicatorLayer.frame = CGRectMake(currentXPosition, 0.0f, BLOCK_WIDTH, 200.0f);

        // single data block layer (must be new for each replicator layer. can't reuse existing layer)
        CALayer * blockLayer = [[CALayer alloc] init];
        blockLayer.frame = CGRectMake(0, 200-BLOCK_HEIGHT, BLOCK_WIDTH, BLOCK_HEIGHT);
        blockLayer.backgroundColor = [UIColor whiteColor].CGColor;
        [replicatorLayer addSublayer:blockLayer];

        replicatorLayer.instanceCount = obj.integerValue;
        replicatorLayer.instanceDelay = 0.1f;
        replicatorLayer.instanceTransform = CATransform3DMakeTranslation(0, -BLOCK_HEIGHT, 0);    // negative height moves back up the column

        [self.blockLayers addObject:blockLayer];                
        [self.replicatorLayers addObject:replicatorLayer];      
    }];
}

- (void)viewDidLayoutSubviews {
    // add replicator layers as sublayers
    [self.replicatorLayers enumerateObjectsUsingBlock:^(CAReplicatorLayer * obj, NSUInteger idx, BOOL *stop) {
        [self.view.layer addSublayer:obj];
    }];
}

- (void)viewDidAppear:(BOOL)animated {

    // add animations to each block layer
    [self.blockLayers enumerateObjectsUsingBlock:^(CALayer * obj, NSUInteger idx, BOOL *stop) {
        // position animation
        CABasicAnimation * animation = [CABasicAnimation animationWithKeyPath:@"transform.translation.y"];
        animation.fromValue = @(-300);
        animation.toValue = @(0);
        animation.duration = 1.0f;
        animation.fillMode = kCAFillModeBoth;
        animation.removedOnCompletion = NO;
        animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault];
        [obj addAnimation:animation forKey:@"falling-block-animation"];
    }];
}

最后,问题来了。如果您未指定 timingFunctionLinear 函数或 EaseIn 函数,则动画不会完全完成。就像块没有完全到位。看起来他们已经接近完成,但还没有完全完成。只掉一两帧。换掉-viewDidAppear:中的定时函数赋值,你会得到这个怪异:

来吧,试试看。我什至尝试使用与EaseIn 函数完全相同的控制点 (link) 指定函数:

// use control points for an EaseIn timing:
animation.timingFunction = [CAMediaTimingFunction functionWithControlPoints:0.42 :0.00 :1.0 :1.0];

// or simply specify the ease in timing function:
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];

由于某种原因,EaseOutDefault 计时功能工作正常,但从视觉上看,轻松样式功能似乎需要更长的时间才能完成,可能是因为插值曲线的长度?

我已经尝试了很多方法来缩小问题范围:

  1. 不循环遍历这些结构,并且仅使用 1 层和 1 复制器层和相同的动画。我的意思是,只为一列设置动画。没运气。循环不是问题。
  2. 将创建、添加子层和添加动画的代码全部放在-viewDidAppear中。如果动画是在视图生命周期的早期或晚期添加的,似乎并没有什么不同。
  3. 为我的结构使用属性。没有区别。
  4. 不使用我的结构的属性。同样,没有区别。
  5. 添加了动画完成回调以删除动画,从而使列保持正确的“最终”状态。这不太好,但值得一试。
  6. iOS 6 和 7 都存在此问题。

我真的很想使用EaseIn 计时功能,因为它是放置到位的最佳动画。我也想使用CAReplicatorLayer,因为它完全符合我的要求。

那么在我使用EaseIn 计时功能时有什么问题?这是一个错误吗?

【问题讨论】:

    标签: ios animation core-animation calayer careplicatorlayer


    【解决方案1】:

    我同意这似乎是 CAReplicatorLayer 和 kCAMediaTimingFunctionEaseIn 计时函数组合中的错误。

    作为一种解决方法,我建议使用具有三个关键帧的关键帧动画。前两个关键帧通过缓入时间定义了您想要的动画。最后一个关键帧被定义为与第二个关键帧相同,但给动画额外的时间位于最后一个关键帧上,这似乎让复制器层将最后一个复制层动画化到最终状态。

    因此我的建议是:

    CAKeyframeAnimation * animation = [CAKeyframeAnimation animationWithKeyPath:@"transform.translation.y"];
    
    //  extra keyframe duplicating final state
    animation.values = @[@-300.0f, @0.0f, @0.0f];
    
    //  double length of original animation, with last half containing no actual animation
    animation.keyTimes = @[@0.0, @0.5, @1.0];
    animation.duration = 2.0f;
    
    animation.fillMode = kCAFillModeBoth;
    animation.removedOnCompletion = NO;
    
    //  ease-in for first half of animation; default after
    animation.timingFunctions = @[[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn], [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionDefault]];
    [obj addAnimation:animation forKey:@"falling-block-animation"];
    

    【讨论】:

    • 请不要使用removedOnCompletion = NO。这是制作动画的一种不好的方法。我很难说服人们这是一个坏主意,即使他们没有在 SO 答案中看到它。我知道 OP 在问题中使用了它,但我们都可以成为优秀的童子军,让营地处于比我们到达那里时更好的状态。
    • 我完全同意removedOnCompletion = NO 是一个坏主意,但对于 CAReplicatorLayer 上的动画,它是完全必需的。否则,动画可能会在最终复制层完全播放之前被移除。我认为这里正确的解决方案是设置一个计时器以在duration + (instanceCount - 1) * instanceDelay 触发并在那时手动删除动画。
    猜你喜欢
    • 2021-02-01
    • 1970-01-01
    • 2019-12-19
    • 2011-10-20
    • 2012-07-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多