形状图层的动画路径
Animate path of shape layer
我被我认为是一个简单的问题难住了。
我想绘制由线连接的视图,为视图的位置设置动画,并让连接线也设置动画。我创建视图,并像这样在它们之间创建一条线:
- (UIBezierPath *)pathFrom:(CGPoint)pointA to:(CGPoint)pointB {
CGFloat halfY = pointA.y + 0.5*(pointB.y - pointA.y);
UIBezierPath *linePath=[UIBezierPath bezierPath];
[linePath moveToPoint: pointA];
[linePath addLineToPoint:CGPointMake(pointA.x, halfY)];
[linePath addLineToPoint:CGPointMake(pointB.x, halfY)];
[linePath addLineToPoint:pointB];
return linePath;
}
-(void)makeTheLine {
CGPoint pointA = self.viewA.center;
CGPoint pointB = self.viewB.center;
CAShapeLayer *lineShape = [CAShapeLayer layer];
UIBezierPath *linePath=[self pathFrom:pointA to:pointB];
lineShape.path=linePath.CGPath;
lineShape.fillColor = nil;
lineShape.opacity = 1.0;
lineShape.strokeColor = [UIColor blackColor].CGColor;
[self.view.layer addSublayer:lineShape];
self.lineShape = lineShape;
}
它画的正是我想要的样子。我对文档的理解是,我可以通过在动画块中更改形状的路径来为其设置动画,如下所示:
- (void)moveViewATo:(CGPoint)dest {
UIBezierPath *destPath=[self pathFrom:dest to:self.viewB.center];
[UIView animateWithDuration:1 animations:^{
self.viewA.center = dest;
self.lineShape.path = destPath.CGPath;
}];
}
但是没有骰子。视图位置按预期设置动画,但连接到另一个视图的线 "jumps" 立即到达目标路径。
This answer implies that what I'm doing should work. And this answer 建议使用 CABasic 动画,这对我来说似乎更糟,因为 (a) 然后我需要与对视图完成的更酷的块动画进行协调,以及 (b) 当我尝试它时这样,这条线就完全没有改变....
// worse way
- (void)moveViewATo:(CGPoint)dest {
UIBezierPath *linePath=[self pathFrom:dest to:self.viewB.center];
[UIView animateWithDuration:1 animations:^{
self.viewA.center = dest;
//self.lineShape.path = linePath.CGPath;
}];
CABasicAnimation *morph = [CABasicAnimation animationWithKeyPath:@"path"];
morph.duration = 1;
morph.toValue = (id)linePath.CGPath;
[self.view.layer addAnimation:morph forKey:nil];
}
提前致谢。
我想这就是您所需要的:
#import "ViewController.h"
@interface ViewController ()
//the view to animate, nothing but a simple empty UIView here.
@property (nonatomic, strong) IBOutlet UIView *targetView;
@property (nonatomic, strong) CAShapeLayer *shapeLayer;
@property NSTimeInterval animationDuration;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
//the shape layer appearance
self.shapeLayer = [[CAShapeLayer alloc]init];
self.shapeLayer.strokeColor = [UIColor blackColor].CGColor;
self.shapeLayer.fillColor = [UIColor clearColor].CGColor;
self.shapeLayer.opacity = 1.0;
self.shapeLayer.lineWidth = 2.0;
[self.view.layer insertSublayer:self.shapeLayer below:self.targetView.layer];
//animation config
self.animationDuration = 2;
}
- (UIBezierPath *)pathFrom:(CGPoint)pointA to:(CGPoint)pointB {
CGFloat halfY = pointA.y + 0.5*(pointB.y - pointA.y);
UIBezierPath *linePath=[UIBezierPath bezierPath];
[linePath moveToPoint: pointA];
[linePath addLineToPoint:CGPointMake(pointA.x, halfY)];
[linePath addLineToPoint:CGPointMake(pointB.x, halfY)];
[linePath addLineToPoint:pointB];
return linePath;
}
- (void) moveViewTo: (CGPoint) point {
UIBezierPath *linePath= [self pathFrom:self.targetView.center to:point];
self.shapeLayer.path = linePath.CGPath;
//Use CAKeyframeAnimation to animate the view along the path
//animate the position of targetView.layer instead of the center of targetView
CAKeyframeAnimation *viewMovingAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
viewMovingAnimation.duration = self.animationDuration;
viewMovingAnimation.path = linePath.CGPath;
//set the calculationMode to kCAAnimationPaced to make the movement in a constant speed
viewMovingAnimation.calculationMode =kCAAnimationPaced;
[self.targetView.layer addAnimation:viewMovingAnimation forKey:viewMovingAnimation.keyPath];
//draw the path, animate the keyPath "strokeEnd"
CABasicAnimation *lineDrawingAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
lineDrawingAnimation.duration = self.animationDuration;
lineDrawingAnimation.fromValue = [NSNumber numberWithDouble: 0];
lineDrawingAnimation.toValue = [NSNumber numberWithDouble: 1];
[self.shapeLayer addAnimation:lineDrawingAnimation forKey:lineDrawingAnimation.keyPath];
//This part is crucial, update the values, otherwise it will back to its original state
self.shapeLayer.strokeEnd = 1.0;
self.targetView.center = point;
}
//the IBAction for a UITapGestureRecognizer
- (IBAction) viewDidTapped:(id)sender {
//move the view to the tapped location
[self moveViewTo:[sender locationInView: self.view]];
}
@end
一些解释:
对于UIViewAnimation
,属性值在
动画完成。对于 CALayerAnimation
,属性 值为
永远不会改变,它只是一个动画,当动画是
完成后,图层将回到其原始状态(在本例中,
路径).
输入 self.lineShape.path = linePath.CGPath
不起作用是
因为 self.linePath
是 CALayer
而不是 UIView
,你
必须使用 CALayerAnimation
来制作 CALayer
的动画
绘制路径最好用keyPath动画路径绘制
strokeEnd
而不是 path
。我不确定为什么 path
在
原来的post,但我觉得很奇怪。
CAKeyframeAnimation
(而不是 CABasicAnimation
或 UIViewAnimation
)用于沿路径设置视图动画。 (我猜你更喜欢这个而不是直接从起点到终点的线性动画)。将calculationMode
设置为kCAAnimationPaced
会使动画速度恒定,否则视图移动不会与画线同步。
感谢大家的帮助。问完这个问题后我发现我制作的动画是错误的 属性。事实证明,您可以在动画中替换图层的形状,如下所示:
CABasicAnimation *morph = [CABasicAnimation animationWithKeyPath:@"path"];
morph.duration = 1;
morph.fromValue = (__bridge id)oldPath.path;
morph.toValue = (__bridge id)newPath.CGPath;
[line addAnimation:morph forKey:@"change line path"];
line.path=linePath.CGPath;
我被我认为是一个简单的问题难住了。
我想绘制由线连接的视图,为视图的位置设置动画,并让连接线也设置动画。我创建视图,并像这样在它们之间创建一条线:
- (UIBezierPath *)pathFrom:(CGPoint)pointA to:(CGPoint)pointB {
CGFloat halfY = pointA.y + 0.5*(pointB.y - pointA.y);
UIBezierPath *linePath=[UIBezierPath bezierPath];
[linePath moveToPoint: pointA];
[linePath addLineToPoint:CGPointMake(pointA.x, halfY)];
[linePath addLineToPoint:CGPointMake(pointB.x, halfY)];
[linePath addLineToPoint:pointB];
return linePath;
}
-(void)makeTheLine {
CGPoint pointA = self.viewA.center;
CGPoint pointB = self.viewB.center;
CAShapeLayer *lineShape = [CAShapeLayer layer];
UIBezierPath *linePath=[self pathFrom:pointA to:pointB];
lineShape.path=linePath.CGPath;
lineShape.fillColor = nil;
lineShape.opacity = 1.0;
lineShape.strokeColor = [UIColor blackColor].CGColor;
[self.view.layer addSublayer:lineShape];
self.lineShape = lineShape;
}
它画的正是我想要的样子。我对文档的理解是,我可以通过在动画块中更改形状的路径来为其设置动画,如下所示:
- (void)moveViewATo:(CGPoint)dest {
UIBezierPath *destPath=[self pathFrom:dest to:self.viewB.center];
[UIView animateWithDuration:1 animations:^{
self.viewA.center = dest;
self.lineShape.path = destPath.CGPath;
}];
}
但是没有骰子。视图位置按预期设置动画,但连接到另一个视图的线 "jumps" 立即到达目标路径。
This answer implies that what I'm doing should work. And this answer 建议使用 CABasic 动画,这对我来说似乎更糟,因为 (a) 然后我需要与对视图完成的更酷的块动画进行协调,以及 (b) 当我尝试它时这样,这条线就完全没有改变....
// worse way
- (void)moveViewATo:(CGPoint)dest {
UIBezierPath *linePath=[self pathFrom:dest to:self.viewB.center];
[UIView animateWithDuration:1 animations:^{
self.viewA.center = dest;
//self.lineShape.path = linePath.CGPath;
}];
CABasicAnimation *morph = [CABasicAnimation animationWithKeyPath:@"path"];
morph.duration = 1;
morph.toValue = (id)linePath.CGPath;
[self.view.layer addAnimation:morph forKey:nil];
}
提前致谢。
我想这就是您所需要的:
#import "ViewController.h"
@interface ViewController ()
//the view to animate, nothing but a simple empty UIView here.
@property (nonatomic, strong) IBOutlet UIView *targetView;
@property (nonatomic, strong) CAShapeLayer *shapeLayer;
@property NSTimeInterval animationDuration;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
//the shape layer appearance
self.shapeLayer = [[CAShapeLayer alloc]init];
self.shapeLayer.strokeColor = [UIColor blackColor].CGColor;
self.shapeLayer.fillColor = [UIColor clearColor].CGColor;
self.shapeLayer.opacity = 1.0;
self.shapeLayer.lineWidth = 2.0;
[self.view.layer insertSublayer:self.shapeLayer below:self.targetView.layer];
//animation config
self.animationDuration = 2;
}
- (UIBezierPath *)pathFrom:(CGPoint)pointA to:(CGPoint)pointB {
CGFloat halfY = pointA.y + 0.5*(pointB.y - pointA.y);
UIBezierPath *linePath=[UIBezierPath bezierPath];
[linePath moveToPoint: pointA];
[linePath addLineToPoint:CGPointMake(pointA.x, halfY)];
[linePath addLineToPoint:CGPointMake(pointB.x, halfY)];
[linePath addLineToPoint:pointB];
return linePath;
}
- (void) moveViewTo: (CGPoint) point {
UIBezierPath *linePath= [self pathFrom:self.targetView.center to:point];
self.shapeLayer.path = linePath.CGPath;
//Use CAKeyframeAnimation to animate the view along the path
//animate the position of targetView.layer instead of the center of targetView
CAKeyframeAnimation *viewMovingAnimation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
viewMovingAnimation.duration = self.animationDuration;
viewMovingAnimation.path = linePath.CGPath;
//set the calculationMode to kCAAnimationPaced to make the movement in a constant speed
viewMovingAnimation.calculationMode =kCAAnimationPaced;
[self.targetView.layer addAnimation:viewMovingAnimation forKey:viewMovingAnimation.keyPath];
//draw the path, animate the keyPath "strokeEnd"
CABasicAnimation *lineDrawingAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
lineDrawingAnimation.duration = self.animationDuration;
lineDrawingAnimation.fromValue = [NSNumber numberWithDouble: 0];
lineDrawingAnimation.toValue = [NSNumber numberWithDouble: 1];
[self.shapeLayer addAnimation:lineDrawingAnimation forKey:lineDrawingAnimation.keyPath];
//This part is crucial, update the values, otherwise it will back to its original state
self.shapeLayer.strokeEnd = 1.0;
self.targetView.center = point;
}
//the IBAction for a UITapGestureRecognizer
- (IBAction) viewDidTapped:(id)sender {
//move the view to the tapped location
[self moveViewTo:[sender locationInView: self.view]];
}
@end
一些解释:
对于
UIViewAnimation
,属性值在 动画完成。对于CALayerAnimation
,属性 值为 永远不会改变,它只是一个动画,当动画是 完成后,图层将回到其原始状态(在本例中, 路径).输入
self.lineShape.path = linePath.CGPath
不起作用是 因为self.linePath
是CALayer
而不是UIView
,你 必须使用CALayerAnimation
来制作CALayer
的动画
绘制路径最好用keyPath动画路径绘制
strokeEnd
而不是path
。我不确定为什么path
在 原来的post,但我觉得很奇怪。CAKeyframeAnimation
(而不是CABasicAnimation
或UIViewAnimation
)用于沿路径设置视图动画。 (我猜你更喜欢这个而不是直接从起点到终点的线性动画)。将calculationMode
设置为kCAAnimationPaced
会使动画速度恒定,否则视图移动不会与画线同步。
感谢大家的帮助。问完这个问题后我发现我制作的动画是错误的 属性。事实证明,您可以在动画中替换图层的形状,如下所示:
CABasicAnimation *morph = [CABasicAnimation animationWithKeyPath:@"path"];
morph.duration = 1;
morph.fromValue = (__bridge id)oldPath.path;
morph.toValue = (__bridge id)newPath.CGPath;
[line addAnimation:morph forKey:@"change line path"];
line.path=linePath.CGPath;