有一种很好的方法可以在给定时间(从0到1)计算路径(CGPath或UIBezierPath)的位置吗?
例如,使用CAShapeLayer可以创建动画笔画结束。 我想在任意时间知道该笔画结束的位置。
提前致谢,Adrian
答案 0 :(得分:4)
您绝对可以将您的方法建立在CADisplayLink和跟踪图层上。但是,如果你不介意自己做一些数学运算,解决方案就不会太复杂。此外,您不必依赖于设置显示链接和额外的图层。事实上,你甚至不必依赖QuartzCore。
以下内容适用于任何CGPathRef。如果是UIBezierPath,请获取相同的CGPath属性:
CGPathApply
以及自定义CGPathApplierFunction
功能。 CGPathApplierFunction
。 CGPathElement(应用程序的参数)将告诉您它是什么类型的路径元素以及构成该元素的点(控制点或端点)。kCGPathElementMoveToPoint
,kCGPathElementAddLineToPoint
,kCGPathElementAddQuadCurveToPoint
和kCGPathElementAddCurveToPoint
将分别获得一,二,三和四分。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的显示链接答案的基础上,您可以通过创建第二个“不可见”关键帧动画来跟踪终点的位置。
备注:强>
我们从3个属性开始:
@interface ViewController ()
@property (nonatomic, strong) CADisplayLink *displayLink;
@property (nonatomic, strong) CAShapeLayer *pathLayer;
@property (nonatomic, strong) CALayer *trackingLayer;
@end
displayLink
允许我们在每次屏幕更新时运行代码。
pathLayer
提供视觉效果,即我们将制作动画的视觉效果。
trackingLayer
提供了一个不可见的图层,我们将用它来跟踪strokeEnd
上pathLayer
动画的位置。
我们打开我们的视图控制器:
@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];
}
如果您有想要遵循的路径,但这种技术非常有用,但不想让自己为数学做好准备。
其中一些有价值的原因是:
修改强> 这是github仓库的链接:https://github.com/C4Code/layerTrackPosition
这是我的模拟器的图像:
答案 2 :(得分:0)
如果您从头开始记录所有积分,则可以计算它们之间的距离 如果您想知道在给定时间哪些点在屏幕上被动画,您可以这样做:
首先,获取strokeEnd的当前值(介于0和1之间),如下所示:
CAShapeLayer *presentationLayer = (CAShapeLayer*)[_pathLayer presentationLayer];
CGFloat strokeValue = [[presentationLayer valueForKey:@"strokeEnd"] floatValue];
然后计算你现在已经绘制的距离:
CGFloat doneDistance = _allTheDistance * strokeValue;
在此之后,您必须迭代所有点并计算它们之间的距离,直到得到doneDistance
这不会告诉您路径在屏幕上的确切位置,而是动画的当前点。 也许它会帮助别人。