及时获取路径的位置

Adr*_*ian 5 2d core-animation core-graphics objective-c cashapelayer

有没有一种很好的方法来计算给定时间(从0到1)的路径(CGPath或UIBezierPath)的位置?

例如,使用CAShapeLayer可以创建动画笔画结束.我想在任意时间知道那个笔画结束的位置.

先谢谢,阿德里安

Buz*_*zzy 5

您绝对可以将方法基于CADisplayLink和跟踪层。但是,如果您不介意自己做一点数学,那么解决方案就不会太复杂。另外,您不必依赖设置显示链接和额外的图层。实际上,您甚至不必依赖QuartzCore。

以下内容适用于任何CGPathRef。如果是UIBezierPath,则获取相同的CGPath属性:

  • CGPathApply在要自检的路径上使用CGPathApplierFunction
  • CGPathApplierFunction将为该路径的每个组件调用。CGPathElement(应用程序的参数)将告诉您路径元素的种类以及构成该元素的点(控制点或端点)。
  • 您将获得一,二,三,四点kCGPathElementMoveToPointkCGPathElementAddLineToPointkCGPathElementAddQuadCurveToPointkCGPathElementAddCurveToPoint分别。
  • 将这些点内部存储为您选择的表示形式。您只需要对CGPathApply每个路径使用一次,此步骤将非常快。

现在,进入数学:

  • 根据您希望找到位置的时间,例如t,获取元素(稍后会详细介绍)及其组成点。
  • 如果元素类型为kCGPathElementMoveToPoint,则为线性插值p0 + t * (p1 - p0)(用于x和y)
  • 如果元素类型为kCGPathElementAddQuadCurveToPoint,则其二次((1 - t) * (1 - t)) * p0 + 2 * (1 - t) * t * p1 + t * t * p2
  • 如果元素类型为kCGPathElementAddCurveToPoint,则其立方贝塞尔曲线((1 - t) * (1 - t) * (1 - t)) * p0 + 3 * (1 - t) * (1 - t) * t * p1 + 3 * (1 - t) * t * t * p2 + t * t * t * p3

现在的问题仍然存在,您如何及时确定路径元素t。您可以假定每个路径元素都具有相等的时间片,或者可以计算每个元素的距离并考虑小数时间(前一种方法对我来说效果很好)。同样,不要忘记为所有先前的路径元素添加时间(您不必为这些元素添加插值)。

就像我说的那样,这仅是出于完整性(可能是Apple如何自行解决此问题),并且仅当您愿意进行数学计算时。


C4 *_*vis 2

在马特的显示链接答案的基础上,您可以通过创建第二个“不可见”关键帧动画来跟踪终点的位置。

笔记:

  1. 使用这种技术,您不需要自己计算终点的位置
  2. 您可以使用任何路径形状。
  3. 这是在 Xcode 中基本 iOS 单视图应用程序模板的视图控制器类中编写的

我们从 3 个属性开始:

@interface ViewController ()
@property (nonatomic, strong) CADisplayLink *displayLink;
@property (nonatomic, strong) CAShapeLayer *pathLayer;
@property (nonatomic, strong) CALayer *trackingLayer;
@end
Run Code Online (Sandbox Code Playgroud)

displayLink将允许我们在每次屏幕更新时运行代码。它pathLayer提供了我们将制作动画的视觉效果。它trackingLayer提供了一个不可见的层,我们将使用它来跟踪strokeEnd动画在 上的位置pathLayer

我们像这样打开视图控制器:

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];

    [self createDisplayLink];
    [self createPathLayer];
    [self createTrackingLayer];
    [self startAnimating];
}
...
Run Code Online (Sandbox Code Playgroud)

通过以下方法...

我们首先创建显示链接并将其添加到运行循环中(根据 Matt 的代码):

-(void)createDisplayLink {
    _displayLink = [CADisplayLink
                    displayLinkWithTarget:self
                    selector:
                    @selector(displayLinkDidUpdate:)];

    [_displayLink
     addToRunLoop:[NSRunLoop mainRunLoop]
     forMode:NSDefaultRunLoopMode];
}
Run Code Online (Sandbox Code Playgroud)

然后我们创建可见层:

