欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

IOS绘制动画颜色渐变折线条

程序员文章站 2023-01-01 18:54:24
先给大家展示下效果图: 概述 现状 折线图的应用比较广泛,为了增强用户体验,很多应用中都嵌入了折线图。折线图可以更加直观的表示数据的变化。网络上有很多绘制折线图的...

先给大家展示下效果图:

IOS绘制动画颜色渐变折线条

概述

现状

折线图的应用比较广泛,为了增强用户体验,很多应用中都嵌入了折线图。折线图可以更加直观的表示数据的变化。网络上有很多绘制折线图的demo,有的也使用了动画,但是线条颜色渐变的折线图的demo少之又少,甚至可以说没有。该blog阐述了动画绘制线条颜色渐变的折线图的实现方案,以及折线图线条颜色渐变的实现原理,并附以完整的示例。

成果

本人已将折线图封装到了一个uiview子类中,并提供了相应的接口。该自定义折线图视图,基本上可以适用于大部分需要集成折线图的项目。若你遇到相应的需求可以直接将文件拖到项目中,调用相应的接口即可

项目文件中包含了大量的注释代码,若你的需求与折线图的实现效果有差别,那么你可以对项目文件的进行修改,也可以依照思路定义自己的折线图视图

blog中涉及到的知识点

calayer

图层,可以简单的看做一个不接受用户交互的uiview

每个图层都具有一个calayer类型mask属性,作用与蒙版相似

blog中主要用到的calayer子类有

cagradientlayer,绘制颜色渐变的背景图层

cashapelayer,绘制折线图

caanimation

核心动画的基类(不可实例化对象),实现动画操作
quartz 2d
一个二维的绘图引擎,用来绘制折线(path)和坐标轴信息(text)

实现思路

折线图视图

整个折线图将会被自定义到一个uiview子类中

坐标轴绘制

坐标轴直接绘制到折线图视图上,在自定义折线图视图的 drawrect 方法中绘制坐标轴相关信息(线条和文字)

注意坐标系的转换

线条颜色渐变

失败的方案

开始的时候,为了实现线条颜色渐变,我的思考方向是,如何改变路径(uibezierpath)的渲染颜色(strokecolor)。但是strokecolor只可以设置一种,所以最终无法实现线条颜色的渐变。

成功的方案

在探索过程中找到了calayer的calayer类型的mask()属性,最终找到了解决方案,即:使用uiview对象封装渐变背景视图(frame为折线图视图的减去坐标轴后的frame),创建一个cagradientlayer渐变图层添加到背景视图上。

创建一个cashapelayer对象,用于绘制线条,线条的渲染颜色(strokecolor)为whitecolor,填充颜色(fillcolor)为clearcolor,从而显示出渐变图层的颜色。将cashapelayer对象设置为背景视图的mask属性,即背景视图的蒙版。

折线

使用 uibezierpath 类来绘制折线

折线转折处尖角的处理,使用 kcalinecapround 与 kcalinejoinround 设置折线转折处为圆角

折线起点与终点的圆点的处理,可以直接在 uibezierpath 对象上添加一个圆,设置远的半径为路径宽度的一半,从而保证是一个实心的圆而不是一个圆环

折线转折处的点

折线转折处点使用一个类来描述(不使用cgpoint的原因是:折线转折处的点需要放到一个数组中)

坐标轴信息

x轴、y轴的信息分别放到一个数组中

x轴显示的是最近七天的日期,y轴显示的是最近七天数据变化的幅度

动画

使用cabasicanimation类来完成绘制折线图时的动画

需要注意的是,折线路径在一开始时需要社会线宽为0,开始绘制时才设置为适当的线宽,保证一开折线路径是隐藏的

标签

在动画结束时,向折线图视图上添加一个标签(uibutton对象),显示折线终点的信息

标签的位置,需要根据折线终点的位置计算

具体实现

折线转折处的点

使用一个类来描述折线转折处的点,代码如下:

// 接口
/** 折线图上的点 */
@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

效果如图

IOS绘制动画颜色渐变折线条

绘制坐标轴信息

与坐标轴绘制相关的常量

/** 坐标轴信息区域宽度 */
static const cgfloat kpadding = 25.0;
/** 坐标系中横线的宽度 */
static const cgfloat kcoordinatelinewith = 1.0;

在分类中添加与坐标轴绘制相关的成员变量

/** 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方法

- (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;
}

绘制坐标轴的相关信息

- (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);
}];
}

效果如图

IOS绘制动画颜色渐变折线条

渐变背景视图

在分类中添加与背景视图相关的常量

/** 渐变背景视图 */
@property (nonatomic, strong) uiview *gradientbackgroundview;
/** 渐变图层 */
@property (nonatomic, strong) cagradientlayer *gradientlayer;
/** 颜色数组 */
@property (nonatomic, strong) nsmutablearray *gradientlayercolors;

在初始化方法中添加调用设置背景视图方法的代码

设置渐变视图方法的具体实现

- (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];
}

效果如图

IOS绘制动画颜色渐变折线条

折线

在分类中添加与折线绘制相关的成员变量

/** 折线图层 */
@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;
}

效果如图(初始状态不显示折线)

IOS绘制动画颜色渐变折线条

动画的开始与结束

动画开始

/** 动画开始,绘制折线图 */
- (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];
}

好了,关于ios绘制动画颜色渐变折线条就给大家介绍这么多,希望对大家有所帮助!