年前忙着赶项目,也没时间更新,现在告一段落,把用到的技术点总结总结,这篇介绍介绍可自由拖动圆环的使用.
最开始我认为无非就是简单的动画效果,与下图差不多(项目实际效果图),后来才发现并非那么简单!
先来看看我们项目中的效果图
最开始我是想着使用UIView来实现,后来发现有些方法只能使用UIView的子类UIControl来实现,所以自定义视图类继承的是UIControl而非UIView!
如果想实现渐变色滑动圆环,我们要先画出底圆
-(id)initWithFrame:(CGRect)frame lineWidth:(CGFloat)lineWidth circleAngle:(CGFloat)circleAngle imageName:(NSString *)imageName
{
if ([super initWithFrame:frame]) {
// 线宽
_lineWidth = lineWidth;
// 半径
radius = self.frame.size.width/2 - _lineWidth/2;
// 圆起点(角度)
self.startAngle = -((circleAngle - 180)/2 + 180);
// 圆终点 (角度)
self.endAngle = (circleAngle - 180)/2;
self.imagev.image = [UIImage imageNamed:imageName];
self.backgroundColor = [UIColor clearColor];
}
return self;
}
#pragma mark - 绘制图形
- (void)drawRect:(CGRect)rect {
[super drawRect:rect];
CGContextRef context = UIGraphicsGetCurrentContext();
//1.绘制灰色的背景
CGContextAddArc(context, self.frame.size.width/2, self.frame.size.height/2, radius, degreesToRadians(self.startAngle),degreesToRadians(self.endAngle) , 0);
[[UIColor colorWithHexString:@"f2f2f2"] setStroke];
CGContextSetLineWidth(context, _lineWidth);
CGContextSetLineCap(context, kCGLineCapRound);
CGContextDrawPath(context, kCGPathStroke);
}
这样我们就绘制了最底层的灰色圆形背景
然后就是绘制颜色渐变效果:
接着上面的代码
// 设置线宽
CGContextSetLineWidth(context, _lineWidth);
// 设置线条端点为圆角
CGContextSetLineCap(context, kCGLineCapRound);
// 设置画笔颜色
CGContextSetFillColorWithColor(context, [UIColor blackColor].CGColor);
//绘制圆弧(这里终点使用的是_angle所以效果图你看到的是一半圆弧,如果使用self.endAngle就是全部了)
CGContextAddArc(context, self.frame.size.width/2, self.frame.size.height/2,radius,degreesToRadians(self.startAngle), degreesToRadians(_angle), 0);
//使用rgb颜色空间
CGColorSpaceRef colorSpace=CGColorSpaceCreateDeviceRGB();
/*指定渐变色
space:颜色空间
components:颜色数组,注意由于指定了RGB颜色空间,那么四个数组元素表示一个颜色(red、green、blue、alpha),
如果有三个颜色则这个数组有4*3个元素
locations:颜色所在位置(范围0~1),这个数组的个数不小于components中存放颜色的个数
count:渐变个数,等于locations的个数
*/
CGFloat compoents[12]={
248.0/255.0,86.0/255.0,86.0/255.0,1,
249.0/255.0,127.0/255.0,127.0/255.0,1,
1.0,1.0,1.0,1.0
};
CGFloat locations[3]={0,0.3,1.0};
CGGradientRef gradient= CGGradientCreateWithColorComponents(colorSpace, compoents, locations, 3);
// NSArray *colorArr = @[
// (id)[[UIColor colorWithHexString:@"56bcff"] CGColor],
// (id)[[UIColor colorWithHexString:@"56bcff"] CGColor]
// ];
// CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (__bridge CFArrayRef)colorArr, NULL);
/*绘制线性渐变
context:图形上下文
gradient:渐变色
startPoint:起始位置
endPoint:终止位置
options:绘制方式,kCGGradientDrawsBeforeStartLocation 开始位置之前就进行绘制,到结束位置之后不再绘制,
kCGGradientDrawsAfterEndLocation开始位置之前不进行绘制,到结束点之后继续填充
*/
// NSLog(@"point:%@",NSStringFromCGPoint([self pointFromAngle:_angle]));
// CGContextDrawLinearGradient(context, gradient, [self pointFromAngle:-225], [self pointFromAngle:_angle], kCGGradientDrawsAfterEndLocation);
//释放颜色空间
CGColorSpaceRelease(colorSpace);
colorSpace = NULL;
// ----------以下为重点----------
// 3. "反选路径"
// CGContextReplacePathWithStrokedPath
// 将context中的路径替换成路径的描边版本,使用参数context去计算路径(即创建新的路径是原来路径的描边)。用恰当的颜色填充得到的路径将产生类似绘制原来路径的效果。你可以像使用一般的路径一样使用它。例如,你可以通过调用CGContextClip去剪裁这个路径的描边
CGContextReplacePathWithStrokedPath(context);
// 剪裁路径
CGContextClip(context);
// 用渐变色填充(吗的,竟然这句话解决了渐变色的问题,艹,艹,艹)
CGContextDrawLinearGradient(context, gradient, CGPointMake(0, rect.size.height / 2), CGPointMake(rect.size.width, rect.size.height / 2), 0);
// 释放渐变色
CGGradientRelease(gradient);
效果如下
这样渐变色的绘制就完成了
重点来了,怎么实现可自主滑动与点击切换的效果呐?
UIControl中有以下两个方法
//返回Yes表示要继续跟踪触摸事件
- (BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(nullable UIEvent *)event;
//解决滑动改变的问题
- (BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(nullable UIEvent *)event;
首先来解决点击切换的问题(滑动也是可以的,只是没有连续的动画效果,只取滑动最后一个点做绘制效果)
效果如下:
点击切换无非就是获取到你点击那个点的point,然后根据这个point与圆心点坐标计算出弧度,然后根据这个弧度重新绘制渐变效果,有了这个思路就好办多了
#pragma mark - 解决了点击圆环直接跳转到相应角度(对应相应金额)的问题
-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[super touchesEnded:touches withEvent:event];
//集合转数组,其实只有一个对象
NSArray *arr = [touches allObjects];
UITouch *touch = arr[0];
CGPoint lastPoint = [touch locationInView:self];
NSLog(@"%@",NSStringFromCGPoint(lastPoint));
NSLog(@"%d",touches.count);
// 非 线性范围 则不可点击
CGFloat deltaX = lastPoint.x - self.frame.size.width/2;
CGFloat deltaY = lastPoint.y - self.frame.size.width/2;
//sqrt 平方根 还记得勾股定理吗?手动微笑
CGFloat distanceBetweenPoints = sqrt(deltaX*deltaX + deltaY*deltaY);
// NSLog(@"======%f",distanceBetweenPoints);
//设置可触发点击或者滑动事件的范围
if (distanceBetweenPoints>= radius - 50 && distanceBetweenPoints <= radius+ 20) {
[self movehandle:lastPoint];
}
}
#pragma mark - 根据点击或者滑动获取角度(弧度)
-(void)movehandle:(CGPoint)lastPoint{
//获得中心点
CGPoint centerPoint = CGPointMake(self.frame.size.width/2,
self.frame.size.height/2);
//计算中心点到任意点的角度
float currentAngle = AngleFromNorth(centerPoint,
lastPoint,
NO);
//浮点转整形
int angleInt = floor(currentAngle);
NSLog(@"%d",angleInt);
//保存新角度
if (angleInt >= 0 && angleInt <= self.endAngle) {
self.angle = angleInt;
}else if (angleInt >= 360+self.startAngle && angleInt <= 360){
self.angle = -(360 - angleInt);
}else if (angleInt >= self.endAngle && angleInt <= 360+self.startAngle){
//这部分(非圆弧范围)不做处理
}
//重新绘制
[self setNeedsDisplay];
}
#pragma mark - 从苹果是示例代码clockControl中拿来的函数,计算中心点到任意点的角度(弧度)
static inline float AngleFromNorth(CGPoint p1, CGPoint p2, BOOL flipped) {
CGPoint v = CGPointMake(p2.x-p1.x,p2.y-p1.y);
float vmag = sqrt(SQR(v.x) + SQR(v.y)), result = 0;
v.x /= vmag;
v.y /= vmag;
double radians = atan2(v.y,v.x);
result = radiansToDegrees(radians);
return (result >=0 ? result : result + 360.0);
}
效果如下:
如果想在滑动过程中有连续的绘制效果,则必须添加下面的方法了
#pragma mark - 持续滑动触发事件
-(BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event{
[super continueTrackingWithTouch:touch withEvent:event];
//获取触摸点
CGPoint lastPoint = [touch locationInView:self];
NSLog(@"%@",NSStringFromCGPoint(lastPoint));
// 非 money图片 不可点击
CGFloat deltaX = lastPoint.x - self.frame.size.width/2;
CGFloat deltaY = lastPoint.y - self.frame.size.width/2;
CGFloat distanceBetweenPoints = sqrt(deltaX*deltaX + deltaY*deltaY);
// NSLog(@"======%f",distanceBetweenPoints);
if (distanceBetweenPoints>= radius - 50 && distanceBetweenPoints <= radius+ 20) {
//使用触摸点来移动小块
[self movehandle:lastPoint];
}
//发送值改变事件
[self sendActionsForControlEvents:UIControlEventValueChanged];
return YES;
}
效果如下:
此时感觉功能完善了,可是当你滑动到最底部的时候会发现有异常,如下所示:
导致这种情况出现的原因是在你滑动过程中我们调用了continueTrackingWithTouch这个方法追踪你滑动或点击的位置,不在绘制圆环内没什么问题,但是当你从绘制圆环外到绘制圆环内的时候,捕捉到了现在点击点的位置调用movehandle这个方法对视图进行了绘制,所以这个问题怎么解决呐?
很简单,首先获取绘制的圆弧的最大Y值maxY,然后在continueTrackingWithTouch这个方法里面做判断,只要最终触摸点的y值大于等于此maxY(超出了圆弧),直接return NO就好了。修改后的代码如下
#pragma mark - 持续滑动触发事件
-(BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event{
[super continueTrackingWithTouch:touch withEvent:event];
//获取触摸点
CGPoint lastPoint = [touch locationInView:self];
// 超出圆弧部分直接返回NO(解决滑动超出圆弧范围的异常问题)
if (lastPoint.y >= self.maxY) {
return NO;
}
NSLog(@"%@",NSStringFromCGPoint(lastPoint));
// 非 money图片 不可点击
CGFloat deltaX = lastPoint.x - self.frame.size.width/2;
CGFloat deltaY = lastPoint.y - self.frame.size.width/2;
CGFloat distanceBetweenPoints = sqrt(deltaX*deltaX + deltaY*deltaY);
// NSLog(@"======%f",distanceBetweenPoints);
if (distanceBetweenPoints>= radius - 50 && distanceBetweenPoints <= radius+ 20) {
//使用触摸点来移动小块
[self movehandle:lastPoint];
}
//发送值改变事件
[self sendActionsForControlEvents:UIControlEventValueChanged];
return YES;
}
效果如下:
问题得到完美解决
关于怎么获取最大maxY值以及钱标图片的定位问题,在drawRect方法中直接调用
//最大Y值
self.maxY = [self pointFromAngle:self.startAngle].y;
//3.绘制拖动小块
CGPoint handleCenter = [self pointFromAngle: (self.angle)];
// 图片作进一步处理
self.imagev.frame = CGRectMake(handleCenter.x-moneyImgWidth/2, handleCenter.y - moneyImgWidth/2, moneyImgWidth, moneyImgWidth);
现在我们滑动或点击绘制的功能完成了,那怎么用此功能来根据弧度计算我们要展示的数字(金额)呐?
首先看看我们VC中的代码
- (void)viewDidLoad {
[super viewDidLoad];
self.circleView = [[SXCircleView alloc]initWithFrame:CGRectMake(self.view.frame.size.width/2 - 219/2, 100, 219, 219) lineWidth:10 circleAngle:240 imageName:@"qian"];
//ControlEvents记得选择UIControlEventValueChanged
[self.circleView addTarget:self action:@selector(newValue:) forControlEvents:UIControlEventValueChanged];
// 设置初始角度
[self.circleView changeAngle:-90];
[self.view addSubview:self.circleView];
self.moneyLabel = [[UILabel alloc]initWithFrame:CGRectMake(0, 100+219/2-25, self.view.frame.size.width, 30)];
self.moneyLabel.textColor = [UIColor redColor];
self.moneyLabel.font = [UIFont systemFontOfSize:30];
self.moneyLabel.textAlignment = NSTextAlignmentCenter;
self.moneyLabel.text = @"6000";
[self.view addSubview:self.moneyLabel];
}
- (void) newValue:(SXCircleView*)slider{
NSLog(@"newValue:%d",slider.angle);
}
运行后打印结果如下:
到一定角度时候执行了多次,猜想是
-(BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event 方法导致的结果
目前还没有想到好的解决办法(不影响功能的实现,但总体性能上总会有些许影响),看到的童鞋如果有好的办法希望私聊我,在此谢过!
竟然可以获取弧度,那么根据弧度的变化更改UILabel的数字就简单了
/*
*
* @param 2000 最小金额
* @param 10000 最大金额
* @param (slider.angle+210) 当前弧度
* @param 240 总弧度
*
*/
- (void) newValue:(SXCircleView*)slider{
NSLog(@"newValue:%d",slider.angle);
CGFloat xl;
xl = 2000/100+(slider.angle+210) * (10000/100 - 2000/100)/240;
self.moneyLabel.text = [NSString stringWithFormat:@"%.f00",xl];
}
运行效果如下
最后的最后就是关于怎么使用这个封装类的问题了
直接将demo中的SXCircleView、UIColor+SX(颜色处理的分类)类引入到项目中
在你需要用的的VC中按照你们的需求修改相应数据就好了
- (void)viewDidLoad {
[super viewDidLoad];
self.circleView = [[SXCircleView alloc]initWithFrame:CGRectMake(self.view.frame.size.width/2 - 219/2, 100, 219, 219) lineWidth:10 circleAngle:240 imageName:@"qian"];
[self.circleView addTarget:self action:@selector(newValue:) forControlEvents:UIControlEventValueChanged];
// 设置初始弧度(我设的局中,因为总弧度240,-210 -> 30 所以-90就是居中的弧度)
[self.circleView changeAngle:-90];
[self.view addSubview:self.circleView];
self.moneyLabel = [[UILabel alloc]initWithFrame:CGRectMake(0, 100+219/2-25, self.view.frame.size.width, 30)];
self.moneyLabel.textColor = [UIColor redColor];
self.moneyLabel.font = [UIFont systemFontOfSize:30];
self.moneyLabel.textAlignment = NSTextAlignmentCenter;
//因为设置了弧度-90(居中) 所以label初始值也应该是中间值(假设最小值2000,最大值10000)(2000+10000)/ 2
self.moneyLabel.text = @"6000";
[self.view addSubview:self.moneyLabel];
}
#pragma mark - 滑动或点击刻度表触发事件
/*
* @param 2000 最小值
* @param 10000 最大值
* @param slider.angle+210 当前弧度
* @param 240 总弧度
*
*/
- (void) newValue:(SXCircleView*)slider{
NSLog(@"newValue:%d",slider.angle);
CGFloat xl;
xl = 2000/100+(slider.angle+210) * (10000/100 - 2000/100)/240;
self.moneyLabel.text = [NSString stringWithFormat:@"%.f00",xl];
}
如果在集成过程中有什么疑问欢迎私信!