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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,012评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,589评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,819评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,652评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,954评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,381评论 1 210
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,687评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,404评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,082评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,355评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,880评论 1 255
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,249评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,864评论 3 232
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,007评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,760评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,394评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,281评论 2 259

推荐阅读更多精彩内容

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