UIBezierPath & CAShapeLayer 初始动画跳转
UIBezierPath & CAShapeLayer initial animation jump
我为我的公司构建了这个:https://github.com/busycm/BZYStrokeTimer,在构建过程中,我注意到一个有趣的 "bug",我在使用 UIBezierPath
时似乎无法缓解。就在动画开始时,路径会向前(或向后,取决于它是否逆时针)跳跃一定数量的像素,而不是从平滑的增量动画开始。我发现真正有趣的是,路径向前跳转多少实际上是 CAShaperLayer
.
的线宽值
因此,例如,如果我的贝塞尔曲线路径从 CGRectGetMidX(self.bounds)
开始并且行是 35,则动画实际上从 CGRectGetMidX(self.bounds)+35
开始并且线宽越大,跳跃越明显是。有没有办法摆脱它,以便路径从起点平滑地动画出来?
这是第一帧的图片。这是动画开始后的样子。
然后当我恢复动画并再次暂停时,移动的距离大约是您在图片中看到的距离的1/100。
这是我的贝塞尔路径代码:
- (UIBezierPath *)generatePathWithXInset:(CGFloat)dx withYInset:(CGFloat)dy clockWise:(BOOL)clockwise{
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(CGRectGetMidX(self.bounds)+dx/2, dy/2)];
[path addLineToPoint:CGPointMake(CGRectGetMaxX(self.bounds)-dx/2, dy/2)];
[path addLineToPoint:CGPointMake(CGRectGetMaxX(self.bounds)-dx/2, CGRectGetMaxY(self.bounds)-dy/2)];
[path addLineToPoint:CGPointMake(dx/2, CGRectGetMaxY(self.bounds)-dy/2)];
[path addLineToPoint:CGPointMake(dx/2, dy/2)];
[path closePath];
return clockwise ? path : [path bezierPathByReversingPath];
}
动画代码如下:
CABasicAnimation *wind = [self generateAnimationWithDuration:self.duration == 0 ? kDefaultDuration : self.duration fromValue:@(self.shapeLayer.strokeStart) toValue:@(self.shapeLayer.strokeEnd) withKeypath:keypath withFillMode:kCAFillModeForwards];
wind.timingFunction = [CAMediaTimingFunction functionWithName:self.timingFunction];
wind.removedOnCompletion = NO;
self.shapeLayer.path = [self generatePathWithXInset:self.lineWidth withYInset:self.lineWidth clockWise:self.clockwise].CGPath;
[self.shapeLayer addAnimation:wind forKey:@"strokeEndAnimation"];
下面是我构建 CAShapeLayer
.
的方式
- (CAShapeLayer *)shapeLayer {
return !_shapeLayer ? _shapeLayer = ({
CAShapeLayer *layer = [CAShapeLayer layer];
layer.lineWidth = kDefaultLineWidth;
layer.fillColor = UIColor.clearColor.CGColor;
layer.strokeColor = [UIColor blackColor].CGColor;
layer.lineCap = kCALineCapSquare;
layer.frame = self.bounds;
layer.strokeStart = 0;
layer.strokeEnd = 1;
layer;
}) : _shapeLayer;
}
我认为这里发生的事情是,在动画的这一帧中,您正在绘制一条由一个点组成的线。由于线条具有与之关联的粗细,并且线帽类型为 kCALineCapSquare
,因此将呈现为高度和宽度等于线条宽度的正方形。
你可以把它想象成你正在用一个方形标记画一条线,你将拖动标记的中点,使其穿过你指定的曲线中的每个点。对于直线中的第一个点,就好像标记在那一点触地,留下一个正方形。
Here's 不同线帽类型的视觉表示,有望使其更加直观。您可能应该将线帽样式更改为 kCALineCapButt
.
旁注:
进行更改后,在这行代码中
[path moveToPoint:CGPointMake(CGRectGetMidX(self.bounds)+dx/2, dy/2)];
您可能不必再将 x
坐标偏移 dx/2
。
我为我的公司构建了这个:https://github.com/busycm/BZYStrokeTimer,在构建过程中,我注意到一个有趣的 "bug",我在使用 UIBezierPath
时似乎无法缓解。就在动画开始时,路径会向前(或向后,取决于它是否逆时针)跳跃一定数量的像素,而不是从平滑的增量动画开始。我发现真正有趣的是,路径向前跳转多少实际上是 CAShaperLayer
.
因此,例如,如果我的贝塞尔曲线路径从 CGRectGetMidX(self.bounds)
开始并且行是 35,则动画实际上从 CGRectGetMidX(self.bounds)+35
开始并且线宽越大,跳跃越明显是。有没有办法摆脱它,以便路径从起点平滑地动画出来?
这是第一帧的图片。这是动画开始后的样子。
然后当我恢复动画并再次暂停时,移动的距离大约是您在图片中看到的距离的1/100。
这是我的贝塞尔路径代码:
- (UIBezierPath *)generatePathWithXInset:(CGFloat)dx withYInset:(CGFloat)dy clockWise:(BOOL)clockwise{
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(CGRectGetMidX(self.bounds)+dx/2, dy/2)];
[path addLineToPoint:CGPointMake(CGRectGetMaxX(self.bounds)-dx/2, dy/2)];
[path addLineToPoint:CGPointMake(CGRectGetMaxX(self.bounds)-dx/2, CGRectGetMaxY(self.bounds)-dy/2)];
[path addLineToPoint:CGPointMake(dx/2, CGRectGetMaxY(self.bounds)-dy/2)];
[path addLineToPoint:CGPointMake(dx/2, dy/2)];
[path closePath];
return clockwise ? path : [path bezierPathByReversingPath];
}
动画代码如下:
CABasicAnimation *wind = [self generateAnimationWithDuration:self.duration == 0 ? kDefaultDuration : self.duration fromValue:@(self.shapeLayer.strokeStart) toValue:@(self.shapeLayer.strokeEnd) withKeypath:keypath withFillMode:kCAFillModeForwards];
wind.timingFunction = [CAMediaTimingFunction functionWithName:self.timingFunction];
wind.removedOnCompletion = NO;
self.shapeLayer.path = [self generatePathWithXInset:self.lineWidth withYInset:self.lineWidth clockWise:self.clockwise].CGPath;
[self.shapeLayer addAnimation:wind forKey:@"strokeEndAnimation"];
下面是我构建 CAShapeLayer
.
- (CAShapeLayer *)shapeLayer {
return !_shapeLayer ? _shapeLayer = ({
CAShapeLayer *layer = [CAShapeLayer layer];
layer.lineWidth = kDefaultLineWidth;
layer.fillColor = UIColor.clearColor.CGColor;
layer.strokeColor = [UIColor blackColor].CGColor;
layer.lineCap = kCALineCapSquare;
layer.frame = self.bounds;
layer.strokeStart = 0;
layer.strokeEnd = 1;
layer;
}) : _shapeLayer;
}
我认为这里发生的事情是,在动画的这一帧中,您正在绘制一条由一个点组成的线。由于线条具有与之关联的粗细,并且线帽类型为 kCALineCapSquare
,因此将呈现为高度和宽度等于线条宽度的正方形。
你可以把它想象成你正在用一个方形标记画一条线,你将拖动标记的中点,使其穿过你指定的曲线中的每个点。对于直线中的第一个点,就好像标记在那一点触地,留下一个正方形。
Here's 不同线帽类型的视觉表示,有望使其更加直观。您可能应该将线帽样式更改为 kCALineCapButt
.
旁注: 进行更改后,在这行代码中
[path moveToPoint:CGPointMake(CGRectGetMidX(self.bounds)+dx/2, dy/2)];
您可能不必再将 x
坐标偏移 dx/2
。