畫一張餅圖,并且?guī)?strong>動畫效果和點擊效果
如下圖所示:
RPReplay_Final1663921137.GIF
如圖,
白色分割線是
layer的lineWidth;動畫效果:是加了一個遮罩
.mask,在下面就會有實現(xiàn)代碼;點擊效果:是點擊的是哪一個扇形圖,就將它的
半徑加長。
相關(guān)鏈接:1、iOS 餅狀圖(扇形圖)動畫效果的實現(xiàn),還可以在扇形圖上添加UILabel文字
2、iOS 帶指示線說明的餅狀圖
3、Touch:判斷當前點擊的位置是否在某個視圖上
4、CALayer之mask屬性-遮罩
下面是實現(xiàn)代碼:
@interface GW_CareerHomeHeaderView ()
/** 扇形圖:大小同一個中心點 */
@property (nonatomic, strong) UIView *fanChartFatherView; // 扇形圖的父view
@property (nonatomic, strong) CAShapeLayer *big_FanChart_FatherLayer; // 大扇形圖 父layer:點擊小扇形圖時,放大
@property (nonatomic, strong) CAShapeLayer *small_FanChart_FatherLayer; // (遮罩層)
/// 點擊扇形圖,在其位置上添加一個(相同角度和顏色)半徑放大的扇形圖(每次點擊刪掉重新創(chuàng)建)
//@property (nonatomic, strong) CAShapeLayer *selectedIndex_Layer; // 新創(chuàng)建覆蓋在當前點擊的layer上,視覺效果有點不太好,不推薦這種方法
/// 扇形圖,之前形態(tài)是否是“放大版”(大:選中狀態(tài), ?。悍沁x中狀態(tài)):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、首先,根據(jù)數(shù)據(jù),創(chuàng)建扇形圖(餅圖)、遮罩層、創(chuàng)建完成再添加動畫效果:
#pragma mark -- 類型分布 扇形圖 --
/// 當統(tǒng)計沒有數(shù)據(jù)時,改變高度,并顯示缺省圖
- (void)refreshChangeStatistical {
// 數(shù)據(jù)所占百分比:5%,10%,15%,20%,50%
self.statistical_TypeArray = @[
@"5",@"15", @"10",@"20", @"50"
];
// 餅圖(點擊某一個扇形圖,放大效果):
// 首先,在創(chuàng)建之前,先將可能已有的刪除掉:
[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;
/**
* 因為在畫扇形圖的時候,使用到了“線寬lineWidth”,造成當只有一個扇形圖(即,一整個餅圖)時,
* 因為這行代碼的使用:“[small_FanChartPath addLineToPoint:centerPoint]; ”,在首尾鏈接會造成分割線的情況,
* 所以,當在只有一個圓形“餅圖”的時候,將“[small_FanChartPath addLineToPoint:centerPoint]; ”去掉就可以了。
*/
// 判斷是否只有一個有數(shù)據(jù)(即,獨自占比100%),其他類型都是占比0%,只有一個類型有占比時,首、尾兩個位置,不在中心
NSInteger greaterThanZero = 0; // 遍歷所有類型,其中類型占比大于0的有幾個*(只有一個時,全部是同一個顏色,無間隔白色線;greaterThanZero >= 2,有白色間隔)
for (int i = 0; i < self.statistical_TypeArray.count; i++) {
CGFloat fanChart_subTotal = [self.statistical_TypeArray[i] floatValue];
if (fanChart_subTotal > 0) {
greaterThanZero = greaterThanZero + 1;
}
}
// 計算每個類型所占總數(shù)的百分比:
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位置 = 上一個結(jié)束位置 + 當前部分百分比 =(當前部分結(jié)束為止的百分比+上一次結(jié)束的百分比)
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;
// 背景填充色(每個數(shù)據(jù)一個顏色)
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
// 注意??:下邊的貝塞爾曲線,已經(jīng)設(shè)置好start和end了,這里不在需要設(shè)置(如果設(shè)置下邊strokeStart和strokeEnd的話,lineWidth顯示出問題)
//subLayer.strokeStart = small_start;
//subLayer.strokeEnd = small_end;
// 初始化一個路徑:創(chuàng)建圓弧 ,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]; // 設(shè)置closePath后,就不必設(shè)置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];
if (greaterThanZero >= 2) {
[small_FanChartPath addLineToPoint:centerPoint]; // 如果只有一個扇形圖(即,餅圖),就不寫這個,讓首尾鏈接呈圓形
}
// 閉合路徑,即在終點和起點連一根線
[small_FanChartPath closePath];
// 將UIBezierPath類轉(zhuǎn)換成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];
選中某一個扇形圖,改變其半徑的長短,實現(xiàn)放大縮小的效果
#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,或空,并且該點位于矩形內(nèi),返回YES,在范圍外面 返回NO
if (CGRectContainsPoint(self.fanChartFatherView.bounds, point)) {
// NSLog(@"???? 結(jié)束觸摸屏幕 手指所在 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); // 小半徑
// 判斷是否只有一個有數(shù)據(jù)(即,獨自占比100%),其他類型都是占比0%,只有一個類型有占比時,首、尾兩個位置,不在中心
NSInteger greaterThanZero = 0; // 遍歷所有類型,其中類型占比大于0的有幾個*(只有一個時,全部是同一個顏色,無間隔白色線;greaterThanZero >= 2,有白色間隔)
for (int i = 0; i < self.statistical_TypeArray.count; i++) {
CGFloat fanChart_subTotal = [self.statistical_TypeArray[i] floatValue];
if (fanChart_subTotal > 0) {
greaterThanZero = greaterThanZero + 1;
}
}
// 計算每個類型所占總數(shù)的百分比:
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]; // 新創(chuàng)建覆蓋在當前點擊的layer上,視覺效果有點不太好,不推薦這種方法
for (int i = 0; i < self.statistical_TypeArray.count; i++) {
// 計算當前end位置 = 上一個結(jié)束位置 + 當前部分百分比 =(當前部分結(jié)束為止的百分比+上一次結(jié)束的百分比)
small_end = [self.statistical_TypeArray[i] floatValue] / fanChart_total + small_start;
CAShapeLayer *subLayr = self.big_FanChart_FatherLayer.sublayers[i];
/** 選中某一個扇形圖,改變其半徑的長短,實現(xiàn)放大縮小的效果 */
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];
if (greaterThanZero >= 2) {
[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];
if (greaterThanZero >= 2) {
[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];
if (greaterThanZero >= 2) {
[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];
if (greaterThanZero >= 2) {
[small_FanChartPath addLineToPoint:centerPoint];
}
// 閉合路徑,即在終點和起點連一根線
[small_FanChartPath closePath];
subLayr.path = [small_FanChartPath CGPath];
}
/**
/// 新創(chuàng)建覆蓋在當前點擊的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類轉(zhuǎn)換成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];
}
}
}
/// 點擊的區(qū)域,在第幾個扇形圖的范圍,返回點擊的第一個扇形
- (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:如果觸摸的點包含在路徑內(nèi),則返回YES
if (CGPathContainsPoint(path, &transform, point, 0)) {
selectIndex = idx;
NSLog(@"????????點擊的是第幾個扇形圖 = %ld", selectIndex);
}
}];
return selectIndex;
}