....................數組
現狀網絡
折線圖的應用比較普遍,爲了加強用戶體驗,不少應用中都嵌入了折線圖。折線圖能夠更加直觀的表示數據的變化。網絡上有不少繪製折線圖的demo,有的也使用了動畫,可是線條顏色漸變的折線圖的demo少之又少,甚至能夠說沒有。該Blog闡述了動畫繪製線條顏色漸變的折線圖的實現方案,以及折線圖線條顏色漸變的實現原理,並附以完整的示例。
使用一個類來描述折線轉折處的點,代碼以下:ide
// 接口 /** 折線圖上的點 */ @interface IDLineChartPoint : NSObject /** x軸偏移量 */ @property (nonatomic, assign) float x; /** y軸偏移量 */ @property (nonatomic, assign) float y; /** 工廠方法 */ + (instancetype)pointWithX:(float)x andY:(float)y; @end // 實現 @implementation IDLineChartPoint + (instancetype)pointWithX:(float)x andY:(float)y { IDLineChartPoint *point = [[self alloc] init]; point.x = x; point.y = y; return point; } @end
折線圖視圖是一個自定義的UIView子類,代碼以下:動畫
// 接口 /** 折線圖視圖 */ @interface IDLineChartView : UIView /** 折線轉折點數組 */ @property (nonatomic, strong) NSMutableArray<IDLineChartPoint *> *pointArray; /** 開始繪製折線圖 */ - (void)startDrawlineChart; @end // 分類 @interface IDLineChartView () @end // 實現 @implementation IDLineChartView // 初始化 - (instancetype)initWithFrame:(CGRect)frame { if (self = [super initWithFrame:frame]) { // 設置折線圖的背景色 self.backgroundColor = [UIColor colorWithRed:243/255.0 green:243/255.0 blue:243/255.0 alpha:1.0]; } return self; } @end
效果如圖atom
與座標軸繪製相關的常量代理
/** 座標軸信息區域寬度 */ static const CGFloat kPadding = 25.0; /** 座標系中橫線的寬度 */ static const CGFloat kCoordinateLineWith = 1.0;
在分類中添加與座標軸繪製相關的成員變量code
/** X軸的單位長度 */ @property (nonatomic, assign) CGFloat xAxisSpacing; /** Y軸的單位長度 */ @property (nonatomic, assign) CGFloat yAxisSpacing; /** X軸的信息 */ @property (nonatomic, strong) NSMutableArray<NSString *> *xAxisInformationArray; /** Y軸的信息 */ @property (nonatomic, strong) NSMutableArray<NSString *> *yAxisInformationArray;
與座標軸繪製相關的成員變量的get方法component
- (CGFloat)xAxisSpacing { if (_xAxisSpacing == 0) { _xAxisSpacing = (self.bounds.size.width - kPadding) / (float)self.xAxisInformationArray.count; } return _xAxisSpacing; } - (CGFloat)yAxisSpacing { if (_yAxisSpacing == 0) { _yAxisSpacing = (self.bounds.size.height - kPadding) / (float)self.yAxisInformationArray.count; } return _yAxisSpacing; } - (NSMutableArray<NSString *> *)xAxisInformationArray { if (_xAxisInformationArray == nil) { // 建立可變數組 _xAxisInformationArray = [[NSMutableArray alloc] init]; // 當前日期和日曆 NSDate *today = [NSDate date]; NSCalendar *currentCalendar = [NSCalendar currentCalendar]; // 設置日期格式 NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; dateFormatter.dateFormat = @"MM-dd"; // 獲取最近一週的日期 NSDateComponents *components = [[NSDateComponents alloc] init]; for (int i = -7; i<0; i++) { components.day = i; NSDate *dayOfLatestWeek = [currentCalendar dateByAddingComponents:components toDate:today options:0]; NSString *dateString = [dateFormatter stringFromDate:dayOfLatestWeek]; [_xAxisInformationArray addObject:dateString]; } } return _xAxisInformationArray; } - (NSMutableArray<NSString *> *)yAxisInformationArray { if (_yAxisInformationArray == nil) { _yAxisInformationArray = [NSMutableArray arrayWithObjects:@"0", @"10", @"20", @"30", @"40", @"50", nil]; } return _yAxisInformationArray; } // 折線圖上的點(重寫get方法,後期須要暴露接口) - (NSMutableArray<IDLineChartPoint *> *)pointArray { if (_pointArray == nil) { _pointArray = [NSMutableArray arrayWithObjects:[IDLineChartPoint pointWithX:1 andY:1], [IDLineChartPoint pointWithX:2 andY:2], [IDLineChartPoint pointWithX:3 andY:1.5], [IDLineChartPoint pointWithX:4 andY:2], [IDLineChartPoint pointWithX:5 andY:4], [IDLineChartPoint pointWithX:6 andY:1], [IDLineChartPoint pointWithX:7 andY:2], nil]; } return _pointArray; }
繪製座標軸的相關信息orm
- (void)drawRect:(CGRect)rect { // 獲取上下文 CGContextRef context = UIGraphicsGetCurrentContext(); // x軸信息 [self.xAxisInformationArray enumerateObjectsUsingBlock:^(NSString * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { // 計算文字尺寸 UIFont *informationFont = [UIFont systemFontOfSize:10]; NSMutableDictionary *attributes = [NSMutableDictionary dictionary]; attributes[NSForegroundColorAttributeName] = [UIColor colorWithRed:158/255.0 green:158/255.0 blue:158/255.0 alpha:1.0]; attributes[NSFontAttributeName] = informationFont; CGSize informationSize = [obj sizeWithAttributes:attributes]; // 計算繪製起點 float drawStartPointX = kPadding + idx * self.xAxisSpacing + (self.xAxisSpacing - informationSize.width) * 0.5; float drawStartPointY = self.bounds.size.height - kPadding + (kPadding - informationSize.height) / 2.0; CGPoint drawStartPoint = CGPointMake(drawStartPointX, drawStartPointY); // 繪製文字信息 [obj drawAtPoint:drawStartPoint withAttributes:attributes]; }]; // y軸 [self.yAxisInformationArray enumerateObjectsUsingBlock:^(NSString * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { // 計算文字尺寸 UIFont *informationFont = [UIFont systemFontOfSize:10]; NSMutableDictionary *attributes = [NSMutableDictionary dictionary]; attributes[NSForegroundColorAttributeName] = [UIColor colorWithRed:158/255.0 green:158/255.0 blue:158/255.0 alpha:1.0]; attributes[NSFontAttributeName] = informationFont; CGSize informationSize = [obj sizeWithAttributes:attributes]; // 計算繪製起點 float drawStartPointX = (kPadding - informationSize.width) / 2.0; float drawStartPointY = self.bounds.size.height - kPadding - idx * self.yAxisSpacing - informationSize.height * 0.5; CGPoint drawStartPoint = CGPointMake(drawStartPointX, drawStartPointY); // 繪製文字信息 [obj drawAtPoint:drawStartPoint withAttributes:attributes]; // 橫向標線 CGContextSetRGBStrokeColor(context, 231 / 255.0, 231 / 255.0, 231 / 255.0, 1.0); CGContextSetLineWidth(context, kCoordinateLineWith); CGContextMoveToPoint(context, kPadding, self.bounds.size.height - kPadding - idx * self.yAxisSpacing); CGContextAddLineToPoint(context, self.bounds.size.width, self.bounds.size.height - kPadding - idx * self.yAxisSpacing); CGContextStrokePath(context); }]; }
效果如圖對象
在分類中添加與背景視圖相關的常量
/** 漸變背景視圖 */ @property (nonatomic, strong) UIView *gradientBackgroundView; /** 漸變圖層 */ @property (nonatomic, strong) CAGradientLayer *gradientLayer; /** 顏色數組 */ @property (nonatomic, strong) NSMutableArray *gradientLayerColors;
在初始化方法中添加調用設置背景視圖方法的代碼
[self drawGradientBackgroundView];
設置漸變視圖方法的具體實現
- (void)drawGradientBackgroundView { // 漸變背景視圖(不包含座標軸) self.gradientBackgroundView = [[UIView alloc] initWithFrame:CGRectMake(kPadding, 0, self.bounds.size.width - kPadding, self.bounds.size.height - kPadding)]; [self addSubview:self.gradientBackgroundView]; /** 建立並設置漸變背景圖層 */ //初始化CAGradientlayer對象,使它的大小爲漸變背景視圖的大小 self.gradientLayer = [CAGradientLayer layer]; self.gradientLayer.frame = self.gradientBackgroundView.bounds; //設置漸變區域的起始和終止位置(範圍爲0-1),即漸變路徑 self.gradientLayer.startPoint = CGPointMake(0, 0.0); self.gradientLayer.endPoint = CGPointMake(1.0, 0.0); //設置顏色的漸變過程 self.gradientLayerColors = [NSMutableArray arrayWithArray:@[(__bridge id)[UIColor colorWithRed:253 / 255.0 green:164 / 255.0 blue:8 / 255.0 alpha:1.0].CGColor, (__bridge id)[UIColor colorWithRed:251 / 255.0 green:37 / 255.0 blue:45 / 255.0 alpha:1.0].CGColor]]; self.gradientLayer.colors = self.gradientLayerColors; //將CAGradientlayer對象添加在咱們要設置背景色的視圖的layer層 [self.gradientBackgroundView.layer addSublayer:self.gradientLayer]; }
效果如圖
在分類中添加與折線繪製相關的成員變量
/** 折線圖層 */ @property (nonatomic, strong) CAShapeLayer *lineChartLayer; /** 折線圖終點處的標籤 */ @property (nonatomic, strong) UIButton *tapButton;
在初始化方法中添加調用設置折線圖層方法的代碼
[self setupLineChartLayerAppearance];
設置折線圖層方法的具體實現
- (void)setupLineChartLayerAppearance { /** 折線路徑 */ UIBezierPath *path = [UIBezierPath bezierPath]; [self.pointArray enumerateObjectsUsingBlock:^(IDLineChartPoint * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { // 折線 if (idx == 0) { [path moveToPoint:CGPointMake(self.xAxisSpacing * 0.5 + (obj.x - 1) * self.xAxisSpacing, self.bounds.size.height - kPadding - obj.y * self.yAxisSpacing)]; } else { [path addLineToPoint:CGPointMake(self.xAxisSpacing * 0.5 + (obj.x - 1) * self.xAxisSpacing, self.bounds.size.height - kPadding - obj.y * self.yAxisSpacing)]; } // 折線起點和終點位置的圓點 if (idx == 0 || idx == self.pointArray.count - 1) { [path addArcWithCenter:CGPointMake(self.xAxisSpacing * 0.5 + (obj.x - 1) * self.xAxisSpacing, self.bounds.size.height - kPadding - obj.y * self.yAxisSpacing) radius:2.0 startAngle:0 endAngle:2 * M_PI clockwise:YES]; } }]; /** 將折線添加到折線圖層上,並設置相關的屬性 */ self.lineChartLayer = [CAShapeLayer layer]; self.lineChartLayer.path = path.CGPath; self.lineChartLayer.strokeColor = [UIColor whiteColor].CGColor; self.lineChartLayer.fillColor = [[UIColor clearColor] CGColor]; // 默認設置路徑寬度爲0,使其在起始狀態下不顯示 self.lineChartLayer.lineWidth = 0; self.lineChartLayer.lineCap = kCALineCapRound; self.lineChartLayer.lineJoin = kCALineJoinRound; // 設置折線圖層爲漸變圖層的mask self.gradientBackgroundView.layer.mask = self.lineChartLayer; }
效果如圖(初始狀態不顯示折線)
動畫開始
/** 動畫開始,繪製折線圖 */ - (void)startDrawlineChart { // 設置路徑寬度爲4,使其可以顯示出來 self.lineChartLayer.lineWidth = 4; // 移除標籤, if ([self.subviews containsObject:self.tapButton]) { [self.tapButton removeFromSuperview]; } // 設置動畫的相關屬性 CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:@"strokeEnd"]; pathAnimation.duration = 2.5; pathAnimation.repeatCount = 1; pathAnimation.removedOnCompletion = NO; pathAnimation.fromValue = [NSNumber numberWithFloat:0.0f]; pathAnimation.toValue = [NSNumber numberWithFloat:1.0f]; // 設置動畫代理,動畫結束時添加一個標籤,顯示折線終點的信息 pathAnimation.delegate = self; [self.lineChartLayer addAnimation:pathAnimation forKey:@"strokeEnd"]; }
動畫結束,添加標籤
/** 動畫結束時,添加一個標籤 */ - (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag { if (self.tapButton == nil) { // 首次添加標籤(避免屢次建立和計算) CGRect tapButtonFrame = CGRectMake(self.xAxisSpacing * 0.5 + ([self.pointArray[self.pointArray.count - 1] x] - 1) * self.xAxisSpacing + 8, self.bounds.size.height - kPadding - [self.pointArray[self.pointArray.count - 1] y] * self.yAxisSpacing - 34, 30, 30); self.tapButton = [[UIButton alloc] initWithFrame:tapButtonFrame]; self.tapButton.enabled = NO; [self.tapButton setBackgroundImage:[UIImage imageNamed:@"bubble"] forState:UIControlStateDisabled]; [self.tapButton.titleLabel setFont:[UIFont systemFontOfSize:10]]; [self.tapButton setTitle:@"20" forState:UIControlStateDisabled]; } [self addSubview:self.tapButton]; }
集成折線圖視圖
添加成員變量
/** 折線圖 */ @property (nonatomic, strong) IDLineChartView *lineCharView;
在viewDidLoad方法中建立折線圖並添加到控制器的view上
self.lineCharView = [[IDLineChartView alloc] initWithFrame:CGRectMake(35, 164, 340, 170)]; [self.view addSubview:self.lineCharView];
添加開始繪製折線圖視圖的按鈕
添加成員變量
/** 開始繪製折線圖按鈕 */ @property (nonatomic, strong) UIButton *drawLineChartButton;
在viewDidLoad方法中建立開始按鈕並添加到控制器的view上
self.drawLineChartButton = [UIButton buttonWithType:UIButtonTypeSystem]; self.drawLineChartButton.frame = CGRectMake(180, 375, 50, 44); [self.drawLineChartButton setTitle:@"開始" forState:UIControlStateNormal]; [self.drawLineChartButton addTarget:self action:@selector(drawLineChart) forControlEvents:UIControlEventTouchUpInside]; [self.view addSubview:self.drawLineChartButton];
開始按鈕的點擊事件
// 開始繪製折線圖 - (void)drawLineChart { [self.lineCharView startDrawlineChart]; }
效果如圖
聲明:若須要工程文件,請在評論中聯繫我,很是願意與廣大技術愛好者溝通交流。下一篇博客將會介紹如何使用UICollectionView實現具備簽到功能的日曆