本文為投稿文章,作者:Two_Seven
實現效果
剛拿到設計稿的時候大概看了一眼,當時心裡想著放張背景圖,然後計算下相應點的坐標,在最上面畫一層就OK了,其實一開始實現的時候也確實是這麼做的,然後我就日了狗了,發現設計稿上多層五邊形的間隔不是相等的,也就是說繼續按照之前的想法進行實現就要計算出每層頂點的坐標,那樣的話代碼估計會被坐標值霸屏了。好吧,推倒重來。
一層一層的分析這個需求,首先是五邊形的繪制,我創建了一個UIBezierPath的category。具體的代碼如下,其中第一個方法是用來畫各頂點不規律的五邊形的,而第二個方法是用來畫那幾個背景五邊形,兩個方法中的length都指的的中心點到各頂點的距離,第三個方法則是用來將距離轉換成具體坐標。
+ (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; }
至於最頂層數據五邊形的動畫繪制,我做了兩種實現(因為他們也還沒確定用哪個),額,怎麼解釋兩者的區別呢,一種是按照與各邊成比例的速度放大,一種是按照各邊同樣的速度放大。兩種方法我都放上來:
#pragma mark - 描繪分數五邊行 按照與各邊成比例的速度放大 - (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 - 描繪分數五邊行 按照各邊同樣的速度放大 - (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]; }
接下來就是在動畫結束的時候,將頂點的幾個小圖標加上去,沒錯,就是上面出現過的changeBgSizeFinish方法。
#pragma mark - 描點 - (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]; } }
到這裡整個需求就實現了,至於幾個文字的Label,我沒想到好的辦法,都是通過量具體的坐標放到指定的位置上面的。好吧,我知道大家都很忙也比較喜歡偷懶,把剩余的相關代碼也貼上來,大家也順便幫我看看代碼是否有錯誤的地方。
#pragma mark - 分數轉換 - (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 - 對分數進行排序(第二種動畫方法需要) - (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; }
最後,總結一下這次的需求實現過程,敲代碼之前一定一定要很仔細很仔細的分析一下需求,一定一定一定要。