我有一个由CAMetalLayer的子类支持的视图,它需要重绘其内容以响应捏合和平移手势。该图层具有一些自定义属性,这些属性将转换为变换矩阵,并在两种不同的绘图样式(displayMode
)之间显示和转换。我没有使用基于CADisplayLink的动画循环,但我确实需要对这些自定义属性进行几种不同的显式动画。
自定义属性代码看起来像这样,受到启发 this objc.io article:
@implementation RHLayer
@dynamic origin;
@dynamic scale;
@dynamic rotation;
@dynamic reveal;
@dynamic transition;
@dynamic displayMode;
- (RHLayer *)init {
self = [super init];
if (self) {
[self setUpRenderPipeline];
self.scale = 1.0;
self.reveal = 1.0;
self.transition = 0.0;
self.displayMode = RHSpokeMode;
self.vertexData = [[NSMutableData alloc] initWithCapacity:32768];
self.indexData = [[NSMutableData alloc] initWithCapacity:32768];
[self.vertexData increaseLengthBy:32768];
[self.indexData increaseLengthBy:32768];
self.presentsWithTransaction = YES;
self.needsDisplayOnBoundsChange = YES;
[self setNeedsDisplay];
}
return self;
}
- (RHLayer *)initWithLayer: (RHLayer *)layer {
self = [super initWithLayer:layer];
self.origin = layer.origin;
self.scale = layer.scale;
self.rotation = layer.rotation;
self.reveal = layer.reveal;
self.transition = layer.transition;
self.displayMode = layer.displayMode;
return self;
}
static NSSet *_animatableKeys = nil;
+ (BOOL)needsDisplayForKey:(NSString *)key {
if (!_animatableKeys) {
_animatableKeys = [[NSSet alloc] initWithObjects:@"origin", @"scale", @"rotation",
@"reveal", @"transition", @"displayMode", nil];
}
if ([_animatableKeys member:key] ) {
return YES;
}
return [super needsDisplayForKey:key];
}
到目前为止,我没有实施actionForKey:
,因为我还没有(还)对隐式动画感兴趣,只对显式动画感兴趣。
绘图代码如下所示:
- (id<CAMetalDrawable>)drawable {
while (_drawable == nil) {
_drawable = [self nextDrawable];
}
return _drawable;
}
- (void)display {
[self update]; // Update transform matrix based on presentationLayer's current scale, origin, rotation
id<MTLCommandBuffer> commandBuffer = [self.commandQueue commandBuffer];
id<CAMetalDrawable> drawable = self.drawable;
MTLRenderPassDescriptor *rpd = [self renderPassDescriptorForTexture: drawable.texture];
id<MTLRenderCommandEncoder> encoder = [commandBuffer renderCommandEncoderWithDescriptor:rpd];
[encoder setRenderPipelineState: self.pipelineState];
[encoder setVertexBuffer:self.vertexBuffer offset:0 atIndex:0];
[encoder setVertexBuffer:self.uniformBuffer offset:0 atIndex:1];
// ...
// Draw metal primitives based on presentationLayer's current displayMode, reveal, transition
// ...
[encoder endEncoding];
[commandBuffer commit];
[commandBuffer waitUntilScheduled];
[drawable present];
self.drawable = nil;
}
@end
伴侣UIView子类由上面的子类子类支持,并包含手势识别器逻辑,并根据需要将scale
,offset
和rotation
值传递给图层。 (它还会安装UIButton子视图,最终会在后备层顶部安装其他子文件以进行文本注释。)
@implementation RHView
+ (Class)layerClass {
return [RHLayer class];
}
- (void)setFrame:(CGRect)frame {
[super setFrame: frame];
self.layer.drawableSize = CGSizeMake(self.bounds.size.width * self.contentScaleFactor,
self.bounds.size.height * self.contentScaleFactor);
}
到目前为止,这么好。两种绘图模式运行良好,转换矩阵与手势完美跟踪,按钮子视图布局跟踪背板层转换。
我的明确动画有两个明显的问题:
在动画开始之前, 1。 display
始终使用模型图层的新属性值调用一次。例如,这里是视图代码中的回弹动画,它强制执行最小比例值:
if (gesture.state == UIGestureRecognizerStateEnded) {
if (self.layer.scale < 1.0) {
[UIView animateWithDuration:0.2 animations:^{
CABasicAnimation *resetScale = [CABasicAnimation animationWithKeyPath:@"scale"];
resetScale.fromValue = [NSNumber numberWithFloat: self.layer.scale];
resetScale.toValue = @1.0;
resetScale.duration = 0.2;
resetScale.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
self.layer.scale = 1.0;
[self.layer addAnimation:resetScale forKey:@"ResetScale"];
}];
}
}
使用此代码,我得到一个缩放设置为1.0的帧,然后下一帧以请求的fromValue开始,动画从那里继续。这会导致恼人的视觉流行音乐。
2. 动画有时会到达toValue,但有时并不会。在上面的示例中,在动画display
的最后一帧上,最终可能会使用presentationLayer值调用,例如,scale
的0.993,而不是1.0。模型图层的值肯定设置为1.0,但需要手势轻推以强制使用该值进行额外的重新显示。
我已经搜索了数十篇SO文章和在线教程,但我还没有看到一个CAMetalLayer被直接子类化的例子。
presentsWithTransaction
设置为YES和NO,但没有明显效果。最终,如果我能正确理解此设置的作用,我希望我需要使用YES来动态我的子图层与背景层同步。kCAFillModeBackwards
。没效果。[self setNeedsDisplay]
的CATransaction,以强制进行一次display
次呼叫。那里也没有运气。另一个观察,可能是无关的:在一些我不太了解的偶然情况下,[self presentationLayer]
返回零。所以我做的事情就像这个代码片段的第一行(由update
调用),以便回退到模型层值:
- (matrix_float4x4)layerToViewTransform: (BOOL)animated {
RadialHierarchyLayer *layer = animated && [self presentationLayer] ? [self presentationLayer] : self;
matrix_float4x4 layerToView = matrix_float4x4_translation((vector_float3){ layer.origin.x, layer.origin.y, 0.0 });
layerToView = matrix_multiply (layerToView, matrix_float4x4_rotateZ (layer.rotation));
layerToView = matrix_multiply (layerToView, matrix_float4x4_uniform_scale (layer.scale));
return layerToView;
}