年前忙著趕項(xiàng)目桥状,也沒時(shí)間更新,現(xiàn)在告一段落戏罢,把用到的技術(shù)點(diǎn)總結(jié)總結(jié)屋谭,這篇介紹介紹可自由拖動(dòng)圓環(huán)的使用.
最開始我認(rèn)為無(wú)非就是簡(jiǎn)單的動(dòng)畫效果,與下圖差不多(項(xiàng)目實(shí)際效果圖)龟糕,后來(lái)才發(fā)現(xiàn)并非那么簡(jiǎn)單桐磁!
先來(lái)看看我們項(xiàng)目中的效果圖
最開始我是想著使用UIView來(lái)實(shí)現(xiàn),后來(lái)發(fā)現(xiàn)有些方法只能使用UIView的子類UIControl來(lái)實(shí)現(xiàn)讲岁,所以自定義視圖類繼承的是UIControl而非UIView我擂!
如果想實(shí)現(xiàn)漸變色滑動(dòng)圓環(huán),我們要先畫出底圓
-(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;
// 圓起點(diǎn)(角度)
self.startAngle = -((circleAngle - 180)/2 + 180);
// 圓終點(diǎn) (角度)
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);
}
這樣我們就繪制了最底層的灰色圓形背景
然后就是繪制顏色漸變效果:
接著上面的代碼
// 設(shè)置線寬
CGContextSetLineWidth(context, _lineWidth);
// 設(shè)置線條端點(diǎn)為圓角
CGContextSetLineCap(context, kCGLineCapRound);
// 設(shè)置畫筆顏色
CGContextSetFillColorWithColor(context, [UIColor blackColor].CGColor);
//繪制圓淮呤住(這里終點(diǎn)使用的是_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:顏色數(shù)組,注意由于指定了RGB顏色空間,那么四個(gè)數(shù)組元素表示一個(gè)顏色(red郎任、green秧耗、blue、alpha)舶治,
如果有三個(gè)顏色則這個(gè)數(shù)組有4*3個(gè)元素
locations:顏色所在位置(范圍0~1)分井,這個(gè)數(shù)組的個(gè)數(shù)不小于components中存放顏色的個(gè)數(shù)
count:漸變個(gè)數(shù)车猬,等于locations的個(gè)數(shù)
*/
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 開始位置之前就進(jìn)行繪制,到結(jié)束位置之后不再繪制尺锚,
kCGGradientDrawsAfterEndLocation開始位置之前不進(jìn)行繪制珠闰,到結(jié)束點(diǎn)之后繼續(xù)填充
*/
// NSLog(@"point:%@",NSStringFromCGPoint([self pointFromAngle:_angle]));
// CGContextDrawLinearGradient(context, gradient, [self pointFromAngle:-225], [self pointFromAngle:_angle], kCGGradientDrawsAfterEndLocation);
//釋放顏色空間
CGColorSpaceRelease(colorSpace);
colorSpace = NULL;
// ----------以下為重點(diǎn)----------
// 3. "反選路徑"
// CGContextReplacePathWithStrokedPath
// 將context中的路徑替換成路徑的描邊版本,使用參數(shù)context去計(jì)算路徑(即創(chuàng)建新的路徑是原來(lái)路徑的描邊)瘫辩。用恰當(dāng)?shù)念伾畛涞玫降穆窂綄a(chǎn)生類似繪制原來(lái)路徑的效果伏嗜。你可以像使用一般的路徑一樣使用它。例如伐厌,你可以通過調(diào)用CGContextClip去剪裁這個(gè)路徑的描邊
CGContextReplacePathWithStrokedPath(context);
// 剪裁路徑
CGContextClip(context);
// 用漸變色填充(嗎的承绸,竟然這句話解決了漸變色的問題,艸,艸挣轨,艸)
CGContextDrawLinearGradient(context, gradient, CGPointMake(0, rect.size.height / 2), CGPointMake(rect.size.width, rect.size.height / 2), 0);
// 釋放漸變色
CGGradientRelease(gradient);
效果如下
這樣漸變色的繪制就完成了
重點(diǎn)來(lái)了军熏,怎么實(shí)現(xiàn)可自主滑動(dòng)與點(diǎn)擊切換的效果吶?
UIControl中有以下兩個(gè)方法
//返回Yes表示要繼續(xù)跟蹤觸摸事件
- (BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(nullable UIEvent *)event;
//解決滑動(dòng)改變的問題
- (BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(nullable UIEvent *)event;
首先來(lái)解決點(diǎn)擊切換的問題(滑動(dòng)也是可以的卷扮,只是沒有連續(xù)的動(dòng)畫效果荡澎,只取滑動(dòng)最后一個(gè)點(diǎn)做繪制效果)
效果如下:
點(diǎn)擊切換無(wú)非就是獲取到你點(diǎn)擊那個(gè)點(diǎn)的point,然后根據(jù)這個(gè)point與圓心點(diǎn)坐標(biāo)計(jì)算出弧度晤锹,然后根據(jù)這個(gè)弧度重新繪制漸變效果摩幔,有了這個(gè)思路就好辦多了
#pragma mark - 解決了點(diǎn)擊圓環(huán)直接跳轉(zhuǎn)到相應(yīng)角度(對(duì)應(yīng)相應(yīng)金額)的問題
-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
[super touchesEnded:touches withEvent:event];
//集合轉(zhuǎn)數(shù)組,其實(shí)只有一個(gè)對(duì)象
NSArray *arr = [touches allObjects];
UITouch *touch = arr[0];
CGPoint lastPoint = [touch locationInView:self];
NSLog(@"%@",NSStringFromCGPoint(lastPoint));
NSLog(@"%d",touches.count);
// 非 線性范圍 則不可點(diǎn)擊
CGFloat deltaX = lastPoint.x - self.frame.size.width/2;
CGFloat deltaY = lastPoint.y - self.frame.size.width/2;
//sqrt 平方根 還記得勾股定理嗎?手動(dòng)微笑
CGFloat distanceBetweenPoints = sqrt(deltaX*deltaX + deltaY*deltaY);
// NSLog(@"======%f",distanceBetweenPoints);
//設(shè)置可觸發(fā)點(diǎn)擊或者滑動(dòng)事件的范圍
if (distanceBetweenPoints>= radius - 50 && distanceBetweenPoints <= radius+ 20) {
[self movehandle:lastPoint];
}
}
#pragma mark - 根據(jù)點(diǎn)擊或者滑動(dòng)獲取角度(弧度)
-(void)movehandle:(CGPoint)lastPoint{
//獲得中心點(diǎn)
CGPoint centerPoint = CGPointMake(self.frame.size.width/2,
self.frame.size.height/2);
//計(jì)算中心點(diǎn)到任意點(diǎn)的角度
float currentAngle = AngleFromNorth(centerPoint,
lastPoint,
NO);
//浮點(diǎn)轉(zhuǎn)整形
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中拿來(lái)的函數(shù),計(jì)算中心點(diǎn)到任意點(diǎn)的角度(弧度)
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);
}
效果如下:
如果想在滑動(dòng)過程中有連續(xù)的繪制效果抖甘,則必須添加下面的方法了
#pragma mark - 持續(xù)滑動(dòng)觸發(fā)事件
-(BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event{
[super continueTrackingWithTouch:touch withEvent:event];
//獲取觸摸點(diǎn)
CGPoint lastPoint = [touch locationInView:self];
NSLog(@"%@",NSStringFromCGPoint(lastPoint));
// 非 money圖片 不可點(diǎn)擊
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) {
//使用觸摸點(diǎn)來(lái)移動(dòng)小塊
[self movehandle:lastPoint];
}
//發(fā)送值改變事件
[self sendActionsForControlEvents:UIControlEventValueChanged];
return YES;
}
效果如下:
此時(shí)感覺功能完善了热鞍,可是當(dāng)你滑動(dòng)到最底部的時(shí)候會(huì)發(fā)現(xiàn)有異常,如下所示:
導(dǎo)致這種情況出現(xiàn)的原因是在你滑動(dòng)過程中我們調(diào)用了continueTrackingWithTouch這個(gè)方法追蹤你滑動(dòng)或點(diǎn)擊的位置衔彻,不在繪制圓環(huán)內(nèi)沒什么問題薇宠,但是當(dāng)你從繪制圓環(huán)外到繪制圓環(huán)內(nèi)的時(shí)候,捕捉到了現(xiàn)在點(diǎn)擊點(diǎn)的位置調(diào)用movehandle這個(gè)方法對(duì)視圖進(jìn)行了繪制艰额,所以這個(gè)問題怎么解決吶澄港?
很簡(jiǎn)單,首先獲取繪制的圓弧的最大Y值maxY柄沮,然后在continueTrackingWithTouch這個(gè)方法里面做判斷回梧,只要最終觸摸點(diǎn)的y值大于等于此maxY(超出了圓弧),直接return NO就好了祖搓。修改后的代碼如下
#pragma mark - 持續(xù)滑動(dòng)觸發(fā)事件
-(BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event{
[super continueTrackingWithTouch:touch withEvent:event];
//獲取觸摸點(diǎn)
CGPoint lastPoint = [touch locationInView:self];
// 超出圓弧部分直接返回NO(解決滑動(dòng)超出圓弧范圍的異常問題)
if (lastPoint.y >= self.maxY) {
return NO;
}
NSLog(@"%@",NSStringFromCGPoint(lastPoint));
// 非 money圖片 不可點(diǎn)擊
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) {
//使用觸摸點(diǎn)來(lái)移動(dòng)小塊
[self movehandle:lastPoint];
}
//發(fā)送值改變事件
[self sendActionsForControlEvents:UIControlEventValueChanged];
return YES;
}
效果如下:
問題得到完美解決
關(guān)于怎么獲取最大maxY值以及錢標(biāo)圖片的定位問題狱意,在drawRect方法中直接調(diào)用
//最大Y值
self.maxY = [self pointFromAngle:self.startAngle].y;
//3.繪制拖動(dòng)小塊
CGPoint handleCenter = [self pointFromAngle: (self.angle)];
// 圖片作進(jìn)一步處理
self.imagev.frame = CGRectMake(handleCenter.x-moneyImgWidth/2, handleCenter.y - moneyImgWidth/2, moneyImgWidth, moneyImgWidth);
現(xiàn)在我們滑動(dòng)或點(diǎn)擊繪制的功能完成了,那怎么用此功能來(lái)根據(jù)弧度計(jì)算我們要展示的數(shù)字(金額)吶拯欧?
首先看看我們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];
// 設(shè)置初始角度
[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);
}
運(yùn)行后打印結(jié)果如下:
到一定角度時(shí)候執(zhí)行了多次详囤,猜想是
-(BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event 方法導(dǎo)致的結(jié)果
目前還沒有想到好的解決辦法(不影響功能的實(shí)現(xiàn),但總體性能上總會(huì)有些許影響),看到的童鞋如果有好的辦法希望私聊我藏姐,在此謝過!
竟然可以獲取弧度隆箩,那么根據(jù)弧度的變化更改UILabel的數(shù)字就簡(jiǎn)單了
/*
*
* @param 2000 最小金額
* @param 10000 最大金額
* @param (slider.angle+210) 當(dāng)前弧度
* @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];
}
運(yùn)行效果如下
最后的最后就是關(guān)于怎么使用這個(gè)封裝類的問題了
直接將demo中的SXCircleView、UIColor+SX(顏色處理的分類)類引入到項(xiàng)目中
在你需要用的的VC中按照你們的需求修改相應(yīng)數(shù)據(jù)就好了
- (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];
// 設(shè)置初始弧度(我設(shè)的局中羔杨,因?yàn)榭偦《?40捌臊,-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;
//因?yàn)樵O(shè)置了弧度-90(居中) 所以label初始值也應(yīng)該是中間值(假設(shè)最小值2000,最大值10000)(2000+10000)/ 2
self.moneyLabel.text = @"6000";
[self.view addSubview:self.moneyLabel];
}
#pragma mark - 滑動(dòng)或點(diǎn)擊刻度表觸發(fā)事件
/*
* @param 2000 最小值
* @param 10000 最大值
* @param slider.angle+210 當(dāng)前弧度
* @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];
}
如果在集成過程中有什么疑問歡迎私信兜材!