位于关键帧值中间的 CALayer 的 CAKeyframeAnimation?

Posted

技术标签:

【中文标题】位于关键帧值中间的 CALayer 的 CAKeyframeAnimation?【英文标题】:CAKeyframeAnimation for CALayer which is positioned at middle of keyframe values? 【发布时间】:2017-03-28 07:24:34 【问题描述】:

假设我想将layer 从 A 点移动到 B,然后再移动到 C:

CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"position.x"];
animation.values = @[pointA, pointB, pointC];
animation.duration = 1;

将动画添加到图层后,我将其速度设置为0:

animation.speed = 0;

所以我可以使用滑块来调整图层的位置:

layer.timeOffset = slider.value;

但是如果我的图层的当前位置在pointB,添加动画后它会移回pointA,即使我将layertimeOffset设置为0.5。

有什么我错过的吗?

谢谢!

更新: 为了更好地说明问题,这里有一些测试代码:

- (void)viewDidAppear:(BOOL)animated 
    [super viewDidAppear:animated];
    self.testLayer = [CALayer layer];
    self.testLayer.frame = CGRectMake(100, 100, 30, 30);
    self.testLayer.backgroundColor = [UIColor redColor].CGColor;

    [self.view.layer addSublayer:self.testLayer];


- (IBAction)action:(id)sender

    CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"position.y"];

    animation.values = @[@50, @100, @150];
    animation.duration = 3.0f;

    [self.testLayer addAnimation:animation forKey:@"animation"];
    self.testLayer.speed = 0;

testLayer 的 Y 位置是 100,而添加的动画的键值是 50、100 和 150,当添加动画时,testLayer 会移动到第一个键值位置 50,但是如何防止这种情况发生?我尝试设置self.testLayer.timeOffset = 0.5,但没有帮助。

【问题讨论】:

试试我的答案。刚刚修正了一些错别字,所以应该很好去 【参考方案1】:

面纱背后有一些神秘的功能,经过我自己的一些实验,似乎在动画之后立即调用 timeOffset setter 与一段时间后发出的调用有所不同:(这是在 Swift 中,但概念是一样的)

layer.add(animation, forKey: "...")
layer.timeOffset = CFTimeInterval(0.5)

这会以某种方式为图层下的所有动画设置“全局”偏移量,如果您以这种方式调用 timeOffset 设置器,所有动画将在 0.5 秒后开始工作。

/* The animation won't do anything when slider value is 0 - 0.5,
 * even if the function call is exactly the same. It would perform
 * the animation at time offset 0 - 0.5 when the slider value is 
 * 0.6 - 1.0.
 */
layer.timeOffset = CFTimeInterval(slider.value)

要实现你想要的,你可以这样完成任务:

// Note: do not set timeOffset on the layer.
animation.timeOffset = CFTimeInterval(0.5)
layer.add(animation, forKey: "...")

但是,由于您的点ABC 不一定形成闭合路径,因此在滑块值(因此为timeOffset)超过0.5 之后,您的图层将立即向后移动到A,如果滑块值小于0,图层就会消失,所以你必须这样做:

// set the initial slider value at 0.5
slider.value = 0.5

// match the slider value to actual animation time offset
layer.timeOffset = CFTimeInterval(slider.value > 0.5 ? slider.value - 0.5 : slider.value + 0.5)

这使得动画可以根据您的需要正确执行,但是必须有更好的方法,我也很好奇为什么 timeOffset 设置器的行为不同,希望有擅长 CAAnimation 的人能够解释.

【讨论】:

【参考方案2】:

