这是我第一次在简书上写文章,主要是之前有点懒。最近经过某人的提醒,觉得是时候写点东西总结一下了。先写一下写博客的原因和动力。
- 使自己理解的更加深刻;
- 给自己立一些时间戳,可以看到时间的轨迹;
- 给小伙伴们一些参考,看了很多大神的文章,使小弟进步不少;
先说自定义控件的原因:
UIKit并没有提供所有的控件样式,比如像一些柱状图、饼图、曲线图等。类似的应用例如股票类APP里会经常出现自定义的控件。
再说一下这次要做的样式:
用图表去显示一个人的血压情况,并且点击控件具体部分显示详情。
先上图
从图片可以看出控件细节还是很多的,所以需要分解一下,这样更加容易理解和实现。
所以步骤应该是:
- 画底部直线和中间虚线;
- 画圆圈的连接线;
- 画圆圈;
- 根据点击事件位置画详情消息界面;
- 根据逻辑在需要绘制的时候调用;
- 添加事件
下面是提供的画这些小图形的具体方法
1、画底部直线和中间虚线
/**
画直线的方法
@param startPoint 开始点
@param endPoint 结束点
*/
- (void)drawVerticalLineAtstartPoint:(CGPoint)startPoint endPonit:(CGPoint)endPoint
{
// 先获取当前画布(上下文)
CGContextRef gc = UIGraphicsGetCurrentContext();
// 创建一个路径
CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint(path, NULL, startPoint.x, startPoint.y);
CGPathAddLineToPoint(path, NULL, endPoint.x, endPoint.y);
// 将路径加到画布中去
CGContextAddPath(gc, path);
// 画布的参数设置
CGContextSetStrokeColorWithColor(gc, self.lineBGColor.CGColor);
CGContextSetLineWidth(gc, self.lineW);
// 画布中画图
CGContextDrawPath(gc, kCGPathFillStroke);
// 最后不要忘了去释放路径
CGPathRelease(path);
}
/**
画虚线方法
@param startPoint 起始点
@param endPoint 结束点
@param color 虚线的颜色
*/
- (void)drawDashVerticalLineAtstartPoint:(CGPoint)startPoint endPonit:(CGPoint)endPoint andColor:(UIColor *)color
{
CGContextRef gc = UIGraphicsGetCurrentContext();
CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint(path, NULL, startPoint.x, startPoint.y);
CGPathAddLineToPoint(path, NULL, endPoint.x, endPoint.y);
CGContextAddPath(gc, path);
// 虚线的间隔
CGFloat lengths[] = {2,2};
// 虚线的起始点
CGContextSetLineDash(gc, 0, lengths,2);
CGContextSetStrokeColorWithColor(gc, color.CGColor);
CGContextSetLineWidth(gc, self.lineW);
CGContextDrawPath(gc, kCGPathFillStroke);
CGPathRelease(path);
// 将画布环境设置为实线
CGContextSetLineDash(gc, 0, NULL, 0);
}
2、圆点的画连接线
/**
画连接线
@param leftPoint 起始点
@param rightPoint 结束点
*/
- (void)drawBezierLinkLineWithLeftPoint:(CGPoint)leftPoint rightPoint:(CGPoint)rightPoint
{
// 此方法返回一个点,用来做控制点,
CGPoint midPoint = midpoint(leftPoint, rightPoint);
//用贝瑟尔曲线是为了微调连接线不至于僵硬
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:leftPoint];
[path addQuadCurveToPoint:rightPoint controlPoint:midPoint];
[path setLineWidth:self.linkLineW];
CGContextRef gc = UIGraphicsGetCurrentContext();
CGContextSetStrokeColorWithColor(gc, self.linkLineColor.CGColor);
[path stroke];
}
3、画圆圈
/**
画圆圈
@param color 颜色
@param isFill 实心还是空心
@param rect 画的位置
*/
- (void)drawCircleImageWithColor:(UIColor *)color isFill:(BOOL)isFill AtRect:(CGRect)rect
{
CGContextRef gc = UIGraphicsGetCurrentContext();
CGContextAddArc(gc, CGRectGetMidX(rect), CGRectGetMidY(rect), 5, 0, M_PI*2, 0);
if (isFill) {
CGContextSetFillColorWithColor(gc, color.CGColor);
}
else
{
CGContextSetFillColorWithColor(gc, [UIColor whiteColor].CGColor);
}
CGContextSetStrokeColorWithColor(gc, color.CGColor);
CGContextDrawPath(gc, kCGPathFillStroke);
}
4、画消息框
/**
画消息框
@param point 消息框的尖尖指向的那个点
@param color 消息框的背景颜色
@param value 要显示的值
*/
- (void)drawTopMsgAtPoint:(CGPoint)point andColor:(UIColor *)color andValue:(CGFloat)value
{
CGContextRef gc = UIGraphicsGetCurrentContext();
CGMutablePathRef path = CGPathCreateMutable();
CGFloat pointSpace = 6.0;
CGFloat triangleH = 3.0;
CGFloat rectWidth = 30.0;
CGFloat rectHeight = 14.0;
CGFloat triangleY = point.y-pointSpace;// 这里的点是三角形的尖尖
CGFloat triangleX = point.x;
CGFloat rato = 3.0;
// 这里的点是三角形的尖尖
CGPathMoveToPoint(path, NULL, triangleX, triangleY) ;
// 画弧线
CGPathAddArcToPoint(path, NULL, triangleX-triangleH, triangleY-triangleH, triangleX-rectWidth/2, triangleY-triangleH, rato);
CGPathAddArcToPoint(path, NULL, triangleX-rectWidth/2, triangleY-triangleH, triangleX-rectWidth/2, triangleY-triangleH-rectHeight, rato);
CGPathAddArcToPoint(path, NULL, triangleX-rectWidth/2, triangleY-triangleH-rectHeight, triangleX+rectWidth/2, triangleY-triangleH-rectHeight, rato);
CGPathAddArcToPoint(path, NULL, triangleX+rectWidth/2, triangleY-triangleH-rectHeight, triangleX+rectWidth/2, triangleY-triangleH, rato);
CGPathAddArcToPoint(path, NULL, triangleX+rectWidth/2, triangleY-triangleH, triangleX+triangleH, triangleY-triangleH, rato);
CGPathAddArcToPoint(path, NULL, triangleX+triangleH, triangleY-triangleH, triangleX, triangleY, rato);
CGContextAddPath(gc, path);
CGContextSetFillColorWithColor(gc, color.CGColor);
CGContextDrawPath(gc, kCGPathFillStroke);
CGPathRelease(path);
NSString * valueStr = [NSString stringWithFormat:@"%.0f",value];
NSDictionary *valueAttributes = @{NSForegroundColorAttributeName:[UIColor whiteColor],NSFontAttributeName:[UIFont systemFontOfSize:12]};
// 计算字符串的大小
CGRect valueRect = [valueStr boundingRectWithSize:CGSizeMake(rectWidth, rectHeight) options:NSStringDrawingUsesLineFragmentOrigin attributes:valueAttributes context:nil];
CGContextSaveGState(gc);
// 画字符串
[valueStr drawInRect:CGRectMake(triangleX-valueRect.size.width/2, triangleY-triangleH-rectHeight/2-valueRect.size.height/2, valueRect.size.width, valueRect.size.height) withAttributes:valueAttributes];
CGContextRestoreGState(gc);
}
5、将这些方法在view绘制的时候通过一定的逻辑调用
这段代码内引用有其他逻辑,拷贝也不能运行,只是提供思路。
- (void)drawRect:(CGRect)rect {
// Drawing code
self.backgroundColor = [UIColor whiteColor];
CGFloat lastStarY = 0;
CGFloat lastEndY = 0;
CGFloat lastX = 0;
CGFloat maxSpaceValue = self.xueyaMaxandMinValue.topValue-self.xueyaMaxandMinValue.bottomValue;
CGFloat maxSpacePt = self.topH-self.verticalSapce*2;
// 这个比率是血压值和点的转换
CGFloat rato = maxSpacePt/maxSpaceValue;
// 根据数据去画界面
for (int i = 0; i<self.xueYaValueArray.count; i++) {
CGFloat x = self.spaceW+i*self.leftSpaceW;
XueYaValue *value = self.xueYaValueArray[i];
CGFloat starY = fabs(maxSpacePt - (value.topValue-self.xueyaMaxandMinValue.bottomValue)*rato)+self.verticalSapce;
;
CGFloat endY = fabs(maxSpacePt - (value.bottomValue-self.xueyaMaxandMinValue.bottomValue)*rato)+self.verticalSapce;
if (i<self.xueYaValueArray.count-1) {
lastX = x+self.spaceW;
XueYaValue *lastValue = self.xueYaValueArray[i+1];
lastStarY = fabs(maxSpacePt - (lastValue.topValue-self.xueyaMaxandMinValue.bottomValue)*rato)+self.verticalSapce;
lastEndY = fabs(maxSpacePt - (lastValue.bottomValue-self.xueyaMaxandMinValue.bottomValue)*rato)+self.verticalSapce;
}
[self drawVerticalLineAtstartPoint:CGPointMake(x, 0) endPonit:CGPointMake(x, starY)];
[self drawVerticalLineAtstartPoint:CGPointMake(x, endY) endPonit:CGPointMake(x, self.topH)];
// 这里140,180,是临界点,用来区分颜色的
if (value.topValue<140) {
[self drawDashVerticalLineAtstartPoint:CGPointMake(x, starY) endPonit:CGPointMake(x, endY) andColor:[UIColor colorWithHexString:@"78B3F5"]];
}
else if(value.topValue<180)
{
[self drawDashVerticalLineAtstartPoint:CGPointMake(x, starY) endPonit:CGPointMake(x, endY) andColor:[UIColor colorWithHexString:@"D97F5C"]];
}
else
{
[self drawDashVerticalLineAtstartPoint:CGPointMake(x, starY) endPonit:CGPointMake(x, endY) andColor:[UIColor colorWithHexString:@"FF4B4B"]];
}
if (i<self.xueYaValueArray.count-1) {
[self drawBezierLinkLineWithLeftPoint:CGPointMake(lastX, lastStarY) rightPoint:CGPointMake(x, starY)];
[self drawBezierLinkLineWithLeftPoint:CGPointMake(lastX, lastEndY) rightPoint:CGPointMake(x, endY)];
}
if (value.topValue<140) {
[self drawCircleImageWithColor:[UIColor colorWithHexString:@"78B3F5"] isFill:YES AtRect:CGRectMake(x-5, starY-5, 10, 10)];
[self drawCircleImageWithColor:[UIColor colorWithHexString:@"78B3F5"] isFill:YES AtRect:CGRectMake(x-5, endY-5, 10, 10)];
}
else if (value.topValue<180)
{
[self drawCircleImageWithColor:[UIColor colorWithHexString:@"D97F5C"] isFill:YES AtRect:CGRectMake(x-5, starY-5, 10, 10)];
[self drawCircleImageWithColor:[UIColor colorWithHexString:@"D97F5C"] isFill:YES AtRect:CGRectMake(x-5, endY-5, 10, 10)];
}
else
{
[self drawCircleImageWithColor:[UIColor colorWithHexString:@"FF4B4B"] isFill:YES AtRect:CGRectMake(x-5, starY-5, 10, 10)];
[self drawCircleImageWithColor:[UIColor colorWithHexString:@"FF4B4B"] isFill:YES AtRect:CGRectMake(x-5, endY-5, 10, 10)];
}
[self drawTimeStrAtX:x andValue:value.timeStr];
if (i>0) {
XueYaValue *lastValue = self.xueYaValueArray[i-1];
if(![value.dateStr isEqualToString:lastValue.dateStr])
{
[self drawDateStrAtX:x andValue:value.dateStr];
}
}
else
{
[self drawDateStrAtX:x andValue:value.dateStr];
}
if ([self.currentValue isEqual:self.xueYaValueArray[i]]) {
if (value.topValue<140) {
[self drawBottomMsgAtPoint:CGPointMake(x, endY) andColor:[UIColor colorWithHexString:@"78B3F5"] andValue:self.currentValue.bottomValue];
[self drawTopMsgAtPoint:CGPointMake(x, starY) andColor:[UIColor colorWithHexString:@"78B3F5"] andValue:self.currentValue.topValue];
}
else if (value.topValue<180)
{
[self drawBottomMsgAtPoint:CGPointMake(x, endY) andColor:[UIColor colorWithHexString:@"D97F5C"] andValue:self.currentValue.bottomValue];
[self drawTopMsgAtPoint:CGPointMake(x, starY) andColor:[UIColor colorWithHexString:@"D97F5C"] andValue:self.currentValue.topValue];
}
else
{
[self drawBottomMsgAtPoint:CGPointMake(x, endY) andColor:[UIColor colorWithHexString:@"FF4B4B"] andValue:self.currentValue.bottomValue];
[self drawTopMsgAtPoint:CGPointMake(x, starY) andColor:[UIColor colorWithHexString:@"FF4B4B"] andValue:self.currentValue.topValue];
}
}
}
}
6、最后添加事件
此处添加事件,画龙点睛。
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
UITouch *touch = touches.anyObject;
// 获取点击位置
CGPoint point = [touch locationInView:self];
// 计算需要交互的点
NSInteger index = (point.x - self.leftSpaceW+self.spaceW/2)/self.spaceW;
NSLog(@"点击位置为:%@",NSStringFromCGPoint(point));
NSLog(@"点击index:%ld",index);
if (self.xueYaValueArray.count>index) {
// 此处存储需要绘制消息的内容
self.currentValue = self.xueYaValueArray[index];
// 刷新界面,重新绘制,
[self setNeedsDisplay];
}
}
总结
CoreGraphics 主要用于自定义控件,实线个性化需求。
关于CoreGraphics 官方这么说明
这是一个绘图专用的API族,它经常被称为QuartZ或QuartZ 2D。Core Graphics是iOS上所有绘图功能的基石,包括UIKit。
所以是一组很强大的API,需要我们后续学习。