【问题标题】:Animating NSView property of custom structure type自定义结构类型的动画 NSView 属性
【发布时间】:2023-01-12 21:28:24
【问题描述】:

我有一个 NSView 子类,其标头中的属性声明如下:

@property (nonatomic) BaseRange range;

BaseRange 定义为:

typedef struct BaseRange {          
    float start;
    float len;
} BaseRange;

我想使用 NSAnimatablePropertyContainer 协议为 range 属性设置动画。

我的班级根据需要覆盖+ defaultAnimationForKey:

问题是,当我调用[myView.animator setRange:<some value>]myView是相关类的一个实例)时,myView在动画的每一步都被发送-setNilValueForKey:用于range键,除了最终值. IOW,动画不起作用。

如果我这样定义 range 属性:

@property (nonatomic) NSSize range;

并且不要更改任何其他内容,不会发送 -setNilValueForKey: 消息,而是像往常一样发送 range 键的中间值。

但我不想使用NSSize,因为range键应该代表一个范围而不是一个大小。

有什么建议么?

【问题讨论】:

    标签: objective-c nsview nsanimation


    【解决方案1】:

    如果您从 +[NSAnimatablePropertyContainer defaultAnimationForKey:] 方法返回 CAPropertyAnimation 或其子类,则行为符合预期,如 it works with strict set of values

    • 整数和双精度数
    • CGRectCGPointCGSizeCGAffineTransform结构
    • CATransform3D数据结构
    • CGColorCGImage 参考资料

    据我所知,CAAnimation 及其子类无法使用超出此集合的任意值,并且主要专注于使用 Core Graphics 属性(图层、框架、颜色等)。但是,在 macOS 上,您可以改用 NSAnimation 类,这更加灵活,但需要对您的类进行额外的自定义。首先,您应该继承 NSAnimation 本身并覆盖 -[NSAnimation setCurrentProgress:] 方法(这不是强制性的,但否则您将无法在动画步骤之间获得足够平滑的过渡):

    - (void)setCurrentProgress:(NSAnimationProgress)currentProgress {
        [super setCurrentProgress:currentProgress];
    
        // Range owner refers to the object (view) with the property of custom struct type
        // Range Key Path helps to locate the property inside the object
        if (!_rangeOwner || !_rangeKeyPath) {
            return;
        }
    
        static const char *const kTDWRangeEncoding = @encode(TDWRange);
    
        // Wraps new range with NSValue
        NSValue *newRange = [NSValue value:&(TDWRange){
            .location = (_targetRange.location - _originalRange.location) * currentProgress + _originalRange.location,
            .length = (_targetRange.length - _originalRange.length) * currentProgress + _originalRange.length
        } withObjCType:kTDWRangeEncoding];
    
        // Sends new value to the object that owns the range property
        [_rangeOwner setValue:newRange
                   forKeyPath:_rangeKeyPath];
    }
    

    在这个实现中:

    • TDWRange 指的是表示范围的自定义结构;
    • _rangeOwner 指的是具有TDWRange 类型属性的对象;
    • _rangeKeyPath指的是NSAnimation子类可以找到该属性的关键路径;
    • _targetRange 是动画插入的值;
    • _originalRange 是动画开始前的属性值。

    然后,在您的自定义视图类中,您应该提供一种单独的方法来使用给定的动画更新属性。假设动画类名为 TDWRangeAnimation 并且可以通过 @"range" 键路径访问范围属性,这样的方法可能如下所示:

    - (void)setRange:(TDWRange)range animated:(BOOL)animated {
        if (animated) {
            TDWRangeAnimation *rangeAnimation = [[TDWRangeAnimation alloc] initWithRangeOwnder:self
                                                                                  rangeKeyPath:@"range"
                                                                                   targetRange:range
                                                                                      duration:0.4
                                                                                animationCurve:NSAnimationEaseOut];
            rangeAnimation.animationBlockingMode = NSAnimationNonblocking;
            [rangeAnimation startAnimation];
        } else {
            self.range = range;
        }
    }
    

    您不需要保留动画对象,因为它由运行循环维护直到动画结束。


    附言如果您需要完整的实施示例,请随时参考the gist

    【讨论】:

      猜你喜欢
      • 2011-01-24
      • 1970-01-01
      • 1970-01-01
      • 2011-03-11
      • 1970-01-01
      • 2015-08-13
      • 2012-12-20
      • 2012-07-29
      相关资源
      最近更新 更多