前言
圖表的繪製相信大家都用的很多, 也有現成的很好的框架, 但如果定製程度特別高, 特別是動畫, 還是得自己來實現, 先看看準備實現的效果, 個人覺得還是有一些炫酷的.
另外本文不會科普最基本的概念與Api, 直接從實戰出發, 希望大家看完後都能寫出各種炫酷的效果
曲線圖
曲線圖在平時用的應該是最多的, 曲線圖會了, 折線圖就更容易了.
圖上的效果大致分3步(下面的動畫也一樣):
1.處理資料: 將得到的資料轉換為點座標資料, 這一步就不細說了
2.繪製圖形: 可以用Quartz2D或者UIKit中封裝好的UIBezierPath
3.設定動畫: 主要利用到CoreAnimation中的"strokeEnd"動畫
下面就看具體程式碼吧:
繪製圖形
/*
pointArray是所有點的陣列
color是主題色
compete繪製完成的回撥
*/
- (void)drawLayerWithPointArray:(NSMutableArray *)pointArray color:(UIColor *)color compete:(completeBlock)compete{
//初始化下面漸變色路徑
UIBezierPath *fillPath = [UIBezierPath new];
//初始化曲線的路徑
UIBezierPath *borderPath = [UIBezierPath new];
//這裡是我個人設定點數過多 忽略部分點, 讓曲線更平滑, 按需刪除
NSInteger ignoreSpace = pointArray.count / 15;
//記錄上一個點
__block CGPoint lastPoint;
//記錄上一個點的索引
__block NSUInteger lastIdx;
//漸變色路徑移動到左下角
[fillPath moveToPoint:CGPointMake(0, _chart.height)];
//遍歷所有點, 移動Path繪製圖形
[pointArray enumerateObjectsUsingBlock:^(NSValue *obj, NSUInteger idx, BOOL * _Nonnull stop) {
CGPoint point = obj.CGPointValue;
if (idx == 0) { //第一個點
[fillPath addLineToPoint:point];
[borderPath moveToPoint:point];
lastPoint = point;
lastIdx = idx;
} else if ((idx == pointArray.count - 1) || (point.y == 0) || (lastIdx + ignoreSpace + 1 == idx)) { //最後一個點最高點要畫/當點數過多時 忽略部分點
[fillPath addCurveToPoint:point controlPoint1:CGPointMake((lastPoint.x + point.x) / 2, lastPoint.y) controlPoint2:CGPointMake((lastPoint.x + point.x) / 2, point.y)]; //三次曲線
[borderPath addCurveToPoint:point controlPoint1:CGPointMake((lastPoint.x + point.x) / 2, lastPoint.y) controlPoint2:CGPointMake((lastPoint.x + point.x) / 2, point.y)];
lastPoint = point;
lastIdx = idx;
}
}];
//將漸變色區域封閉
[fillPath addLineToPoint:CGPointMake(_chart.width, _chart.height)];
[fillPath addLineToPoint:CGPointMake(0, _chart.height)];
//初始化Path的載體分別顯示路徑及填充漸變色
CAShapeLayer *shapeLayer = [CAShapeLayer layer];
shapeLayer.path = fillPath.CGPath;
[_chart.layer addSublayer:shapeLayer];
CAShapeLayer *borderShapeLayer = [CAShapeLayer layer];
borderShapeLayer.path = borderPath.CGPath;
borderShapeLayer.lineWidth = 2.f;
borderShapeLayer.strokeColor = color.CGColor;
borderShapeLayer.fillColor = [UIColor clearColor].CGColor;
[_chart.layer addSublayer:borderShapeLayer];
//設定漸變色
CAGradientLayer *gradientLayer = [CAGradientLayer layer];
gradientLayer.frame = _chart.bounds;
[gradientLayer setColors:[NSArray arrayWithObjects:(id)[[color colorWithAlphaComponent:0.5] CGColor], (id)[[UIColor clearColor] CGColor], nil]];
[gradientLayer setStartPoint:CGPointMake(0.5, 0)];
[gradientLayer setEndPoint:CGPointMake(0.5, 1)];
[gradientLayer setMask:shapeLayer];
[_chart.layer addSublayer:gradientLayer];
compete(borderShapeLayer, shapeLayer, gradientLayer);
}複製程式碼
以上 一個曲線圖就畫完了, 下面看看怎麼樣讓它動起來
設定動畫
- (void)animation{
//動畫之前讓曲線不隱藏
_bulletBorderLayer.hidden = NO;
//路徑動畫的KeyPath為@"strokeEnd"
//根據需要的效果, 從0-1意味著畫完整個曲線
CABasicAnimation *animation1 = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
animation1.fromValue = @(0);
animation1.toValue = @(1);
animation1.duration = 0.8;
[_bulletBorderLayer addAnimation:animation1 forKey:nil];
//動畫需要0.8秒完成, 延遲0.8秒讓漸變色動畫, 當然也可以用代理
[self performSelector:@selector(bulletLayerAnimation) withObject:nil afterDelay:0.8];
}
- (void)bulletLayerAnimation{
//動畫之前讓漸變色不隱藏
_bulletLayer.hidden = NO;
//漸變色看起來像是從上往下長出來, 實際只是透明度的變化
CABasicAnimation *animation2 = [CABasicAnimation animationWithKeyPath:@"opacity"];
animation2.fromValue = @(0);
animation2.toValue = @(1);
animation2.duration = 0.4;
[_bulletLayer addAnimation:animation2 forKey:nil];
}複製程式碼
整個曲線圖效果就完成了.
柱狀圖
柱狀圖其實更容易, 只是繪製這種柱狀圖稍微麻煩一點點而已,
這裡我沒有用strokeEnd, 而是直接垂直方向高度變化, 需要注意的是圖表的Y方向跟螢幕座標系的Y方向是相仿的, 所以這裡是位置動畫加上垂直方向縮放動畫的組動畫, 也就是AnimationGroup
繪製圖形
/*
wordsArrayRandom是亂序過後的詞語陣列, 記錄了每個詞語的頻次
*/
CGFloat maxHeight = _chart.height; //確定最大高度
CGFloat width = 2; //確定豎線寬度
CGFloat margin = _chart.width / 9;
NSInteger maxCount = wordsModel.count.integerValue;
[wordsArrayRandom enumerateObjectsUsingBlock:^(BAWordsModel *wordsModel, NSUInteger idx, BOOL * _Nonnull stop) {
//繪製
CGPoint orginPoint = CGPointMake(margin * idx, maxHeight); //圓點, 在矩形下邊中間
CGFloat height = maxHeight * wordsModel.count.integerValue / maxCount; //高度
//其實就是一個矩形加上一個圓形
UIBezierPath *path = [UIBezierPath new];
[path moveToPoint:orginPoint];
[path addLineToPoint:CGPointMake(path.currentPoint.x - width / 2, path.currentPoint.y)];
[path addLineToPoint:CGPointMake(path.currentPoint.x, path.currentPoint.y - height)];
[path addLineToPoint:CGPointMake(path.currentPoint.x + width, path.currentPoint.y)];
[path addLineToPoint:CGPointMake(path.currentPoint.x, orginPoint.y)];
[path addLineToPoint:orginPoint];
[path addArcWithCenter:CGPointMake(orginPoint.x, maxHeight - height) radius:width * 2 startAngle:0 endAngle:M_PI * 2 clockwise:YES];
CAShapeLayer *shapeLayer = [CAShapeLayer layer];
shapeLayer.path = path.CGPath;
shapeLayer.hidden = YES;
shapeLayer.fillColor = [BAWhiteColor colorWithAlphaComponent:0.8].CGColor;
[_chart.layer addSublayer:shapeLayer];
[_barLayerArray addObject:shapeLayer];
}];複製程式碼
繪製的程式碼我摘出了比較重要的部分, 全部的大家可以去下載Demo檢視
設定動畫
//每間隔0.1秒, 動畫一個柱狀圖
- (void)animation{
for (NSInteger i = 0; i < 10; i++) {
CAShapeLayer *layer = _barLayerArray[9 - i];
[self performSelector:@selector(animateLayer:) withObject:layer afterDelay:i * 0.1];
}
}
- (void)animateLayer:(CAShapeLayer *)layer{
layer.hidden = NO;
//垂直方向的縮放
CABasicAnimation *animation1 = [CABasicAnimation animationWithKeyPath:@"transform.scale.y"];
animation1.fromValue = @(0.0);
animation1.toValue = @(1.0);
//同時垂直方向座標原點在變化
CABasicAnimation *animation2 = [CABasicAnimation animationWithKeyPath:@"transform.translation.y"];
animation2.fromValue = @(_chart.height);
animation2.toValue = @(0.0);
CAAnimationGroup *animationGroup = [CAAnimationGroup animation];
animationGroup.duration = 0.3;
animationGroup.animations = @[animation1, animation2];
[layer addAnimation:animationGroup forKey:nil];
}複製程式碼
柱狀圖也完成了, 上面的都還好, 最後看看餅狀圖的繪製吧
餅狀圖
我們看到餅狀圖不光是需要繪製/動畫/還需要一個互動.
我們都知道CAShapeLayer是也是Layer, 本身是並不響應使用者點選的, 所以這裡需要手動處理, 還是一步步來說.
繪製圖形
本身繪製餅狀圖不復雜, 但是要繪製連線線和小圖示就麻煩了一點, 另外因為要整體移動餅狀圖, 所以每一個餅狀圖加上附帶的圖示得放到一個容器Layer裡面
/*
思路: 外面的大圓跟裡面小圓 其實是兩條比較粗的曲線, 計算好尺寸之後拼接起來,
外面的小圖示(素材)與大圓之間需要計算角度與長度才能畫線連線起來
*/
- (void)drawPieChart{
//設定大圓半徑, 小圓半徑, 中心點
_pieRadius = self.height / 2 - 8 * BAPadding - 7;
_inPieRadius = _pieRadius - 3 * BAPadding + 3.5;
_pieCenter = CGPointMake(self.width / 2, self.height / 2 + 40);
//外面餅狀陣列
NSMutableArray *pieArray = [NSMutableArray array];
//裡面餅狀陣列
NSMutableArray *inPieArray = [NSMutableArray array];
//這個陣列用來存放動畫時間, 每一段的動畫時間應該跟它所佔比成比例
NSMutableArray *durationArray = [NSMutableArray array];
//容器陣列, 動畫時方便整體移動
NSMutableArray *arcArray = [NSMutableArray array];
//起始(終止)角度
__block CGFloat endAngle = - M_PI / 2;
//_giftValueArray幾面已經是處理好的資料, 包括了每一塊的價值
[_giftValueArray enumerateObjectsUsingBlock:^(BAGiftValueModel *giftValueModel, NSUInteger idx, BOOL * _Nonnull stop) {
//建立一個容器 放外部餅狀圖與內部餅狀圖, 為動畫做準備
CALayer *arcLayer = [CALayer layer];
arcLayer.frame = self.bounds;
[arcArray addObject:arcLayer];
[self.layer addSublayer:arcLayer];
//計算每個禮物的起始 終止角度
CGFloat startAngle = endAngle;
//caculateWithStartAngle是根據起始角度與最大價值算終止角度
//_maxValue為之前計算好的總價值
[giftValueModel caculateWithStartAngle:startAngle maxValue:_maxValue];
endAngle = giftValueModel.endAngle;
//1.2是總共的動畫時間, 計算這一塊動畫所需要的時間
CGFloat duration = 1.2 * giftValueModel.totalGiftValue / _maxValue;
[durationArray addObject:@(duration)];
//當前餅狀圖的顏色
UIColor *pieColor = [BAWhiteColor colorWithAlphaComponent:giftValueModel.alpha];
UIColor *inPieColor = [BAWhiteColor colorWithAlphaComponent:giftValueModel.alpha - 0.3];
//畫圖
//外部餅狀圖路徑
UIBezierPath *piePath = [UIBezierPath bezierPath]; //內部圓環路徑
UIBezierPath *inPiePath = [UIBezierPath bezierPath];
[piePath addArcWithCenter:_pieCenter radius:_pieRadius startAngle:startAngle endAngle:endAngle clockwise:YES];
[inPiePath addArcWithCenter:_pieCenter radius:_inPieRadius startAngle:startAngle endAngle:endAngle clockwise:YES];
CAShapeLayer *pieLayer = [CAShapeLayer layer];
pieLayer.path = piePath.CGPath;
pieLayer.lineWidth = 4 * BAPadding;
pieLayer.strokeColor = pieColor.CGColor;
pieLayer.fillColor = [UIColor clearColor].CGColor;
pieLayer.hidden = YES;
CAShapeLayer *inPieLayer = [CAShapeLayer layer];
inPieLayer.path = inPiePath.CGPath;
inPieLayer.lineWidth = 14;
inPieLayer.strokeColor = inPieColor.CGColor;
inPieLayer.fillColor = [UIColor clearColor].CGColor;
inPieLayer.hidden = YES;
[arcLayer addSublayer:pieLayer];
[arcLayer addSublayer:inPieLayer];
[pieArray addObject:pieLayer];
[inPieArray addObject:inPieLayer];
//顯示各種bedge 並繪製連線線
[self drawBedgeWithGiftValueModel:giftValueModel container:arcLayer];
}];
_pieArray = pieArray;
_inPieArray = inPieArray;
_durationArray = durationArray;
_arcArray = arcArray;
}
- (void)drawBedgeWithGiftValueModel:(BAGiftValueModel *)giftValueModel container:(CALayer *)container{
//根據不同的禮物型別顯示不同的圖片
CALayer *iconLayer;
switch (giftValueModel.giftType) {
case BAGiftTypeCostGift:
iconLayer = _costIcon;
break;
case BAGiftTypeDeserveLevel1:
iconLayer = _deserve1Icon;
break;
case BAGiftTypeDeserveLevel2:
iconLayer = _deserve2Icon;
break;
case BAGiftTypeDeserveLevel3:
iconLayer = _deserve3Icon;
break;
case BAGiftTypeCard:
iconLayer = _cardIcon;
break;
case BAGiftTypePlane:
iconLayer = _planeIcon;
break;
case BAGiftTypeRocket:
iconLayer = _rocketIcon;
break;
default:
break;
}
[_bedgeArray addObject:iconLayer];
CGFloat iconDistance = container.frame.size.height / 2 - 40; //圖示到中心點的距離
CGFloat iconCenterX;
CGFloat iconCenterY;
CGFloat borderDistance = _pieRadius + 2 * BAPadding;
CGFloat lineBeginX;
CGFloat lineBeginY;
CGFloat iconBorderDistance = iconDistance - 12.5;
CGFloat lineEndX;
CGFloat lineEndY;
CGFloat moveDistance = BAPadding; //動畫移動的距離
CGFloat moveX;
CGFloat moveY;
/*
這裡計算各種引數
directAngle為之前計算起始終止角度時儲存下來的餅狀圖朝向
這個朝向需要在四個象限, 轉換為銳角, 然後通過三角函式就可以算出連線線的起點終點, 圖示的位置
*/
CGFloat realDirectAngle; //銳角
if (giftValueModel.directAngle > - M_PI / 2 && giftValueModel.directAngle < 0) { //-90° - 0°
realDirectAngle = giftValueModel.directAngle - (- M_PI / 2);
iconCenterX = _pieCenter.x + iconDistance * sin(realDirectAngle);
iconCenterY = _pieCenter.y - iconDistance * cos(realDirectAngle);
lineBeginX = _pieCenter.x + borderDistance * sin(realDirectAngle);
lineBeginY = _pieCenter.y - borderDistance * cos(realDirectAngle);
lineEndX = _pieCenter.x + iconBorderDistance * sin(realDirectAngle);
lineEndY = _pieCenter.y - iconBorderDistance * cos(realDirectAngle);
moveX = moveDistance * sin(realDirectAngle);
moveY = - moveDistance * cos(realDirectAngle);
} else if (giftValueModel.directAngle > 0 && giftValueModel.directAngle < M_PI / 2) { // 0° - 90°
realDirectAngle = giftValueModel.directAngle;
iconCenterX = _pieCenter.x + iconDistance * cos(realDirectAngle);
iconCenterY = _pieCenter.y + iconDistance * sin(realDirectAngle);
lineBeginX = _pieCenter.x + borderDistance * cos(realDirectAngle);
lineBeginY = _pieCenter.y + borderDistance * sin(realDirectAngle);
lineEndX = _pieCenter.x + iconBorderDistance * cos(realDirectAngle);
lineEndY = _pieCenter.y + iconBorderDistance * sin(realDirectAngle);
moveX = moveDistance * cos(realDirectAngle);
moveY = moveDistance * sin(realDirectAngle);
} else if (giftValueModel.directAngle > M_PI / 2 && giftValueModel.directAngle < M_PI) { // 90° - 180°
realDirectAngle = giftValueModel.directAngle - M_PI / 2;
iconCenterX = _pieCenter.x - iconDistance * sin(realDirectAngle);
iconCenterY = _pieCenter.y + iconDistance * cos(realDirectAngle);
lineBeginX = _pieCenter.x - borderDistance * sin(realDirectAngle);
lineBeginY = _pieCenter.y + borderDistance * cos(realDirectAngle);
lineEndX = _pieCenter.x - iconBorderDistance * sin(realDirectAngle);
lineEndY = _pieCenter.y + iconBorderDistance * cos(realDirectAngle);
moveX = - moveDistance * sin(realDirectAngle);
moveY = moveDistance * cos(realDirectAngle);
} else { //180° - -90°
realDirectAngle = giftValueModel.directAngle - M_PI;
iconCenterX = _pieCenter.x - iconDistance * cos(realDirectAngle);
iconCenterY = _pieCenter.y - iconDistance * sin(realDirectAngle);
lineBeginX = _pieCenter.x - borderDistance * cos(realDirectAngle);
lineBeginY = _pieCenter.y - borderDistance * sin(realDirectAngle);
lineEndX = _pieCenter.x - iconBorderDistance * cos(realDirectAngle);
lineEndY = _pieCenter.y - iconBorderDistance * sin(realDirectAngle);
moveX = - moveDistance * cos(realDirectAngle);
moveY = - moveDistance * sin(realDirectAngle);
}
//畫線
UIBezierPath *linePath = [UIBezierPath bezierPath];
[linePath moveToPoint:CGPointMake(lineBeginX, lineBeginY)];
[linePath addLineToPoint:CGPointMake(lineEndX, lineEndY)];
CAShapeLayer *lineLayer = [CAShapeLayer layer];
lineLayer.path = linePath.CGPath;
lineLayer.lineWidth = 1;
lineLayer.strokeColor = [BAWhiteColor colorWithAlphaComponent:0.6].CGColor;
lineLayer.fillColor = [UIColor clearColor].CGColor;
lineLayer.hidden = YES;
[_lineArray addObject:lineLayer];
[container addSublayer:lineLayer];
//儲存移動的動畫
giftValueModel.translation = CATransform3DMakeTranslation(moveX, moveY, 0);
iconLayer.frame = CGRectMake(iconCenterX - 13.75, iconCenterY - 13.75, 27.5, 27.5);
[container addSublayer:iconLayer];
}
/**
* 計算角度 與Y軸夾角 -90 - 270
*/
- (CGFloat)angleForStartPoint:(CGPoint)startPoint EndPoint:(CGPoint)endPoint{
CGPoint Xpoint = CGPointMake(startPoint.x + 100, startPoint.y);
CGFloat a = endPoint.x - startPoint.x;
CGFloat b = endPoint.y - startPoint.y;
CGFloat c = Xpoint.x - startPoint.x;
CGFloat d = Xpoint.y - startPoint.y;
CGFloat rads = acos(((a*c) + (b*d)) / ((sqrt(a*a + b*b)) * (sqrt(c*c + d*d))));
if (startPoint.y > endPoint.y) {
rads = -rads;
}
if (rads < - M_PI / 2 && rads > - M_PI) {
rads += M_PI * 2;
}
return rads;
}
//兩點之間距離
- (CGFloat)distanceForPointA:(CGPoint)pointA pointB:(CGPoint)pointB{
CGFloat deltaX = pointB.x - pointA.x;
CGFloat deltaY = pointB.y - pointA.y;
return sqrt(deltaX * deltaX + deltaY * deltaY );
}複製程式碼
上面畫整體的過程有點小複雜, 因為涉及了各種角度轉換 計算, 以及為之後動畫 互動做準備, 做好了前面的準備, 再進行動畫跟互動處理就容易不少.
設定動畫
動畫的過程其實是餅狀圖按順序一個個執行前面畫曲線所用的strokeEnd動畫, 然後我們小圖示以及我們畫的連線線透明度動畫展現.
- (void)animation{
NSInteger i = 0;
CGFloat delay = 0;
//遍歷所有的餅狀圖, 按順序執行動畫
for (CAShapeLayer *pieLayer in _pieArray) {
CAShapeLayer *inPieLayer = _inPieArray[i];
CGFloat duration = [_durationArray[i] floatValue];
[self performSelector:@selector(animationWithAttribute:) withObject:@{@"layer" : pieLayer, @"duration" : @(duration)} afterDelay:delay inModes:@[NSRunLoopCommonModes]];
[self performSelector:@selector(animationWithAttribute:) withObject:@{@"layer" : inPieLayer, @"duration" : @(duration)} afterDelay:delay inModes:@[NSRunLoopCommonModes]];
delay += duration;
i++;
}
[self performSelector:@selector(animationWithBedge) withObject:nil afterDelay:delay];
}
//根據傳入的時間以及餅狀圖路徑動畫
- (void)animationWithAttribute:(NSDictionary *)attribute{
CAShapeLayer *layer = attribute[@"layer"];
CGFloat duration = [attribute[@"duration"] floatValue];
layer.hidden = NO;
CABasicAnimation *animation1 = [CABasicAnimation animationWithKeyPath:@"strokeEnd"];
animation1.fromValue = @(0);
animation1.toValue = @(1);
animation1.duration = duration;
[layer addAnimation:animation1 forKey:nil];
}
//透明度漸變展示各種小圖示
- (void)animationWithBedge{
NSInteger i = 0;
for (CAShapeLayer *lineLayer in _lineArray) {
CALayer *bedgeLayer = _bedgeArray[i];
lineLayer.hidden = NO;
bedgeLayer.hidden = NO;
CABasicAnimation *animation1 = [CABasicAnimation animationWithKeyPath:@"opacity"];
animation1.fromValue = @(0);
animation1.toValue = @(1);
animation1.duration = 0.4;
[lineLayer addAnimation:animation1 forKey:nil];
[bedgeLayer addAnimation:animation1 forKey:nil];
i++;
}
}複製程式碼
處理互動
互動的思路其實很清晰, 判斷一個餅狀圖被點選了有2個條件:
1.點選的點與圓心之間的連線與-90°(之前設定的基準)之間的夾角是否在之前計算的餅狀圖起始終止角度之間.
2.點選的點與圓心的距離是否大於內圓的半徑(最內), 小於外圓的半徑(最外).
我們發現其實這些之前已經計算好了, 所以直接計算這個點的引數
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
CGPoint touchPoint = [[touches anyObject] locationInView:self];
[self dealWithTouch:touchPoint];
}
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
CGPoint touchPoint = [[touches anyObject] locationInView:self];
[self dealWithTouch:touchPoint];
}
- (void)dealWithTouch:(CGPoint)touchPoint{
CGFloat touchAngle = [self angleForStartPoint:_pieCenter EndPoint:touchPoint];
CGFloat touchDistance = [self distanceForPointA:touchPoint pointB:_pieCenter];
//判斷是否點選了魚丸
if (touchDistance < _inPieRadius - BAPadding) {
if (self.isFishBallClicked) {
_giftPieClicked(BAGiftTypeNone);
} else {
_giftPieClicked(BAGiftTypeFishBall);
}
[self animationFishBall];
return;
}
//求點選位置與-90°的夾角 與 之前的圓弧對比
if (touchDistance > _inPieRadius - BAPadding && touchDistance < _pieRadius + 2 * BAPadding) {
[_giftValueArray enumerateObjectsUsingBlock:^(BAGiftValueModel *giftValueModel, NSUInteger idx, BOOL * _Nonnull stop) {
if (giftValueModel.startAngle < touchAngle && giftValueModel.endAngle > touchAngle) {
//isMovingOut用來標記是否已經移動出去了
if (giftValueModel.isMovingOut) {
_giftPieClicked(BAGiftTypeNone);
} else {
_giftPieClicked(giftValueModel.giftType);
}
[self animationMove:_arcArray[idx] giftValueModel:giftValueModel];
*stop = YES;
}
}];
}
}
//將傳入的餅狀圖移動, 並且遍歷所有餅狀圖, 聯動收回之前的餅狀圖
- (void)animationMove:(CALayer *)arcLayer giftValueModel:(BAGiftValueModel *)giftValueModel{
if (giftValueModel.isMovingOut) {
arcLayer.transform = CATransform3DIdentity;
giftValueModel.movingOut = NO;
} else {
arcLayer.transform = giftValueModel.translation;
giftValueModel.movingOut = YES;
[_arcArray enumerateObjectsUsingBlock:^(CALayer *arc, NSUInteger idx, BOOL * _Nonnull stop) {
BAGiftValueModel *giftValue = _giftValueArray[idx];
if (![arcLayer isEqual:arc] && giftValue.isMovingOut) {
[self animationMove:arc giftValueModel:giftValue];
}
}];
if (self.isFishBallClicked) {
[self animationFishBall];
}
}
}複製程式碼
結語
至此, 所有炫酷的動態可互動圖表就已經完成了, 其實這個App裡面細節動畫處理還挺多的, 例如滑動時背景漸變色的角度改變, 漸變色的動畫, 包括一個有點酷的引導頁, 啟動頁.
專案已上線: 叫直播伴侶, 可以下載下來玩玩,
另外程式碼也是開源的:
github.com/syik/Bullet… 覺得有意思的可以打賞一個Star~
專案中有一個有意思的功能, 中文語義近似的分析可以看看我的上一篇文章.
發現大家對動畫更感興趣, 下一篇講講動態的啟動頁與炫酷的引導頁動畫.