ios 可拖動(dòng)、點(diǎn)擊漸變圓環(huán)

年前忙著趕項(xiàng)目,也沒時(shí)間更新,現(xiàn)在告一段落,把用到的技術(shù)點(diǎn)總結(jié)總結(jié),這篇介紹介紹可自由拖動(dòng)圓環(huán)的使用.
最開始我認(rèn)為無非就是簡(jiǎn)單的動(dòng)畫效果,與下圖差不多(項(xiàng)目實(shí)際效果圖),后來才發(fā)現(xiàn)并非那么簡(jiǎn)單!


ptyhgif.gif

先來看看我們項(xiàng)目中的效果圖

shoujigif2.gif

最開始我是想著使用UIView來實(shí)現(xiàn),后來發(fā)現(xiàn)有些方法只能使用UIView的子類UIControl來實(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);
}


這樣我們就繪制了最底層的灰色圓形背景

WechatIMG4720.jpeg

然后就是繪制顏色漸變效果:
接著上面的代碼

// 設(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)建新的路徑是原來路徑的描邊)。用恰當(dāng)?shù)念伾畛涞玫降穆窂綄a(chǎn)生類似繪制原來路徑的效果。你可以像使用一般的路徑一樣使用它。例如,你可以通過調(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);


效果如下

屏幕快照 2018-02-27 下午5.27.45.png

這樣漸變色的繪制就完成了

重點(diǎn)來了,怎么實(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;


首先來解決點(diǎn)擊切換的問題(滑動(dòng)也是可以的,只是沒有連續(xù)的動(dòng)畫效果,只取滑動(dòng)最后一個(gè)點(diǎn)做繪制效果)
效果如下:

點(diǎn)擊切換無非就是獲取到你點(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中拿來的函數(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);
}


效果如下:

ggtouchgif.gif

如果想在滑動(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)來移動(dòng)小塊
        [self movehandle:lastPoint];
    }

    //發(fā)送值改變事件
    [self sendActionsForControlEvents:UIControlEventValueChanged];
    return YES;
}


效果如下:

ggcontinuegif.gif

此時(shí)感覺功能完善了,可是當(dāng)你滑動(dòng)到最底部的時(shí)候會(huì)發(fā)現(xiàn)有異常,如下所示:

ggcontinueycgif.gif

導(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)來移動(dòng)小塊
        [self movehandle:lastPoint];
    }

    //發(fā)送值改變事件
    [self sendActionsForControlEvents:UIControlEventValueChanged];
    return YES;
}


效果如下:

jjwtgif.gif

問題得到完美解決
關(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)擊繪制的功能完成了,那怎么用此功能來根據(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é)果如下:

屏幕快照 2018-02-28 下午3.01.52.png
到一定角度時(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)行效果如下

zzlgif2.gif

最后的最后就是關(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];
}


如果在集成過程中有什么疑問歡迎私信!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容