我们需要创建一个这样的控件能支持用户方便的选择0-360°之间的一个角度值,这就需要我们自定义控件了,如下图所示。实现方法是创建一个圆形的滑块,用户通过拖动手柄操作就能选择角度值。



绘制用户界面

1.首先,是用一个黑色的圆环当做滑块的背景。



//创建路径
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    CGContextAddArc(ctx, self.frame.size.width/2, self.frame.size.height/2, _radius, 0, M_PI*2, 0);
    
    //设置笔画颜色
    [[UIColor blackColor]setStroke];
    
    //设置线条宽度和类型
    CGContextSetLineWidth(ctx, TB_BACKGROUND_WIDTH);
    CGContextSetLineCap(ctx, kCGLineCapButt);
    
    //绘制
    CGContextDrawPath(ctx, kCGPathStroke);
复制代码

2.弧形图片遮罩绘制。 根据当前的角度绘制弧度。最后,利用CGBitmapContextCreateImage方法获取一张图片(刚刚绘制的弧)。这个图片就是我们所需要的掩码图了。



/****绘制用户的可操作区域*******/
    //创建遮罩图片
    UIGraphicsBeginImageContext(CGSizeMake(320, 320));
    CGContextRef imageCtx = UIGraphicsGetCurrentContext();
    CGContextAddArc(imageCtx, self.frame.size.width/2, self.frame.size.height/2, _radius, 0, ToRad(_angle), 0);
    [[UIColor redColor]set];
    
    //使用阴影创建模糊效果
    CGContextSetShadowWithColor(imageCtx, CGSizeMake(0, 0), 10, [UIColor blackColor].CGColor);
    
    //设置线条宽度
    CGContextSetLineWidth(imageCtx, TB_LINE_WIDTH);
    CGContextDrawPath(imageCtx, kCGPathStroke);
    
    //保存上下文内容到图片遮罩
    CGImageRef mask = CGBitmapContextCreateImage(UIGraphicsGetCurrentContext());
    UIGraphicsEndImageContext();
复制代码

3.裁剪上下文。 现在我们已经有一个的掩码图了。接着利用函数CGContextClipToMask对上下文进行裁剪——给该函数传入上面刚刚创建好的掩码图。代码如下所示:

/**裁剪上下文**/
    CGContextSaveGState(ctx);
    CGContextClipToMask(ctx, self.bounds, mask);
    CGImageRelease(mask);
复制代码

4.绘制渐变效果

//定义颜色变化范围
    CGFloat components[8] = {
        0.0, 0.0, 1.0, 1.0,
        1.0, 0.0, 1.0, 1.0 };
    CGColorSpaceRef baseSpace = CGColorSpaceCreateDeviceRGB();
    CGGradientRef gradient = CGGradientCreateWithColorComponents(baseSpace, components, NULL, 2);
    CGColorSpaceRelease(baseSpace),baseSpace = NULL;
    
    //定义渐变的方向
    CGPoint startPoint = CGPointMake(CGRectGetMidX(rect), CGRectGetMinY(rect));
    CGPoint endPoint = CGPointMake(CGRectGetMidX(rect), CGRectGetMaxY(rect));

    //创建并设置渐变
    CGContextDrawLinearGradient(ctx, gradient, startPoint, endPoint, 0);
    CGGradientRelease(gradient),gradient = NULL;
    CGContextRestoreGState(ctx);
复制代码

5.绘制手柄 根据当前的角度值,在的正确位置绘制出手柄,这里我们需要使用三角函数将一个标量值(scalar number)转换为CGPoint。然后根据中心点和半径绘制圆环手柄。



/*获取当前手柄的中心点*/
-(CGPoint)pointFromAngle:(int)angleInt{
    CGPoint centerPoint = CGPointMake(self.frame.size.width/2, self.frame.size.height/2);
    CGPoint result;
    result.y = round(centerPoint.y + _radius * sin(ToRad(-angleInt))) ;
    result.x = round(centerPoint.x + _radius * cos(ToRad(-angleInt)));
    return result;
}
复制代码
/*
1.保存当前的上下文(当在一个单独的函数中进行绘制任务时,将上下文的状2.态进行保存是编程的一个好习惯)。
3.给手柄设置一些阴影效果
4.定义手柄的颜色,然后利用CGContextAddArc将其绘制出来。
*/
-(void) drawTheHandle:(CGContextRef)ctx{
    CGContextSaveGState(ctx);
    CGContextSetShadowWithColor(ctx, CGSizeMake(0, 0), 3, [UIColor blackColor].CGColor);
    CGPoint handleCenter =  [self pointFromAngle: self.angle];
    [[UIColor colorWithWhite:1.0 alpha:0.7]set];
    CGContextAddArc(ctx, handleCenter.x, handleCenter.y, TB_LINE_WIDTH/2, 0, M_PI*2, 1);
    CGContextDrawPath(ctx, kCGPathFill);
    CGContextRestoreGState(ctx);
}
复制代码

至此,已经完成了全部的绘制任务。

跟踪用户的操作

1.开始跟踪 当在控件的bound内发生了一个触摸事件,首先会调用控件的beginTrackingWithTouch方法,该函数返回的BOOl值决定着:当触摸事件是dragged时,是否需要响应。

-(BOOL)beginTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event{
    [super beginTrackingWithTouch:touch withEvent:event];
    return YES;
}
复制代码

2.持续跟踪 当用户进行drag时,会调continueTrackingWithTouch,通过该方法我们可以根据touch位置对用户的操作进行过滤。

-(BOOL)continueTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event{
    [super continueTrackingWithTouch:touch withEvent:event];
    
    //获取触摸的点
    CGPoint lastPoint = [touch locationInView:self];
    
    //改变手柄的位置(仅当touch位置与手柄位置相交的时候才激活控件(activate control))
    CGMutablePathRef pathRef = CGPathCreateMutable();
    CGPoint handleCenter =  [self pointFromAngle: self.angle];
    CGPathAddArc(pathRef, NULL, handleCenter.x, handleCenter.y, TB_LINE_WIDTH/2, 0, M_PI*2, 0);
    if (CGPathContainsPoint(pathRef, NULL, lastPoint, NO)) {
        [self movehandle:lastPoint];
    }else{
        return NO;
    }

    //如果希望自己定制的控件与UIControl行为保持一致,那么当控件的值发生变化时,需要进行通知处理
    [self sendActionsForControlEvents:UIControlEventValueChanged];
    return YES;
}
复制代码

3.结束跟踪

-(void)endTrackingWithTouch:(UITouch *)touch withEvent:(UIEvent *)event{
    [super endTrackingWithTouch:touch withEvent:event];
}
复制代码

自定义控件的使用

1.通过调用initWithFrame方法实例化了一个圆形滑块(自定义的控件)。

SliderControl *slider = [[SliderControl alloc]initWithFrame:CGRectMake(0, 60, TB_SLIDER_SIZE, TB_SLIDER_SIZE)];
 [self.view addSubview:slider];
复制代码

2.接着定义了如何与该控件进行交互 使用addTarget:action:forControlEvent:方法,每当用户移动手柄时,圆形滑块都会发送一个UIControlEventValueChanged事件

[slider addTarget:self action:@selector(newValue:) forControlEvents:UIControlEventValueChanged];
复制代码