前言
图表的绘制相信大家都用的很多, 也有现成的很好的框架, 但如果定制程度特别高, 特别是动画, 还是得自己来实现, 先看看准备实现的效果, 个人觉得还是有一些炫酷的.
另外本文不会科普最基本的概念与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里面细节动画处理还挺多的, 例如滑动时背景渐变色的角度改变, 渐变色的动画, 包括一个有点酷的引导页, 启动页.
项目已上线: 叫直播伴侣, 可以下载下来玩玩,
另外代码也是开源的:
https://github.com/syik/BulletAnalyzer 觉得有意思的可以打赏一个Star~
项目中有一个有意思的功能, 中文语义近似的分析可以看看我的上一篇文章.
发现大家对动画更感兴趣, 下一篇讲讲动态的启动页与炫酷的引导页动画.
网友评论