老实说,这是最棘手的问题之一。诚然,CALayers 和 CAAnimation 的时间安排通常是相当复杂的。通常在添加动画时更新模型值是好的,但您希望它通过百分比/滑块完成,所以这是我完全理解的挑战。 Cytus 的答案真的很远,因为在添加新动画时设置 layer.timeOffset 时似乎存在错误。见When is layer.timeOffset Available。在动画本身上设置 timeOffset 是有效的,只是在滑​​块处于最大值时删除了这些值。如果您在 viewDidAppear 中添加动画并且仅在滑块操作方法中更新 timeOffset,它也可以完美运行。我开始记录图层的时间值,并注意到 beginTime 的一些疯狂的东西。简短的回答是,当您添加新动画时,您必须将 beginTime(特别是在这种情况下)设置为 CACurrentMediaTime(),并且一切正常。奇怪的是,我通常使用它来延迟,但是关于一遍又一遍地替换当前动画的东西会产生对这个我一直认为是可选的属性的需求。我想说,完成后,您应该删除动画以防止表示层被污染,但这取决于您。这是一个工作示例,它将动画保持在适当的位置,而不会出现故障并恢复为模型值。

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController

@property (nonatomic,strong) CALayer *testLayer;
@property (nonatomic,strong) UILabel *testLabel;

@end

还有 ViewController.m

 #import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad 
    [super viewDidLoad];

    CGRect frame = CGRectMake(0.0, 0.0, 200.0, 20.0);
    UISlider *slider = [[UISlider alloc] initWithFrame:frame];
    [slider addTarget:self action:@selector(sliderAction:) forControlEvents:UIControlEventValueChanged];
    [slider setBackgroundColor:[UIColor clearColor]];
    slider.minimumValue = 0.0;
    slider.maximumValue =  1;
    slider.continuous = YES;
    slider.value = 0.0;
    slider.center = CGPointMake(self.view.center.x, self.view.bounds.size.height - 30);
    [self.view addSubview:slider];

    self.testLabel = [[UILabel alloc]initWithFrame:CGRectMake(40, 30, self.view.bounds.size.width - 80, 40)];
    self.testLabel.text = @"0";
    [self.testLabel setTextColor:[UIColor blackColor]];
    [self.view addSubview:self.testLabel];


- (void)viewDidAppear:(BOOL)animated 
    [super viewDidAppear:animated];
    self.testLayer = [CALayer layer];
    self.testLayer.frame = CGRectMake(100, 100, 30, 30);
    self.testLayer.backgroundColor = [UIColor redColor].CGColor;

    [self.view.layer addSublayer:self.testLayer];


-(void)sliderAction:(id)sender

    self.testLayer.speed = 0;
    UISlider *slider = (UISlider*)sender;
    float value = slider.value * 3.0; //3 is duration so we need to normalize slider
    NSLog(@"The value is %f",value);
    self.testLabel.text = [NSString stringWithFormat:@"%f",value];

    [_testLayer removeAllAnimations];
    CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"position.y"];
    //   //get the real position and add it to the keyframe
    CGFloat positionY = self.testLayer.position.y;
    //I did make A the current position to make it smooth
    animation.values = @[@(positionY), @100, @250];
    animation.duration = 3.0;
    animation.fillMode = kCAFillModeForwards;
    //this line fixes readding the animation over and over with a timeOffset
    animation.beginTime = CACurrentMediaTime();
    [animation setRemovedOnCompletion:NO];


    [self.testLayer addAnimation:animation forKey:@"animation"];

    //updating the offset on the layer and not the animation
    self.testLayer.timeOffset = value + CACurrentMediaTime();




@end

干杯

【讨论】:

【参考方案3】:

尝试在 addAnimation 之后将图层的位置设置为目的地

[layer addAnimation:animation forKey:@"basic"];

layer.position = CGPointMake(x, y);

【讨论】:

以上是关于位于关键帧值中间的 CALayer 的 CAKeyframeAnimation?的主要内容,如果未能解决你的问题,请参考以下文章

添加CAlayer与帧显示在错误的位置

添加带有框架显示在错误位置的 CAlayer

转换后获取帧值的最佳方法?

CALayer 中的抗锯齿绘图

Core Animation 之一

使用堆栈视图时帧值不正确