及时获取路径的位置

时间:2013-12-16 22:24:31

标签: objective-c core-graphics core-animation 2d cashapelayer

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

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

提前致谢,Adrian

3 个答案:

答案 0 :(得分:4)

您绝对可以将您的方法建立在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找出路径元素。您可以假设每个路径元素获得一个相等的时间片,或者您可以计算每个元素的距离并计算小数时间(前一种方法对我来说很好)。另外,不要忘记为所有以前的路径元素添加时间(您不必为这些元素找到插值)。

正如我所说,这只是为了完整性(而且很可能是苹果公司自己解决这些问题)并且只有你愿意做数学。

答案 1 :(得分:2)

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

备注:

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

    我们从3个属性开始:

    @interface ViewController ()
    @property (nonatomic, strong) CADisplayLink *displayLink;
    @property (nonatomic, strong) CAShapeLayer *pathLayer;
    @property (nonatomic, strong) CALayer *trackingLayer;
    @end
    

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

    我们打开我们的视图控制器:

    @implementation ViewController
    
    - (void)viewDidLoad
    {
        [super viewDidLoad];
    
        [self createDisplayLink];
        [self createPathLayer];
        [self createTrackingLayer];
        [self startAnimating];
    }
    ...
    

    使用以下方法......

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

    -(void)createDisplayLink {
        _displayLink = [CADisplayLink
                        displayLinkWithTarget:self
                        selector:
                        @selector(displayLinkDidUpdate:)];
    
        [_displayLink
         addToRunLoop:[NSRunLoop mainRunLoop]
         forMode:NSDefaultRunLoopMode];
    }
    

    然后我们创建可见图层:

    -(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];
    }
    

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

    -(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];
    }
    

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

    - (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);
    }
    

    ...和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];
    }
    

    如果您有想要遵循的路径,但这种技术非常有用,但不想让自己为数学做好准备。

    其中一些有价值的原因是:

    1. 可以使用不同的/自定义路径(不只是省略号......)
    2. 可以使用不同的媒体计时功能(你不必自己弄清楚数学,以便于进出,等等......)。
    3. 您可以随时开始/停止动画跟踪图层(即不必连续运行)
    4. 您可以随时开始/停止显示链接
    5. 在不同的图层坐标之间进行转换非常简单,因此您可以在图层内部使用图层,并将其坐标转移到任何其他图层
    6. 修改 这是github仓库的链接:https://github.com/C4Code/layerTrackPosition

      这是我的模拟器的图像:

      tracking the position of a calayer that is being animated

答案 2 :(得分:0)

如果您从头开始记录所有积分,则可以计算它们之间的距离 如果您想知道在给定时间哪些点在屏幕上被动画,您可以这样做:

  • 首先,获取strokeEnd的当前值(介于0和1之间),如下所示:

    CAShapeLayer *presentationLayer = (CAShapeLayer*)[_pathLayer presentationLayer];

    CGFloat strokeValue = [[presentationLayer valueForKey:@"strokeEnd"] floatValue];

  • 然后计算你现在已经绘制的距离:

    CGFloat doneDistance = _allTheDistance * strokeValue;

  • 在此之后,您必须迭代所有点并计算它们之间的距离,直到得到doneDistance

这不会告诉您路径在屏幕上的确切位置,而是动画的当前点。 也许它会帮助别人。