-(void)createPathLayer {
    //create and style the path layer
    //add it to the root layer of the view controller's view
    _pathLayer = [CAShapeLayer layer];
    _pathLayer.bounds = CGRectMake(0,0,100,100);
    _pathLayer.path = CGPathCreateWithEllipseInRect(_pathLayer.bounds, nil);
    _pathLayer.fillColor = [UIColor clearColor].CGColor;
    _pathLayer.lineWidth = 5;
    _pathLayer.strokeColor = [UIColor blackColor].CGColor;
    _pathLayer.position = self.view.center;
    [self.view.layer addSublayer:_pathLayer];
}
Run Code Online (Sandbox Code Playgroud)

然后我们创建一个“不可见”(即通过没有尺寸的框架)层来跟踪:

-(void)createTrackingLayer {
    _trackingLayer = [CALayer layer];

    //set the frame (NOT bounds) so that we can see the layer
    //uncomment the following two lines to see the tracking layer
    //_trackingLayer.frame = CGRectMake(0,0,5,5);
    //_trackingLayer.backgroundColor = [UIColor redColor].CGColor;

    //we add the blank layer to the PATH LAYER
    //so that its coordinates are always in the path layer's coordinate system
    [_pathLayer addSublayer:_trackingLayer];
}
Run Code Online (Sandbox Code Playgroud)

然后我们创建一个获取跟踪层位置的方法:

- (void)displayLinkDidUpdate:(CADisplayLink *)sender {
    //grab the presentation layer of the blank layer
    CALayer *presentationLayer = [_trackingLayer presentationLayer];
    //grab the position of the blank layer
    //convert it to the main view's layer coordinate system
    CGPoint position = [self.view.layer convertPoint:presentationLayer.position
                                           fromLayer:_trackingLayer];
    //print it out, or do something else with it
    NSLog(@"%4.2f,%4.2f",position.x,position.y);
}
Run Code Online (Sandbox Code Playgroud)

...以及startAnimating方法:

-(void)startAnimating {
    //begin the animation transaction
    [CATransaction begin];
    //create the stroke animation
    CABasicAnimation *strokeEndAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
    //from 0
    strokeEndAnimation.fromValue = @(0);
    //to 1
    strokeEndAnimation.toValue = @(1);
    //1s animation
    strokeEndAnimation.duration = 10.0f;
    //repeat forever
    strokeEndAnimation.repeatCount = HUGE_VAL;
    //ease in / out
    strokeEndAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
    //apply to the pathLayer
    [_pathLayer addAnimation:strokeEndAnimation forKey:@"strokeEndAnimation"];

    //NOTE: we don't actually TRACK above animation, its there only for visual effect

    //begin the follow path animation
    CAKeyframeAnimation *followPathAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
    //set the path for the keyframe animation
    followPathAnimation.path = _pathLayer.path;
    //add an array of times that match the NUMBER of points in the path
    //for custom paths, you'll need to know the number of points and calc this yourself
    //for an ellipse there are 5 points exactly
    followPathAnimation.keyTimes = @[@(0),@(0.25),@(0.5),@(0.75),@(1)];
    //copy the timing function
    followPathAnimation.timingFunction = strokeEndAnimation.timingFunction;
    //copy the duration
    followPathAnimation.duration = strokeEndAnimation.duration;
    //copy the repeat count
    followPathAnimation.repeatCount = strokeEndAnimation.repeatCount;
    //add the animation to the layer
    [_trackingLayer addAnimation:followPathAnimation forKey:@"postionAnimation"];
    [CATransaction commit];
}
Run Code Online (Sandbox Code Playgroud)

如果您有想要遵循的路径,但又不想自己进行数学计算,那么这种技术非常有用。

一些有价值的原因是:

  1. 可以使用不同/自定义路径(不仅仅是省略号......)
  2. 可以使用不同的媒体计时函数(您不必自己计算数学以轻松输入、输出或线性等......)
  3. 您可以随时启动/停止跟踪层的动画(即不必连续运行)
  4. 您可以随时启动/停止显示链接
  5. 不同图层坐标之间的转换非常容易,因此您可以在图层内部放置图层,并且仍然将它们的坐标传输到任何其他图层

编辑 这是 github 存储库的链接:https ://github.com/C4Code/layerTrackPosition

这是我的模拟器的图像:

跟踪正在制作动画的 calayer 的位置