畫一張餅圖,并且?guī)?strong>動(dòng)畫效果和點(diǎn)擊效果
如下圖所示:
RPReplay_Final1663921137.GIF
如圖,
白色分割線是
layer的lineWidth;動(dòng)畫效果:是加了一個(gè)遮罩
.mask,在下面就會(huì)有實(shí)現(xiàn)代碼;點(diǎn)擊效果:是點(diǎn)擊的是哪一個(gè)扇形圖,就將它的
半徑加長(zhǎng)。
相關(guān)鏈接:1、iOS 餅狀圖(扇形圖)動(dòng)畫效果的實(shí)現(xiàn),還可以在扇形圖上添加UILabel文字
2、iOS 帶指示線說(shuō)明的餅狀圖
3、Touch:判斷當(dāng)前點(diǎn)擊的位置是否在某個(gè)視圖上
4、CALayer之mask屬性-遮罩
下面是實(shí)現(xiàn)代碼:
@interface GW_CareerHomeHeaderView ()
/** 扇形圖:大小同一個(gè)中心點(diǎn) */
@property (nonatomic, strong) UIView *fanChartFatherView; // 扇形圖的父view
@property (nonatomic, strong) CAShapeLayer *big_FanChart_FatherLayer; // 大扇形圖 父layer:點(diǎn)擊小扇形圖時(shí),放大
@property (nonatomic, strong) CAShapeLayer *small_FanChart_FatherLayer; // (遮罩層)
/// 點(diǎn)擊扇形圖,在其位置上添加一個(gè)(相同角度和顏色)半徑放大的扇形圖(每次點(diǎn)擊刪掉重新創(chuàng)建)
//@property (nonatomic, strong) CAShapeLayer *selectedIndex_Layer; // 新創(chuàng)建覆蓋在當(dāng)前點(diǎn)擊的layer上,視覺(jué)效果有點(diǎn)不太好,不推薦這種方法
/// 扇形圖,之前形態(tài)是否是“放大版”(大:選中狀態(tài), ?。悍沁x中狀態(tài)):YES 當(dāng)前是“放大版”,NO 當(dāng)前是“縮小版”
@property (nonatomic, assign) BOOL previous_is_selected_FanChart_Big;
@property (nonatomic, assign) NSInteger previous_SelextedIndex_fanChart; // 上一次選中的是第幾個(gè)
@property (nonatomic, strong) NSArray *statistical_TypeArray;
@end
1、首先,根據(jù)數(shù)據(jù),創(chuàng)建扇形圖(餅圖)、遮罩層、創(chuàng)建完成再添加動(dòng)畫效果:
#pragma mark -- 類型分布 扇形圖 --
/// 當(dāng)統(tǒng)計(jì)沒(méi)有數(shù)據(jù)時(shí),改變高度,并顯示缺省圖
- (void)refreshChangeStatistical {
// 數(shù)據(jù)所占百分比:5%,10%,15%,20%,50%
self.statistical_TypeArray = @[
@"5",@"15", @"10",@"20", @"50"
];
// 餅圖(點(diǎn)擊某一個(gè)扇形圖,放大效果):
// 首先,在創(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,每次用時(shí)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];
/** 準(zhǔn)備畫扇形圖: */
// 扇形中心點(diǎn)
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的半徑長(zhǎng)度,(在這里的是大半徑big_radius的原因是:之后,點(diǎn)擊某一個(gè)扇形圖之后,該扇形圖的半徑會(huì)變大顯示,但變大之后的半徑不會(huì)超過(guò)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;
/**
* 因?yàn)樵诋嬌刃螆D的時(shí)候,使用到了“線寬lineWidth”,造成當(dāng)只有一個(gè)扇形圖(即,一整個(gè)餅圖)時(shí),
* 因?yàn)檫@行代碼的使用:“[small_FanChartPath addLineToPoint:centerPoint]; ”,在首尾鏈接會(huì)造成分割線的情況,
* 所以,當(dāng)在只有一個(gè)圓形“餅圖”的時(shí)候,將“[small_FanChartPath addLineToPoint:centerPoint]; ”去掉就可以了。
*/
// 判斷是否只有一個(gè)有數(shù)據(jù)(即,獨(dú)自占比100%),其他類型都是占比0%,只有一個(gè)類型有占比時(shí),首、尾兩個(gè)位置,不在中心
NSInteger greaterThanZero = 0; // 遍歷所有類型,其中類型占比大于0的有幾個(gè)*(只有一個(gè)時(shí),全部是同一個(gè)顏色,無(wú)間隔白色線;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;
}
}
// 計(jì)算每個(gè)類型所占總數(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++) {
// 計(jì)算當(dāng)前end位置 = 上一個(gè)結(jié)束位置 + 當(dāng)前部分百分比 =(當(dāng)前部分結(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;
// 背景填充色(每個(gè)數(shù)據(jù)一個(gè)顏色)
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軸上的位置 支持隱式動(dòng)畫
subLayer.zPosition = 2;
subLayer.lineJoin = kCALineJoinBevel; // 尖角 kCALineJoinMiter, 圓角 kCALineJoinRound, 缺角 kCALineJoinBevel
//subLayer.lineCap = kCALineCapRound; // 無(wú)端點(diǎn) kCALineCapButt,圓形端點(diǎn) kCALineCapRound,方形端點(diǎn) kCALineCapSquare
// 注意??:下邊的貝塞爾曲線,已經(jīng)設(shè)置好start和end了,這里不在需要設(shè)置(如果設(shè)置下邊strokeStart和strokeEnd的話,lineWidth顯示出問(wèn)題)
//subLayer.strokeStart = small_start;
//subLayer.strokeEnd = small_end;
// 初始化一個(gè)路徑:創(chuàng)建圓弧 ,startAngle:起始點(diǎn),endAngle:終止點(diǎn),clockwise:順時(shí)針?lè)较?,M_PI == π:3.1415926
// clockwise 順時(shí)針 YES, 逆時(shí)針 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];
// // 每個(gè)扇形 同角度方向,偏移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]; // 如果只有一個(gè)扇形圖(即,餅圖),就不寫這個(gè),讓首尾鏈接呈圓形
}
// 閉合路徑,即在終點(diǎn)和起點(diǎn)連一根線
[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];
// 計(jì)算下一個(gè)start位置 = 當(dāng)前end位置
small_start = small_end;
}
[self stroke];
}
- (void)stroke {
//畫圖動(dòng)畫
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、餅圖中扇形點(diǎn)擊事件
CAShapeLayer *subLayr = self.big_FanChart_FatherLayer.sublayers[i];
選中某一個(gè)扇形圖,改變其半徑的長(zhǎng)短,實(shí)現(xiàn)放大縮小的效果
#pragma mark -- 餅圖中扇形點(diǎn)擊事件方法 --
- (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,或空,并且該點(diǎn)位于矩形內(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) { //點(diǎn)擊第幾個(gè)扇形圖,那個(gè)扇形圖就改變其半徑長(zhǎng)度
// 扇形中心點(diǎn)
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); // 小半徑
// 判斷是否只有一個(gè)有數(shù)據(jù)(即,獨(dú)自占比100%),其他類型都是占比0%,只有一個(gè)類型有占比時(shí),首、尾兩個(gè)位置,不在中心
NSInteger greaterThanZero = 0; // 遍歷所有類型,其中類型占比大于0的有幾個(gè)*(只有一個(gè)時(shí),全部是同一個(gè)顏色,無(wú)間隔白色線;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;
}
}
// 計(jì)算每個(gè)類型所占總數(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)建覆蓋在當(dāng)前點(diǎn)擊的layer上,視覺(jué)效果有點(diǎn)不太好,不推薦這種方法
for (int i = 0; i < self.statistical_TypeArray.count; i++) {
// 計(jì)算當(dāng)前end位置 = 上一個(gè)結(jié)束位置 + 當(dāng)前部分百分比 =(當(dāng)前部分結(jié)束為止的百分比+上一次結(jié)束的百分比)
small_end = [self.statistical_TypeArray[i] floatValue] / fanChart_total + small_start;
CAShapeLayer *subLayr = self.big_FanChart_FatherLayer.sublayers[i];
/** 選中某一個(gè)扇形圖,改變其半徑的長(zhǎng)短,實(shí)現(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];
}
// 閉合路徑,即在終點(diǎn)和起點(diǎn)連一根線
[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];
}
// 閉合路徑,即在終點(diǎn)和起點(diǎn)連一根線
[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];
}
// 閉合路徑,即在終點(diǎn)和起點(diǎn)連一根線
[small_FanChartPath closePath];
subLayr.path = [small_FanChartPath CGPath];
self.previous_is_selected_FanChart_Big = YES;
}
// 更新選中的是第幾個(gè)
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];
}
// 閉合路徑,即在終點(diǎn)和起點(diǎn)連一根線
[small_FanChartPath closePath];
subLayr.path = [small_FanChartPath CGPath];
}
/**
/// 新創(chuàng)建覆蓋在當(dāng)前點(diǎn)擊的layer上,視覺(jué)效果有點(diǎn)不太好,不推薦這種方法
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軸上的位置 支持隱式動(dòng)畫
_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];
// 閉合路徑,即在終點(diǎn)和起點(diǎn)連一根線
[small_FanChartPath closePath];
// 將UIBezierPath類轉(zhuǎn)換成CGPath,類似于UIColor的CGColor
_selectedIndex_Layer.path = [small_FanChartPath CGPath];
[self.big_FanChart_FatherLayer addSublayer:self.selectedIndex_Layer];
}
*/
// 計(jì)算下一個(gè)start位置 = 當(dāng)前end位置
small_start = small_end;
}
[self.big_FanChart_FatherLayer display];
}
}
}
/// 點(diǎn)擊的區(qū)域,在第幾個(gè)扇形圖的范圍,返回點(diǎn)擊的第一個(gè)扇形
- (NSInteger)getCurrentSelectedOneTouch:(CGPoint)point {
__block NSInteger selectIndex = -1;
// 坐標(biāo)重置
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:如果觸摸的點(diǎn)包含在路徑內(nèi),則返回YES
if (CGPathContainsPoint(path, &transform, point, 0)) {
selectIndex = idx;
NSLog(@"????????點(diǎn)擊的是第幾個(gè)扇形圖 = %ld", selectIndex);
}
}];
return selectIndex;
}