本文主要介绍如何实现一个双向滑块,最终的效果如下:

效果图

Part I 双向滑块的整体结构

从上图观察可得,整个双向滑块控件共由四部分组成,如下图。

image.png

View:整个双向滑块控件。
sliderRect:白条,未被选中的区域。
selectRect:黑条,被选中的区域。
startRect:红条,左滑块。
endRect:蓝条,右滑块。

Part II 双向滑块的初始化及绘制

1、四个区域的初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
barWidth = NSWidth(self.bounds)*0.03;

sliderRect = NSMakeRect(barWidth,
NSHeight(self.bounds)/4,
NSWidth(self.bounds)-barWidth*2,
NSHeight(self.bounds)/2);

startRect = NSMakeRect(NSMinX(self.bounds),
NSMinY(self.bounds),
barWidth,
NSHeight(self.bounds));

endRect = NSMakeRect(NSMaxX(self.bounds)-barWidth,
NSMinY(self.bounds),
barWidth,
NSHeight(self.bounds));

selectRect = NSMakeRect(barWidth,
NSHeight(self.bounds)/4,
(NSWidth(self.bounds)-barWidth),
NSHeight(self.bounds)/2);

2、四个区域的绘制
对绘制NSRect区域的代码做一层封装,其方法定义如下:

1
2
3
4
5
-(void)drawRectBound:(NSRect)bound color:(NSColor*)color{
NSBezierPath *path = [NSBezierPath bezierPathWithRect:bound];
[color setFill];
[path fill];
}

因此,在drawRect方法中调用此方法即可,代码如下:

1
2
3
4
[self drawRectBound:sliderRect color:[NSColor whiteColor]];
[self drawRectBound:selectRect color:[NSColor blackColor]];
[self drawRectBound:startRect color:[NSColor redColor]];
[self drawRectBound:endRect color:[NSColor blueColor]];

Part III 实现左右滑块的移动

在实现滑块的移动时,需要使用以下四个方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
//返回YES的话,View会接收mouseDown消息。
-(BOOL)acceptsFirstMouse:(NSEvent *)event{
return YES;
}

//鼠标按下时,执行此方法。
-(void)mouseDown:(NSEvent *)event{
NSPoint clickedLocationPoint = [event locationInWindow];
NSPoint localPoint = [self convertPoint:clickedLocationPoint fromView:nil];

if(NSPointInRect(localPoint, startRect)){
//左滑块被点击
startChange = YES;
}else if(NSPointInRect(localPoint, endRect)){
//右滑块被点击
endChange = YES;
}
}

//鼠标松开时,执行此方法。
-(void)mouseUp:(NSEvent *)event{
startChange = NO;
endChange = NO;
}

//鼠标拖拽时执行此方法。
-(void)mouseDragged:(NSEvent *)event{
NSPoint clickLocationPoint = [event locationInWindow];
NSPoint localPoint = [self convertPoint:clickLocationPoint fromView:nil];

if(startChange){
[self leftBarChange:localPoint];
}else if(endChange){
[self rightBarChange:localPoint];
}
}

在leftBarChange和rightBarChange方法中,我们需要通过坐标的计算重新绘制双向滑块的四个区域,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
-(void)leftBarChange:(NSPoint)localPoint{
NSRect origin = startRect;
if(localPoint.x-barWidth < NSMinX(self.bounds)){
//超出最小值
startRect = NSMakeRect(0,
NSMinY(origin),
NSWidth(origin),
NSHeight(origin));
}else if(localPoint.x > NSMinX(endRect)){
//超出最大值
startRect = NSMakeRect(NSMinX(endRect)-barWidth,
NSMinY(origin),
NSWidth(origin),
NSHeight(origin));
}else{
//正常值
startRect = NSMakeRect(localPoint.x-barWidth,
NSMinY(origin),
NSWidth(origin),
NSHeight(origin));

}
selectRect = NSMakeRect(NSMaxX(startRect),
NSHeight(self.bounds)/4,
NSMinX(endRect)-NSMaxX(startRect),
NSHeight(self.bounds)/2);
[self setNeedsDisplay:YES];
}

-(void)rightBarChange:(NSPoint)localPoint{
NSRect origin = endRect;

if(localPoint.x < NSMaxX(startRect)){
//超出最小值
endRect = NSMakeRect(NSMaxX(startRect),
NSMinY(origin),
NSWidth(origin),
NSHeight(origin));
}else if(localPoint.x+barWidth > NSMaxX(self.bounds)){
//超出最大值
endRect = NSMakeRect(NSMaxX(self.bounds)-barWidth,
NSMinY(self.bounds),
barWidth,
NSHeight(self.bounds));

}else{
//正常值
endRect = NSMakeRect(localPoint.x,
NSMinY(origin),
NSWidth(origin),
NSHeight(origin));
}
selectRect = NSMakeRect(NSMaxX(startRect),
NSHeight(self.bounds)/4,
NSMinX(endRect)-NSMaxX(startRect),
NSHeight(self.bounds)/2);
[self setNeedsDisplay:YES];
}

Part IV 计算左右滑块的选中范围值

当左右滑块的位置发生变化时,需要计算左右滑块当前位置的值,其公式如下:

1
2
3
4
5
6
7
//计算左滑块起始值
double startScale = (NSMaxX(startRect)-NSMinX(sliderRect))/NSWidth(sliderRect);
_start = startScale*(self.maxValue-self.minValue)+self.minValue;

//计算右滑块结束值
double endScale = (NSMinX(endRect)-NSMinX(sliderRect))/NSWidth(sliderRect);
_end = endScale*(self.maxValue-self.minValue)+self.minValue;

当设定起始值与结束值时,计算左右滑块位置,即set方法,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
-(void)setStart:(double)start{
double startScale = (start-self.minValue)/(self.maxValue-self.minValue);

NSPoint tmpPoint = NSMakePoint(startScale*NSWidth(sliderRect) + NSMinX(sliderRect),0);

_start = start;
[self leftBarChange:tmpPoint];
}

-(void)setEnd:(double)end{

double endScale = (end-self.minValue)/(self.maxValue-self.minValue);

NSPoint tmpPoint = NSMakePoint(endScale*NSWidth(sliderRect)+NSMinX(sliderRect),0);

_end = end;
[self rightBarChange:tmpPoint];

}

Part V 声明、实现委托

代理协议声明如下:

1
2
3
4
@protocol MySliderDelegate<NSObject>
- (void) leftBarChanged:(MySlider *)slider;
- (void) rightBarChanged:(MySlider *)slider;
@end

实现的核心代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
//左滑块改变
if(self.delegate != NULL){
if([self.delegate respondsToSelector:@selector(leftBarChanged:)]){
[self.delegate leftBarChanged:self];
}
}
//右滑块改变
if(self.delegate != NULL){
if([self.delegate respondsToSelector:@selector(rightBarChanged:)]){
[self.delegate rightBarChanged:self];
}
}