Mac 从零开始编写双向滑块控件 课件及源代码

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

效果图

Part I 双向滑块的整体结构

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

image.png

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

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

1、四个区域的初始化

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区域的代码做一层封装,其方法定义如下:

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

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

[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 实现左右滑块的移动

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

//返回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方法中,我们需要通过坐标的计算重新绘制双向滑块的四个区域,代码如下:

-(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 计算左右滑块的选中范围值

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

//计算左滑块起始值
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方法,代码如下:

-(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 声明、实现委托

代理协议声明如下:

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

实现的核心代码如下:

//左滑块改变
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];
    }
}

PS:与本文对应的视频版课程正在积极地筹备中,想获取本项目代码的小伙伴们,请点击下方的喜欢并私信我,我会一一回复给各位小伙伴。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 173,314评论 25 708
  • 1、通过CocoaPods安装项目名称项目信息 AFNetworking网络请求组件 FMDB本地数据库组件 SD...
    阳明AGI阅读 16,009评论 3 119
  • 主要是不写一下可能就忘了。大概有个40天左右,争取搞完吧,听前卫死核,做前卫死宅。 Book List: 1.《牧...
    SoObsidian阅读 285评论 3 1
  • TCP是传输控制协议,提供的是面向连接,可靠的字节流服务,当客户和服务器彼此交换数据前,必须先在双方之间建立一个T...
    牛奶红茶阅读 1,637评论 0 0
  • 如果您不爱我 十月的落叶也不会开花 妈妈的脸颊也不会憔悴 而又那么苍白 如果您不爱我 几十年的汗水换不来我长大 我...
    何晓畅导演阅读 544评论 0 49