UIBezierPath 在上一条路径之上绘制?
UIBezierPath Draw On Top of Previous Path?
我正在尝试自定义 EChart,这是一个 Github 上的条形图库,用于我自己的目的。在这个文件中:https://github.com/zhuhuihuihui/EChart/blob/master/EChart/EColumn.m
我正在尝试修改 setGrade:
方法,这样它就不会从图表底部向上重新绘制整个条形,而是只在条形顶部绘制到下一个所需的 Y 位置在图中。我在下面附上了一张 GIF 来说明我遇到的问题。
这是绘制此栏的代码(连同其下方的 CABasicAnimation):
-(void)setGrade:(float)grade {
_grade = grade;
UIBezierPath *progressline = [UIBezierPath bezierPath];
[progressline moveToPoint:CGPointMake(self.frame.size.width/2.0, self.frame.size.height)];
[progressline addLineToPoint:CGPointMake(self.frame.size.width/2.0, (1 - grade) * self.frame.size.height)];
_chartLine.path = progressline.CGPath;
_chartLine.strokeEnd = 1.0;
CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
pathAnimation.duration = 1.0;
pathAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
pathAnimation.fromValue = [NSNumber numberWithFloat:0.0f];
pathAnimation.toValue = [NSNumber numberWithFloat:1.0f];
[_chartLine addAnimation:pathAnimation forKey:@"strokeEndAnimation"];
}
此代码是从 EColumnChart.m (https://github.com/zhuhuihuihui/EChart/blob/master/EChart/EColumnChart.m) 调用的
这是相关的部分,第一个 if 只是第一次初始化一个柱,第二个我定制为只更新值,调用 setGrade:
方法
if (nil == eColumn) {
eColumn = [[EColumn alloc] initWithFrame:CGRectMake(widthOfTheColumnShouldBe * 0.5 + (i * widthOfTheColumnShouldBe * 1.5), 0, widthOfTheColumnShouldBe, self.frame.size.height)];
eColumn.shouldAnimate = NO;
eColumn.backgroundColor = [UIColor clearColor];
eColumn.grade = eColumnDataModel.value / _fullValueOfTheGraph;
eColumn.eColumnDataModel = eColumnDataModel;
[eColumn setDelegate:self];
[self addSubview:eColumn];
} else {
eColumn.shouldAnimate = YES;
eColumn.grade = eColumnDataModel.value / _fullValueOfTheGraph;
}
[_eColumns setObject:eColumn forKey:[NSNumber numberWithInteger:currentIndex ]];
_fullValueOfTheGraph 有自己的算法来确定应该在图表上绘制多远的条形图。 eColumnDataModel.value 只是我给它的一个整数,它是条形列的 Y 值。
我对贝塞尔曲线非常陌生。是使贝塞尔曲线路径成为 ivar 并以某种方式跟踪顶部位置的技巧吗?
更新:
我曾尝试在 setGrade: 方法的开头部分使用此代码,但它只会在上次更新和当前更新之间产生细微差别。我添加了一个 prevGrade ivar,这是用于更新栏的最后一个等级。它很接近,但好像我需要组合贝塞尔曲线路径才能获得完整的条形。有什么想法吗?
UIBezierPath *_progressline = [UIBezierPath bezierPath];
if (_prevGrade != 0.0f) {
if (grade < _prevGrade) { //Bar graph going down
[_progressline moveToPoint:CGPointMake(self.frame.size.width/2.0, (1 + _prevGrade) * self.frame.size.height)];
} else { //Bar graph going up
[_progressline moveToPoint:CGPointMake(self.frame.size.width/2.0, (1 - _prevGrade) * self.frame.size.height)];
}
} else {
[_progressline moveToPoint:CGPointMake(self.frame.size.width/2.0, self.frame.size.height)];
}
[_progressline addLineToPoint:CGPointMake(self.frame.size.width/2.0, (1 - grade) * self.frame.size.height)];
我认为您需要的是 CAShapeLayer
的动画 path
而不是 strokeEnd
。我一直在测试您共享的代码库中的代码,并进行了一些更改以设置动画 _chartLine
.
这是setGrade:
方法的代码
-(void)setGrade:(float)grade
{
_grade = grade;
if(_chartLine.path == nil)
{
UIBezierPath *_progressline = [UIBezierPath bezierPath];
if (_prevGrade != 0.0f) {
if (grade < _prevGrade) { //Bar graph going down
[_progressline moveToPoint:CGPointMake(self.frame.size.width/2.0, (1 + _prevGrade) * self.frame.size.height)];
} else { //Bar graph going up
[_progressline moveToPoint:CGPointMake(self.frame.size.width/2.0, (1 - _prevGrade) * self.frame.size.height)];
}
} else {
[_progressline moveToPoint:CGPointMake(self.frame.size.width/2.0, self.frame.size.height)];
}
[_progressline addLineToPoint:CGPointMake(self.frame.size.width/2.0, (1 - grade) * self.frame.size.height)];
_chartLine.path = _progressline.CGPath;
_chartLine.strokeEnd = 1.0;
}else
{
CAKeyframeAnimation *morph = [CAKeyframeAnimation animationWithKeyPath:@"path"];
morph.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
morph.values = [self getPathValuesForAnimation:_chartLine startGrade:_prevGrade neededGrade:_grade];
morph.duration = 1;
morph.removedOnCompletion = YES;
morph.fillMode = kCAFillModeForwards;
morph.delegate = self;
[_chartLine addAnimation:morph forKey:@"pathAnimation"];
_chartLine.path = (__bridge CGPathRef _Nullable)((id)[morph.values lastObject]);
}
_prevGrade = grade;
}
//this method return values for path animation
-(NSArray*)getPathValuesForAnimation:(CAShapeLayer*)layer startGrade:(float)currentGrade neededGrade:(float)neededGrade
{
NSMutableArray * values = [NSMutableArray array];
if(_prevGrade != 0.0f)
[values addObject:(id)layer.path];
UIBezierPath * finalPath = [UIBezierPath bezierPath];
[finalPath moveToPoint:CGPointMake(self.frame.size.width/2.0, self.frame.size.height)];
[finalPath addLineToPoint:CGPointMake(self.frame.size.width/2.0,(1 - neededGrade)*self.frame.size.height)];
[values addObject:(id)[finalPath CGPath]];
return values;
}
我希望这对你有帮助,对我来说效果很好,问候
这就是我的工作方式,(我的 gif 导出器不好,所以你看到的所有故障都是我的 gif 导出器的问题)
这是_chartLine.lineCap = kCALineCapButt;
这是 _chartLine.lineCap = kCALineCapRound;
我正在尝试自定义 EChart,这是一个 Github 上的条形图库,用于我自己的目的。在这个文件中:https://github.com/zhuhuihuihui/EChart/blob/master/EChart/EColumn.m
我正在尝试修改 setGrade:
方法,这样它就不会从图表底部向上重新绘制整个条形,而是只在条形顶部绘制到下一个所需的 Y 位置在图中。我在下面附上了一张 GIF 来说明我遇到的问题。
这是绘制此栏的代码(连同其下方的 CABasicAnimation):
-(void)setGrade:(float)grade {
_grade = grade;
UIBezierPath *progressline = [UIBezierPath bezierPath];
[progressline moveToPoint:CGPointMake(self.frame.size.width/2.0, self.frame.size.height)];
[progressline addLineToPoint:CGPointMake(self.frame.size.width/2.0, (1 - grade) * self.frame.size.height)];
_chartLine.path = progressline.CGPath;
_chartLine.strokeEnd = 1.0;
CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
pathAnimation.duration = 1.0;
pathAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
pathAnimation.fromValue = [NSNumber numberWithFloat:0.0f];
pathAnimation.toValue = [NSNumber numberWithFloat:1.0f];
[_chartLine addAnimation:pathAnimation forKey:@"strokeEndAnimation"];
}
此代码是从 EColumnChart.m (https://github.com/zhuhuihuihui/EChart/blob/master/EChart/EColumnChart.m) 调用的
这是相关的部分,第一个 if 只是第一次初始化一个柱,第二个我定制为只更新值,调用 setGrade:
方法
if (nil == eColumn) {
eColumn = [[EColumn alloc] initWithFrame:CGRectMake(widthOfTheColumnShouldBe * 0.5 + (i * widthOfTheColumnShouldBe * 1.5), 0, widthOfTheColumnShouldBe, self.frame.size.height)];
eColumn.shouldAnimate = NO;
eColumn.backgroundColor = [UIColor clearColor];
eColumn.grade = eColumnDataModel.value / _fullValueOfTheGraph;
eColumn.eColumnDataModel = eColumnDataModel;
[eColumn setDelegate:self];
[self addSubview:eColumn];
} else {
eColumn.shouldAnimate = YES;
eColumn.grade = eColumnDataModel.value / _fullValueOfTheGraph;
}
[_eColumns setObject:eColumn forKey:[NSNumber numberWithInteger:currentIndex ]];
_fullValueOfTheGraph 有自己的算法来确定应该在图表上绘制多远的条形图。 eColumnDataModel.value 只是我给它的一个整数,它是条形列的 Y 值。
我对贝塞尔曲线非常陌生。是使贝塞尔曲线路径成为 ivar 并以某种方式跟踪顶部位置的技巧吗?
更新: 我曾尝试在 setGrade: 方法的开头部分使用此代码,但它只会在上次更新和当前更新之间产生细微差别。我添加了一个 prevGrade ivar,这是用于更新栏的最后一个等级。它很接近,但好像我需要组合贝塞尔曲线路径才能获得完整的条形。有什么想法吗?
UIBezierPath *_progressline = [UIBezierPath bezierPath];
if (_prevGrade != 0.0f) {
if (grade < _prevGrade) { //Bar graph going down
[_progressline moveToPoint:CGPointMake(self.frame.size.width/2.0, (1 + _prevGrade) * self.frame.size.height)];
} else { //Bar graph going up
[_progressline moveToPoint:CGPointMake(self.frame.size.width/2.0, (1 - _prevGrade) * self.frame.size.height)];
}
} else {
[_progressline moveToPoint:CGPointMake(self.frame.size.width/2.0, self.frame.size.height)];
}
[_progressline addLineToPoint:CGPointMake(self.frame.size.width/2.0, (1 - grade) * self.frame.size.height)];
我认为您需要的是 CAShapeLayer
的动画 path
而不是 strokeEnd
。我一直在测试您共享的代码库中的代码,并进行了一些更改以设置动画 _chartLine
.
这是setGrade:
方法的代码
-(void)setGrade:(float)grade
{
_grade = grade;
if(_chartLine.path == nil)
{
UIBezierPath *_progressline = [UIBezierPath bezierPath];
if (_prevGrade != 0.0f) {
if (grade < _prevGrade) { //Bar graph going down
[_progressline moveToPoint:CGPointMake(self.frame.size.width/2.0, (1 + _prevGrade) * self.frame.size.height)];
} else { //Bar graph going up
[_progressline moveToPoint:CGPointMake(self.frame.size.width/2.0, (1 - _prevGrade) * self.frame.size.height)];
}
} else {
[_progressline moveToPoint:CGPointMake(self.frame.size.width/2.0, self.frame.size.height)];
}
[_progressline addLineToPoint:CGPointMake(self.frame.size.width/2.0, (1 - grade) * self.frame.size.height)];
_chartLine.path = _progressline.CGPath;
_chartLine.strokeEnd = 1.0;
}else
{
CAKeyframeAnimation *morph = [CAKeyframeAnimation animationWithKeyPath:@"path"];
morph.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];
morph.values = [self getPathValuesForAnimation:_chartLine startGrade:_prevGrade neededGrade:_grade];
morph.duration = 1;
morph.removedOnCompletion = YES;
morph.fillMode = kCAFillModeForwards;
morph.delegate = self;
[_chartLine addAnimation:morph forKey:@"pathAnimation"];
_chartLine.path = (__bridge CGPathRef _Nullable)((id)[morph.values lastObject]);
}
_prevGrade = grade;
}
//this method return values for path animation
-(NSArray*)getPathValuesForAnimation:(CAShapeLayer*)layer startGrade:(float)currentGrade neededGrade:(float)neededGrade
{
NSMutableArray * values = [NSMutableArray array];
if(_prevGrade != 0.0f)
[values addObject:(id)layer.path];
UIBezierPath * finalPath = [UIBezierPath bezierPath];
[finalPath moveToPoint:CGPointMake(self.frame.size.width/2.0, self.frame.size.height)];
[finalPath addLineToPoint:CGPointMake(self.frame.size.width/2.0,(1 - neededGrade)*self.frame.size.height)];
[values addObject:(id)[finalPath CGPath]];
return values;
}
我希望这对你有帮助,对我来说效果很好,问候
这就是我的工作方式,(我的 gif 导出器不好,所以你看到的所有故障都是我的 gif 导出器的问题)
这是_chartLine.lineCap = kCALineCapButt;
这是 _chartLine.lineCap = kCALineCapRound;