
剛拿到設(shè)計(jì)稿的時候大概看了一眼,當(dāng)時心里想著放張背景圖,然后計(jì)算下相應(yīng)點(diǎn)的坐標(biāo),在最上面畫一層就OK了,其實(shí)一開始實(shí)現(xiàn)的時候也確實(shí)是這么做的,然后我就日了狗了,發(fā)現(xiàn)設(shè)計(jì)稿上多層五邊形的間隔不是相等的,也就是說繼續(xù)按照之前的想法進(jìn)行實(shí)現(xiàn)就要計(jì)算出每層頂點(diǎn)的坐標(biāo),那樣的話代碼估計(jì)會被坐標(biāo)值霸屏了。好吧,推倒重來。
一層一層的分析這個需求,首先是五邊形的繪制,我創(chuàng)建了一個UIBezierPath的category。具體的代碼如下,其中第一個方法是用來畫各頂點(diǎn)不規(guī)律的五邊形的,而第二個方法是用來畫那幾個背景五邊形,兩個方法中的length都指的的中心點(diǎn)到各頂點(diǎn)的距離,第三個方法則是用來將距離轉(zhuǎn)換成具體坐標(biāo)。
+ (CGPathRef)drawPentagonWithCenter:(CGPoint)center Length:(double)length
{
NSArray *lengths = [NSArray arrayWithObjects:@(length),@(length),@(length),@(length),@(length), nil];
return [self drawPentagonWithCenter:center LengthArray:lengths];
}
+ (CGPathRef)drawPentagonWithCenter:(CGPoint)center LengthArray:(NSArray *)lengths
{
NSArray *coordinates = [self converCoordinateFromLength:lengths Center:center];
UIBezierPath *bezierPath = [UIBezierPath bezierPath];
for (int i = 0; i < [coordinates count]; i++) {
CGPoint point = [[coordinates objectAtIndex:i] CGPointValue];
if (i == 0) {
[bezierPath moveToPoint:point];
} else {
[bezierPath addLineToPoint:point];
}
}
[bezierPath closePath];
return bezierPath.CGPath;
}
+ (NSArray *)converCoordinateFromLength:(NSArray *)lengthArray Center:(CGPoint)center
{
NSMutableArray *coordinateArray = [NSMutableArray array];
for (int i = 0; i < [lengthArray count] ; i++) {
double length = [[lengthArray objectAtIndex:i] doubleValue];
CGPoint point = CGPointZero;
if (i == 0) {
point = CGPointMake(center.x - length * sin(M_PI / 5.0),
center.y - length * cos(M_PI / 5.0));
} else if (i == 1) {
point = CGPointMake(center.x + length * sin(M_PI / 5.0),
center.y - length * cos(M_PI / 5.0));
} else if (i == 2) {
point = CGPointMake(center.x + length * cos(M_PI / 10.0),
center.y + length * sin(M_PI / 10.0));
} else if (i == 3) {
point = CGPointMake(center.x,
center.y +length);
} else {
point = CGPointMake(center.x - length * cos(M_PI / 10.0),
center.y + length * sin(M_PI / 10.0));
}
[coordinateArray addObject:[NSValue valueWithCGPoint:point]];
}
return coordinateArray;
}
至于最頂層數(shù)據(jù)五邊形的動畫繪制,我做了兩種實(shí)現(xiàn)(因?yàn)樗麄円策€沒確定用哪個),額,怎么解釋兩者的區(qū)別呢,一種是按照與各邊成比例的速度放大,一種是按照各邊同樣的速度放大。兩種方法我都放上來:
#pragma mark - 描繪分?jǐn)?shù)五邊行 按照與各邊成比例的速度放大
- (void)drawScorePentagonV
{
NSArray *lengthsArray = [self convertLengthsFromScore:self.scoresArray];
CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:@"path"];
pathAnimation.fromValue = (id)[UIBezierPath drawPentagonWithCenter:CGPointMake(kRScrollAlertViewWidth / 2.0, 100.0) Length:0];
pathAnimation.toValue = (id)[UIBezierPath drawPentagonWithCenter:CGPointMake(kRScrollAlertViewWidth / 2.0, 100.0) LengthArray:lengthsArray];
pathAnimation.duration = 0.75;
pathAnimation.autoreverses = NO;
pathAnimation.repeatCount = 0;
pathAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
[self.shapeLayer addAnimation:pathAnimation forKey:@"scale"];
self.shapeLayer.path = [UIBezierPath drawPentagonWithCenter:CGPointMake(kRScrollAlertViewWidth / 2.0, 100.0) LengthArray:lengthsArray];
[self.layer addSublayer:self.shapeLayer];
[self performSelector:@selector(changeBgSizeFinish) withObject:nil afterDelay:0.75];
}
#pragma mark - 描繪分?jǐn)?shù)五邊行 按照各邊同樣的速度放大
- (void)drawScorePentagonV
{
NSArray *scoresArray = [self analysisScoreArray:self.scoresArray];
NSMutableArray *lengthsArray = [NSMutableArray array];
[lengthsArray addObject:(id)[UIBezierPath drawPentagonWithCenter:CGPointMake(kRScrollAlertViewWidth / 2.0, 231 / 2.0) Length:0.0]];
for (int i = 0; i < [scoresArray count]; i++) {
NSArray *scores = [scoresArray objectAtIndex:i];
[lengthsArray addObject:(id)[UIBezierPath drawPentagonWithCenter:CGPointMake(kRScrollAlertViewWidth / 2.0, 231 / 2.0) LengthArray:[self convertLengthsFromScore:scores]]];
}
CAKeyframeAnimation *frameAnimation = [CAKeyframeAnimation animationWithKeyPath:@"path"];
frameAnimation.values = lengthsArray;
frameAnimation.keyTimes = [self analysisDurationArray:self.scoresArray];
frameAnimation.duration = 2;
frameAnimation.calculationMode = kCAAnimationLinear;
[self.shapeLayer addAnimation:frameAnimation forKey:@"scale"];
self.shapeLayer.path = [UIBezierPath drawPentagonWithCenter:CGPointMake(kRScrollAlertViewWidth / 2.0, 231 / 2.0) LengthArray:[self convertLengthsFromScore:[scoresArray lastObject]]];
[self.layer addSublayer:self.shapeLayer];
[self performSelector:@selector(changeBgSizeFinish) withObject:nil afterDelay:2];
}
接下來就是在動畫結(jié)束的時候,將頂點(diǎn)的幾個小圖標(biāo)加上去,沒錯,就是上面出現(xiàn)過的changeBgSizeFinish方法。
#pragma mark - 描點(diǎn)
- (void)changeBgSizeFinish
{
NSArray *array = [self convertLengthsFromScore:self.scoresArray];
NSArray *lengthsArray = [UIBezierPath converCoordinateFromLength:array Center:CGPointMake(kRScrollAlertViewWidth / 2.0, 100.0)];
for (int i = 0; i < [lengthsArray count]; i++) {
CGPoint point = [[lengthsArray objectAtIndex:i] CGPointValue];
RADotView *dotV = [[RADotView alloc] init];
dotV.dotColor = [UIColor colorWithHex:0xF86465];
dotV.center = point;
dotV.bounds = CGRectMake(0, 0, 8, 8);
[self addSubview:dotV];
}
}
到這里整個需求就實(shí)現(xiàn)了,至于幾個文字的Label,我沒想到好的辦法,都是通過量具體的坐標(biāo)放到指定的位置上面的。好吧,我知道大家都很忙也比較喜歡偷懶,把剩余的相關(guān)代碼也貼上來,大家也順便幫我看看代碼是否有錯誤的地方。
#pragma mark - 分?jǐn)?shù)轉(zhuǎn)換
- (NSNumber *)convertLengthFromScore:(double)score
{
if (score >= 4) {
return @(12 + 22 + 30 + 30);
} else if (score >= 3){
return @(12 + 22 + 30 + 30 * (score - 3));
} else if (score >= 2) {
return @(12 + 22 + 30 * (score - 2));
} else if (score >= 1) {
return @(12 + 22 * (score - 1));
} else {
return @(12 * score);
}
}
- (NSArray *)convertLengthsFromScore:(NSArray *)scoreArray
{
NSMutableArray *lengthArray = [NSMutableArray array];
for (int i = 0; i < [scoreArray count]; i++) {
double score = [[scoreArray objectAtIndex:i] doubleValue];
[lengthArray addObject:[self convertLengthFromScore:score]];
}
return lengthArray;
}
#pragma mark - 對分?jǐn)?shù)進(jìn)行排序(第二種動畫方法需要)
- (NSArray *)sortMergeScoresArray:(NSArray *)scores
{
NSMutableDictionary *dic = [NSMutableDictionary dictionary];
for (int i = 0; i < [scores count]; i++) {
[dic setObject:@"scoresValue" forKey:[scores objectAtIndex:i]];
}
NSMutableArray *sortArray = [NSMutableArray arrayWithArray:dic.allKeys];
for (int i = 0; i < [sortArray count] - 1; i++) {
for (int j = 0; j < [sortArray count] - i - 1 ; j++) {
if ([[sortArray objectAtIndex:j] doubleValue] > [[sortArray objectAtIndex:j + 1] doubleValue]) {
[sortArray exchangeObjectAtIndex:j withObjectAtIndex:j + 1];
}
}
}
return sortArray;
}
- (NSArray *)analysisDurationArray:(NSArray *)scores
{
NSMutableArray *analysisArray = [NSMutableArray array];
NSArray *sortArray = [self sortMergeScoresArray:scores];
double lastProportion = 0;
[analysisArray addObject:@(0)];
for (int i = 0; i < [sortArray count]; i++) {
double currentProportion = [[sortArray objectAtIndex:i] doubleValue] / [[sortArray lastObject] doubleValue];
[analysisArray addObject:@(currentProportion)];
lastProportion = currentProportion;
}
return analysisArray;
}
- (NSArray *)analysisScoreArray:(NSArray *)scores
{
NSArray *sortArray = [self sortMergeScoresArray:scores];
NSMutableArray *analysisArray = [NSMutableArray array];
for (int i = 0; i < [sortArray count]; i++) {
double stepScore = [[sortArray objectAtIndex:i] doubleValue];
NSMutableArray *analysisScores = [NSMutableArray array];
for (int j = 0; j < [scores count]; j++) {
double score = [[scores objectAtIndex:j] doubleValue];
if (stepScore > score) {
[analysisScores addObject:@(score)];
} else {
[analysisScores addObject:@(stepScore)];
}
}
[analysisArray addObject:analysisScores];
}
return analysisArray;
}
最后,總結(jié)一下這次的需求實(shí)現(xiàn)過程,敲代碼之前一定一定要很仔細(xì)很仔細(xì)的分析一下需求,一定一定一定要。
如果你覺得這篇文章對你有一定的幫助,請點(diǎn)擊一下下面的喜歡按鈕,當(dāng)然如果有問題也可以大家一起討論。
2016.6.5更新:
iOS RadarChart實(shí)現(xiàn)
大家要的demo我放到github上面了,感興趣的可以去看看,喜歡的可以star支持一下哈,不要再留郵箱了??。