画一张饼图,并且带动画效果
和点击效果
如下图所示:
如图,
白色分割线是
layer
的lineWidth
;动画效果:是加了一个遮罩
.mask
,在下面就会有实现代码;点击效果:是点击的是哪一个扇形图,就将它的
半径
加长。
相关链接:1、iOS 饼状图(扇形图)动画效果的实现,还可以在扇形图上添加UILabel
文字
2、iOS 带指示线说明的饼状图
3、Touch:判断当前点击的位置是否在某个视图上
4、CALayer之mask属性-遮罩
下面是实现代码:
@interface GW_CareerHomeHeaderView ()
/** 扇形图:大小同一个中心点 */
@property (nonatomic, strong) UIView *fanChartFatherView; // 扇形图的父view
@property (nonatomic, strong) CAShapeLayer *big_FanChart_FatherLayer; // 大扇形图 父layer:点击小扇形图时,放大
@property (nonatomic, strong) CAShapeLayer *small_FanChart_FatherLayer; // (遮罩层)
/// 点击扇形图,在其位置上添加一个(相同角度和颜色)半径放大的扇形图(每次点击删掉重新创建)
//@property (nonatomic, strong) CAShapeLayer *selectedIndex_Layer; // 新创建覆盖在当前点击的layer上,视觉效果有点不太好,不推荐这种方法
/// 扇形图,之前形态是否是“放大版”(大:选中状态, 小:非选中状态):YES 当前是“放大版”,NO 当前是“缩小版”
@property (nonatomic, assign) BOOL previous_is_selected_FanChart_Big;
@property (nonatomic, assign) NSInteger previous_SelextedIndex_fanChart; // 上一次选中的是第几个
@property (nonatomic, strong) NSArray *statistical_TypeArray;
@end
1、首先,根据数据,创建扇形图(饼图)、遮罩层、创建完成再添加动画效果:
#pragma mark -- 类型分布 扇形图 --
/// 当统计没有数据时,改变高度,并显示缺省图
- (void)refreshChangeStatistical {
// 数据所占百分比:5%,10%,15%,20%,50%
self.statistical_TypeArray = @[
@"5",@"15", @"10",@"20", @"50"
];
// 饼图(点击某一个扇形图,放大效果):
// 首先,在创建之前,先将可能已有的删除掉:
[self.fanChartFatherView removeFromSuperview];
[self.big_FanChart_FatherLayer removeFromSuperlayer];
[self.small_FanChart_FatherLayer removeFromSuperlayer];
_fanChartFatherView = [[UIView alloc] initWithFrame:CGRectMake([UIScreen mainScreen].bounds.size.width/375*13, [UIScreen mainScreen].bounds.size.width/375*(457 - 13 - 173), [UIScreen mainScreen].bounds.size.width/375*173, [UIScreen mainScreen].bounds.size.width/375*173)];
self.fanChartFatherView.backgroundColor = RGBA(218, 255, 251, 1);
// self.fanChartFatherView.backgroundColor = UIColor.whiteColor;
[self.backView addSubview:self.fanChartFatherView];
/// 贝塞尔曲线(父layer,每次用时removeFromSuperlayer删除一下)
/// 大(扇形图和mask遮罩层的父layer):
_big_FanChart_FatherLayer = [[CAShapeLayer alloc] init];
_big_FanChart_FatherLayer.backgroundColor = [UIColor whiteColor].CGColor;
UIBezierPath *big_BackbezierPath = [UIBezierPath
bezierPathWithRoundedRect:CGRectMake(0, 0, self.fanChartFatherView.frame.size.width, self.fanChartFatherView.frame.size.height)
byRoundingCorners:UIRectCornerTopLeft | UIRectCornerTopRight
cornerRadii:CGSizeMake([UIScreen mainScreen].bounds.size.width/375*0, [UIScreen mainScreen].bounds.size.width/375*0)];
_big_FanChart_FatherLayer.lineWidth = [UIScreen mainScreen].bounds.size.width/375*0.01;
// 颜色
_big_FanChart_FatherLayer.strokeColor = [UIColor clearColor].CGColor;
// 背景填充色
_big_FanChart_FatherLayer.fillColor = [UIColor clearColor].CGColor;
_big_FanChart_FatherLayer.path = [big_BackbezierPath CGPath];
[self.fanChartFatherView.layer addSublayer:self.big_FanChart_FatherLayer];
/** 准备画扇形图: */
// 扇形中心点
CGFloat centerX = self.fanChartFatherView.frame.size.width * 0.5f;
CGFloat centerY = self.fanChartFatherView.frame.size.height * 0.5f;
CGPoint centerPoint = CGPointMake(centerX, centerY);
CGFloat big_radius = [UIScreen mainScreen].bounds.size.width/375*(85); // 大半径
CGFloat small_radius = [UIScreen mainScreen].bounds.size.width/375*(77); // 小半径
// 大 饼图:(遮罩层):radius 是1/2的半径长度,(在这里的是大半径big_radius的原因是:之后,点击某一个扇形图之后,该扇形图的半径会变大显示,但变大之后的半径不会超过big_radius)
UIBezierPath *bigFanChartPath = [UIBezierPath bezierPathWithArcCenter:centerPoint
radius:big_radius/2
startAngle:-M_PI_2
endAngle:M_PI_2 * 3
clockwise:YES];
_small_FanChart_FatherLayer = [CAShapeLayer layer];
// 填充色
_small_FanChart_FatherLayer.fillColor = [UIColor clearColor].CGColor;
// 线条颜色
_small_FanChart_FatherLayer.strokeColor = [UIColor whiteColor].CGColor;
// 线条宽度
_small_FanChart_FatherLayer.lineWidth = big_radius;
_small_FanChart_FatherLayer.strokeStart = 0.0f;
_small_FanChart_FatherLayer.strokeEnd = 1.0f;
_small_FanChart_FatherLayer.zPosition = 1;
_small_FanChart_FatherLayer.path = [bigFanChartPath CGPath];
// _small_FanChart_FatherLayer.backgroundColor = RGBA(0, 0, 0, 1).CGColor;
// 遮罩
self.big_FanChart_FatherLayer.mask = self.small_FanChart_FatherLayer;
// 计算每个类型所占总数的百分比:
CGFloat fanChart_total = 0.0f;
for (int i = 0; i < self.statistical_TypeArray.count; i++) {
fanChart_total = fanChart_total + [self.statistical_TypeArray[i] floatValue];
}
CGFloat small_start = 0.0f;
CGFloat small_end = 0.0f;
for (int i = 0; i < self.statistical_TypeArray.count; i++) {
// 计算当前end位置 = 上一个结束位置 + 当前部分百分比 =(当前部分结束为止的百分比+上一次结束的百分比)
small_end = [self.statistical_TypeArray[i] floatValue] / fanChart_total + small_start;
NSLog(@"😁😁small_start = %f, small_end = %f", small_start, small_end);
//图层
CAShapeLayer *subLayer = [CAShapeLayer layer];
subLayer.backgroundColor = RGBA(0, 0, 0, 1).CGColor;
// 背景填充色(每个数据一个颜色)
if (i == 0) {
subLayer.fillColor = RGBA(226, 73, 85, 1).CGColor;
} else if (i == 1) {
subLayer.fillColor = RGBA(255, 196, 15, 1).CGColor;
} else if (i == 2) {
subLayer.fillColor = RGBA(153, 153, 153, 1).CGColor;
} else if (i == 3) {
subLayer.fillColor = RGBA(60, 134, 196, 1).CGColor;
} else if (i == 4) {
subLayer.fillColor = RGBA(33, 63, 111, 1).CGColor;
}
else {
subLayer.fillColor = UIColor.blackColor.CGColor;
}
// 线条的颜色
subLayer.strokeColor = UIColor.whiteColor.CGColor;
// 线宽
subLayer.lineWidth = [UIScreen mainScreen].bounds.size.width/375*2;
// 在z轴上的位置 支持隐式动画
subLayer.zPosition = 2;
subLayer.lineJoin = kCALineJoinBevel; // 尖角 kCALineJoinMiter, 圆角 kCALineJoinRound, 缺角 kCALineJoinBevel
//subLayer.lineCap = kCALineCapRound; // 无端点 kCALineCapButt,圆形端点 kCALineCapRound,方形端点 kCALineCapSquare
// 注意📢:下边的贝塞尔曲线,已经设置好start和end了,这里不在需要设置(如果设置下边strokeStart和strokeEnd的话,lineWidth显示出问题)
//subLayer.strokeStart = small_start;
//subLayer.strokeEnd = small_end;
// 初始化一个路径:创建圆弧 ,startAngle:起始点,endAngle:终止点,clockwise:顺时针方向 ,M_PI == π:3.1415926
// clockwise 顺时针 YES, 逆时针 NO, (M_PI_2 = π/2) = 270°
// UIBezierPath *small_FanChartPath = [UIBezierPath bezierPathWithArcCenter:centerPoint
// radius:small_radius
// startAngle:-M_PI_2 + M_PI*2*small_start
// endAngle:-M_PI_2 + M_PI*2*small_end
// clockwise:YES];
// [small_FanChartPath closePath];
// // 每个扇形 同角度方向,偏移5,造成有间隔的形状
// CGFloat centerAngle = M_PI * (big_start + big_end);
// CGFloat centerPointX = 5 * sinf(centerAngle) + centerX;
// CGFloat centerPointY = -5 * cosf(centerAngle) + centerY;
// CGPoint centerPoint1 = CGPointMake(centerPointX, centerPointY);
UIBezierPath *small_FanChartPath = [UIBezierPath bezierPath];
// [small_FanChartPath moveToPoint:centerPoint]; // 设置closePath后,就不必设置moveToPoint了
[small_FanChartPath addArcWithCenter:centerPoint
radius:small_radius
startAngle:-M_PI_2 + M_PI*2*small_start
endAngle:-M_PI_2 + M_PI*2*small_end
clockwise:YES];
[small_FanChartPath addLineToPoint:centerPoint];
// 闭合路径,即在终点和起点连一根线
[small_FanChartPath closePath];
// 将UIBezierPath类转换成CGPath,类似于UIColor的CGColor
subLayer.path = [small_FanChartPath CGPath];
[self.big_FanChart_FatherLayer addSublayer:subLayer];
// [self.fanChartFatherView.layer addSublayer:subLayer];
// 计算下一个start位置 = 当前end位置
small_start = small_end;
}
[self stroke];
}
- (void)stroke {
//画图动画
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
animation.duration = 3;
animation.fromValue = @0.0f;
animation.toValue = @1.0f;
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
animation.removedOnCompletion = YES;
[self.small_FanChart_FatherLayer addAnimation:animation forKey:@"circleAnimation"];
}
2、饼图中扇形点击事件
CAShapeLayer *subLayr = self.big_FanChart_FatherLayer.sublayers[i];
选中某一个扇形图,改变其半径的长短,实现放大缩小的效果
#pragma mark -- 饼图中扇形点击事件方法 --
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(nullable UIEvent *)event {
UITouch *touch = touches.anyObject;
// CGPoint point = [touch locationInView:self.fanChartFatherView];
CGPoint point = [touch preciseLocationInView:self.fanChartFatherView];
// 如果矩形不为null,或空,并且该点位于矩形内,返回YES,在范围外面 返回NO
if (CGRectContainsPoint(self.fanChartFatherView.bounds, point)) {
// NSLog(@"😆😆 结束触摸屏幕 手指所在 view上的位置 point.X ==== %f,\n point.Y ==== %f", point.x, point.y);
NSInteger selectIndex = [self getCurrentSelectedOneTouch:point];
if (selectIndex >= 0) { //点击第几个扇形图,那个扇形图就改变其半径长度
// 扇形中心点
CGFloat centerX = self.fanChartFatherView.frame.size.width * 0.5f;
CGFloat centerY = self.fanChartFatherView.frame.size.height * 0.5f;
CGPoint centerPoint = CGPointMake(centerX, centerY);
CGFloat big_radius = [UIScreen mainScreen].bounds.size.width/375*(85); // 大半径
CGFloat small_radius = [UIScreen mainScreen].bounds.size.width/375*(77); // 小半径
// 计算每个类型所占总数的百分比:
CGFloat fanChart_total = 0.0f;
for (int i = 0; i < self.statistical_TypeArray.count; i++) {
fanChart_total = fanChart_total + [self.statistical_TypeArray[i] floatValue];
}
CGFloat small_start = 0.0f;
CGFloat small_end = 0.0f;
// [self.selectedIndex_Layer removeFromSuperlayer]; // 新创建覆盖在当前点击的layer上,视觉效果有点不太好,不推荐这种方法
for (int i = 0; i < self.statistical_TypeArray.count; i++) {
// 计算当前end位置 = 上一个结束位置 + 当前部分百分比 =(当前部分结束为止的百分比+上一次结束的百分比)
small_end = [self.statistical_TypeArray[i] floatValue] / fanChart_total + small_start;
CAShapeLayer *subLayr = self.big_FanChart_FatherLayer.sublayers[i];
/** 选中某一个扇形图,改变其半径的长短,实现放大缩小的效果 */
if (i == selectIndex) {
if (selectIndex == self.previous_SelextedIndex_fanChart) {
if (self.previous_is_selected_FanChart_Big == YES) {
UIBezierPath *small_FanChartPath = [UIBezierPath bezierPath];
[small_FanChartPath addArcWithCenter:centerPoint
radius:small_radius
startAngle:-M_PI_2 + M_PI*2*small_start
endAngle:-M_PI_2 + M_PI*2*small_end
clockwise:YES];
[small_FanChartPath addLineToPoint:centerPoint];
// 闭合路径,即在终点和起点连一根线
[small_FanChartPath closePath];
subLayr.path = [small_FanChartPath CGPath];
self.previous_is_selected_FanChart_Big = NO;
} else {
UIBezierPath *small_FanChartPath = [UIBezierPath bezierPath];
[small_FanChartPath addArcWithCenter:centerPoint
radius:big_radius
startAngle:-M_PI_2 + M_PI*2*small_start
endAngle:-M_PI_2 + M_PI*2*small_end
clockwise:YES];
[small_FanChartPath addLineToPoint:centerPoint];
// 闭合路径,即在终点和起点连一根线
[small_FanChartPath closePath];
subLayr.path = [small_FanChartPath CGPath];
self.previous_is_selected_FanChart_Big = YES;
}
} else {
UIBezierPath *small_FanChartPath = [UIBezierPath bezierPath];
[small_FanChartPath addArcWithCenter:centerPoint
radius:big_radius
startAngle:-M_PI_2 + M_PI*2*small_start
endAngle:-M_PI_2 + M_PI*2*small_end
clockwise:YES];
[small_FanChartPath addLineToPoint:centerPoint];
// 闭合路径,即在终点和起点连一根线
[small_FanChartPath closePath];
subLayr.path = [small_FanChartPath CGPath];
self.previous_is_selected_FanChart_Big = YES;
}
// 更新选中的是第几个
self.previous_SelextedIndex_fanChart = selectIndex;
} else {
UIBezierPath *small_FanChartPath = [UIBezierPath bezierPath];
[small_FanChartPath addArcWithCenter:centerPoint
radius:small_radius
startAngle:-M_PI_2 + M_PI*2*small_start
endAngle:-M_PI_2 + M_PI*2*small_end
clockwise:YES];
[small_FanChartPath addLineToPoint:centerPoint];
// 闭合路径,即在终点和起点连一根线
[small_FanChartPath closePath];
subLayr.path = [small_FanChartPath CGPath];
}
/**
/// 新创建覆盖在当前点击的layer上,视觉效果有点不太好,不推荐这种方法
if (i == selectIndex) {
//图层
_selectedIndex_Layer = [CAShapeLayer layer];
_selectedIndex_Layer.backgroundColor = RGBA(0, 0, 0, 1).CGColor;
// 背景填充色
if (i == 0) {
_selectedIndex_Layer.fillColor = RGBA(226, 73, 85, 1).CGColor;
} else if (i == 1) {
_selectedIndex_Layer.fillColor = RGBA(255, 196, 15, 1).CGColor;
} else if (i == 2) {
_selectedIndex_Layer.fillColor = RGBA(153, 153, 153, 1).CGColor;
} else if (i == 3) {
_selectedIndex_Layer.fillColor = RGBA(60, 134, 196, 1).CGColor;
} else if (i == 4) {
_selectedIndex_Layer.fillColor = RGBA(33, 63, 111, 1).CGColor;
} else {
_selectedIndex_Layer.fillColor = UIColor.blackColor.CGColor;
}
// 线条的颜色
_selectedIndex_Layer.strokeColor = UIColor.whiteColor.CGColor;
// 线宽
_selectedIndex_Layer.lineWidth = [UIScreen mainScreen].bounds.size.width/375*2;
// 在z轴上的位置 支持隐式动画
_selectedIndex_Layer.zPosition = 2;
_selectedIndex_Layer.lineJoin = kCALineJoinBevel; // 尖角 kCALineJoinMiter, 圆角 kCALineJoinRound, 缺角 kCALineJoinBevel
UIBezierPath *small_FanChartPath = [UIBezierPath bezierPath];
[small_FanChartPath addArcWithCenter:centerPoint
radius:big_radius
startAngle:-M_PI_2 + M_PI*2*small_start
endAngle:-M_PI_2 + M_PI*2*small_end
clockwise:YES];
[small_FanChartPath addLineToPoint:centerPoint];
// 闭合路径,即在终点和起点连一根线
[small_FanChartPath closePath];
// 将UIBezierPath类转换成CGPath,类似于UIColor的CGColor
_selectedIndex_Layer.path = [small_FanChartPath CGPath];
[self.big_FanChart_FatherLayer addSublayer:self.selectedIndex_Layer];
}
*/
// 计算下一个start位置 = 当前end位置
small_start = small_end;
}
[self.big_FanChart_FatherLayer display];
}
}
}
/// 点击的区域,在第几个扇形图的范围,返回点击的第一个扇形
- (NSInteger)getCurrentSelectedOneTouch:(CGPoint)point {
__block NSInteger selectIndex = -1;
// 坐标重置
CGAffineTransform transform = CGAffineTransformIdentity;
// 遍历饼图中的子layer(self.big_FanChart_FatherLayer 是扇形图layer的父layer)
[self.big_FanChart_FatherLayer.sublayers enumerateObjectsUsingBlock:^(__kindof CALayer * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
CAShapeLayer *shapeLayer = (CAShapeLayer *)obj;
CGPathRef path = [shapeLayer path];
// CGPathContainsPoint:如果触摸的点包含在路径内,则返回YES
if (CGPathContainsPoint(path, &transform, point, 0)) {
selectIndex = idx;
NSLog(@"👍🏻👍🏻点击的是第几个扇形图 = %ld", selectIndex);
}
}];
return selectIndex;
}
网友评论