简单刻度尺的实现

在新的开发版本中,遇到一个这样的需要,实现一个类似刻度尺的样式,UI如下:第一眼看到这个需求的时候,我的第一感觉,就是使用图形画线,在进行一番思索之后,我就开始demo阶段,首先分析这个UI有哪些部分组成:

  • 刻度尺(高低不同)
  • 选中区域
  • 刻度尺颜色
  • 选中区域颜色
  • 选中区域的刻度尺的颜色
  • 刻度尺第一和最后一个刻度尺单独处理不显示
  • 刻度尺的宽度
    ……
WechatIMG1.jpeg
  1. 第一种实现思路,首先我想到的是自定义UIView,其实设计方法很简单,头文件代码如下:
/** 选中的数值 */
@property (nonatomic, assign) CGFloat selectedValue;
/** 最小值 */
@property (nonatomic, assign) NSInteger minValue;
/** 最大值 */
@property (nonatomic, assign) NSInteger maxValue;
/** 小刻度分成几块 */
@property (nonatomic, assign) NSInteger splitNumber;
/** 主刻度长度,默认值 10.0 */
@property (nonatomic, assign) CGFloat majorScaleLength;
/** 小刻度长度,默认值 5.0 */
@property (nonatomic, assign) CGFloat minorScaleLength;
/** 刻度尺宽度 */
@property (nonatomic, assign) CGFloat scaleWidth;
/** 指示条颜色 */
@property (nonatomic, strong) UIColor *indicatorColor;
/** 背景颜色 */
@property (nonatomic, strong) UIColor *rulerBackgroundColor;
/** 指示条的颜色 */
@property (nonatomic, strong) UIColor *rulerColor;
/** 选中的刻度尺的颜色 */
@property (nonatomic, strong) UIColor *selectedRuleColor;

这些就是暴露给使用者使用的属性,当然有些业务需要在里面,有些属性我就直接在内部写死了,当然也是可以抽取出来的。.m文件的实现也很简单:

#import "YCRulerView.h"

/** 主刻度长度默认值 */
static CGFloat const kMajorScaleDefaultLength = 13.0;
/** 小刻度长度默认值 */
static CGFloat const kMinorScaleDefaultLength = 5.0;

@interface YCRulerView ()

/** 指示器的view */
@property (nonatomic, strong) UIView *indicatorView;

@end

@implementation YCRulerView

- (instancetype)initWithFrame:(CGRect)frame {
    if (self = [super initWithFrame:frame]) {
        [self setupUI];
    }
    return self;
}

- (instancetype)initWithCoder:(NSCoder *)aDecoder {
    if (self = [super initWithCoder:aDecoder]) {
        [self setupUI];
    }
    return self;
}

- (void)layoutSubviews {
    [super layoutSubviews];
    [self reloadRuler];
}

- (void)setupUI {
    self.indicatorView = [[UIView alloc] init];
    [self addSubview:self.indicatorView];
}

- (void)reloadRuler {
    
    self.backgroundColor = self.rulerBackgroundColor;
    
    // 每一条刻度线之前的间距
    CGFloat space = (self.frame.size.width - self.maxValue * self.scaleWidth) / self.maxValue;
    for (int i = 1; i <= _maxValue; i ++) {
        CGFloat viewX = space + (i - 1) * (self.scaleWidth + space);
        UIView *lineView = [[UIView alloc] init];
        
        if (i <= self.selectedValue) {
            lineView.backgroundColor = self.selectedRuleColor;
        } else {
            lineView.backgroundColor = self.rulerColor;
        }
        if (i == 0) {
            lineView.frame = CGRectZero;
        } else if (i == _maxValue) {
            lineView.frame = CGRectZero;
        } else {
            if (i % self.splitNumber == 0) {
                lineView.frame = CGRectMake(viewX, self.frame.size.height - self.majorScaleLength, self.scaleWidth, self.majorScaleLength);
            } else {
                lineView.frame = CGRectMake(viewX, self.frame.size.height - self.minorScaleLength, self.scaleWidth, self.minorScaleLength);
            }
        }
        [self addSubview:lineView];
    }
    self.indicatorView.backgroundColor = self.indicatorColor;
    self.indicatorView.frame = CGRectMake(0, 0, self.frame.size.width * self.selectedValue / self.maxValue, self.frame.size.height);
}

#pragma mark - 属性默认值

- (CGFloat)majorScaleLength {
    if (_majorScaleLength <= 0) {
        _majorScaleLength = kMajorScaleDefaultLength;
    }
    return _majorScaleLength;
}

- (CGFloat)minorScaleLength {
    if (_minorScaleLength <= 0) {
        _minorScaleLength = kMinorScaleDefaultLength;
    }
    return _minorScaleLength;
}

- (UIColor *)indicatorColor {
    if (_indicatorColor == nil) {
        _indicatorColor = [UIColor colorWithRed:251.0/255.0 green:207.0/255.0 blue:0.0/255.0 alpha:1.0];
    }
    return _indicatorColor;
}

- (UIColor *)rulerBackgroundColor {
    
    if (_rulerBackgroundColor == nil) {
        _rulerBackgroundColor = [UIColor colorWithRed:243.0/255.0 green:243.0/255.0 blue:243.0/255.0 alpha:1.0];;
    }
    return _rulerBackgroundColor;
}

- (UIColor *)rulerColor {
    if (_rulerColor == nil) {
        _rulerColor = [UIColor colorWithRed:232.0/255.0 green:232.0/255.0 blue:232.0/255.0 alpha:1.0];
    }
    return _rulerColor;
}

- (UIColor *)selectedRuleColor {
    if (_selectedRuleColor == nil) {
        _selectedRuleColor = [UIColor colorWithRed:246.0/255.0 green:193.0/255.0 blue:21.0/255.0 alpha:1.0];
    }
    return _selectedRuleColor;
}

- (NSInteger)splitNumber {
    if (_splitNumber == 0) {
        _splitNumber = 3;
    }
    return _splitNumber;
}

- (CGFloat)scaleWidth {
    if (_scaleWidth == 0) {
        _scaleWidth = 2;
    }
    return _scaleWidth;
}

@end

下面是基本的使用,我只是选择几个属性进行设置,使用姿势如下:

    YCRulerView *rulerView = [[YCRulerView alloc] initWithFrame:rulerFrame];
    UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:rulerView.bounds
                                                   byRoundingCorners:corner
                                                         cornerRadii:CGSizeMake(10.f, 10.f)];
    CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
    maskLayer.frame = rulerView.bounds;
    maskLayer.path = maskPath.CGPath;
    rulerView.layer.mask = maskLayer;
    rulerView.backgroundColor = rulerBackgroundColor;
    // 最大值
    rulerView.maxValue = maxValue;
    // 设置默认值
    rulerView.selectedValue = selectedValue;
    // 指示块的颜色
    rulerView.indicatorColor = indicatorColor;
    // 刻度尺的颜色
    rulerView.rulerColor = rulerColor;
    // 选中刻度尺的颜色
    rulerView.selectedRuleColor = selectedRulerColor;
    [view addSubview:rulerView];
  1. 第二种实现思路,以上这种实现方法使用UIView实现的,在实现完这一套之后,我觉得不是太好,如果内部的刻度线比较多的时候,如果还是采用创建View的方法,就会造成内存的极大消耗,因此我在这个基础之上,又实现了另外一套方法,当然这套方法实现的不是很好,还存在很大的问题,先记录一下,后期完成之后,在更新一下这篇文章。
    首先看一下头文件的部分,头文件和上面实现方法头文件差不多:
/** 选中的数值 */
@property (nonatomic, assign)  CGFloat selectedValue;
/** 最小值 */
@property (nonatomic, assign)  NSInteger minValue;
/** 最大值 */
@property (nonatomic, assign)  NSInteger maxValue;
/** 步长 */
@property (nonatomic, assign)  NSInteger valueStep;
/** 小刻度分成几块 */
@property (nonatomic, assign) NSInteger splitNumber;
/** 主刻度长度,默认值 25.0 */
@property (nonatomic, assign) CGFloat majorScaleLength;
/** 中间刻度长度,默认值 20.0 */
@property (nonatomic, assign) CGFloat middleScaleLength;
/** 小刻度长度,默认值 10.0 */
@property (nonatomic, assign) CGFloat minorScaleLength;
/** 指示条颜色 */
@property (nonatomic, strong) UIColor *indicatorColor;
/** 背景颜色 */
@property (nonatomic, strong) UIColor *rulerBackgroundColor;
/** 指示条的颜色 */
@property (nonatomic, strong) UIColor *rulerColor;
/** 是否显示刻度尺上的数值 */
@property (nonatomic, assign) BOOL showRulerNumber;

.m文件的实现如下:

#import "YCRuler.h"

/** 主刻度长度默认值 */
static CGFloat const kMajorScaleDefaultLength = 20.0;
/** 中间刻度长度默认值 */
static CGFloat const kMiddleScaleDefaultLength = 20.0;
/** 小刻度长度默认值 */
static CGFloat const kMinorScaleDefaultLength = 10.0;

@interface YCRuler ()

/** 小刻度间距 */
@property (nonatomic, assign) CGFloat minorScaleSpacing;
/** 刻度尺 */
@property (nonatomic, strong) UIImageView *rulerImageView;
/** 指示器 */
@property (nonatomic, strong) UIView *indicatorView;

@end

@implementation YCRuler

- (instancetype)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        [self setupUI];
    }
    return self;
}

- (instancetype)initWithCoder:(NSCoder *)coder {
    self = [super initWithCoder:coder];
    if (self) {
        [self setupUI];
    }
    return self;
}

- (void)layoutSubviews {
    [super layoutSubviews];
    if (_rulerImageView.image == nil) {
        [self reloadRuler];
    }
}

#pragma mark - 设置属性
- (void)setSelectedValue:(CGFloat)selectedValue {
    
    if (selectedValue < _minValue || selectedValue > _maxValue || _valueStep <= 0) {
        return;
    }
    _selectedValue = selectedValue;
    _indicatorView.backgroundColor = self.indicatorColor;
    _indicatorView.alpha = 0.6;
    _indicatorView.frame = CGRectMake(0, 0, (self.frame.size.width * self.selectedValue / self.maxValue) , self.frame.size.height);
}

#pragma mark - 绘制标尺相关方法
/**
 * 刷新标尺
 */
- (void)reloadRuler {
    UIImage *image = [self rulerImage];
    if (image == nil) {
        return;
    }
    _rulerImageView.image = image;
    [_rulerImageView sizeToFit];
    _rulerImageView.frame = self.bounds;
    // 更新初始值
    self.selectedValue = _selectedValue;
}

//  生成标尺图像
- (UIImage *)rulerImage {
    
    // 1. 常数计算
    CGFloat steps = [self stepsWithValue:_maxValue];
    if (steps == 0) {
        return nil;
    }
    // 水平方向绘制图像的大小
    CGSize textSize = [self maxValueTextSize];
    CGFloat height = self.majorScaleLength + textSize.height + 2 * self.minorScaleSpacing;
    CGFloat startX = textSize.width - 5;
    CGRect rect = CGRectMake(0, 0, steps * self.minorScaleSpacing + 2 * startX, height);
    
    // 2. 绘制图像
    UIGraphicsBeginImageContextWithOptions(rect.size, NO, 0);
    UIBezierPath *path = [UIBezierPath bezierPath];
    
    for (NSInteger i = _minValue; i <= _maxValue; i += _valueStep) {
        // 绘制主刻度
        CGFloat x = (i - _minValue) / _valueStep * self.minorScaleSpacing * self.splitNumber + startX;
        
        if (i == _minValue) {
            [path moveToPoint:CGPointMake(0, 0)];
            [path addLineToPoint:CGPointMake(0, 0)];
        } else if (i == _maxValue) {
            break;
        } else {
            [path moveToPoint:CGPointMake(x, height)];
            [path addLineToPoint:CGPointMake(x, height - self.majorScaleLength - self.frame.size.height * 2 / 3)];
        }
        // 绘制小刻度线
        for (NSInteger j = 1; j < self.splitNumber; j++) {
            CGFloat scaleX = x + j * self.minorScaleSpacing;
            CGFloat scaleY = 0;
            [path moveToPoint:CGPointMake(scaleX, height)];
            if (self.splitNumber == 3) {
                scaleY = height - self.minorScaleLength - 10;
            } else {
                scaleY = height - ((j == self.splitNumber / 2) ? self.middleScaleLength : self.minorScaleLength);
            }
            [path addLineToPoint:CGPointMake(scaleX, scaleY)];
        }
    }
    
    [self.rulerBackgroundColor set];
    [path stroke];
    
    // 2> 绘制刻度值
    NSDictionary *strAttributes = [self scaleTextAttributes];
    
    for (NSInteger i = _minValue; i <= _maxValue; i += _valueStep) {
        NSString *str = @(i).description;
        if (i == _minValue) {
            [str drawInRect:CGRectZero withAttributes:strAttributes];
        } else if (i == _maxValue) {
            [str drawInRect:CGRectZero withAttributes:strAttributes];
        } else {
            CGRect strRect = [str boundingRectWithSize:CGSizeMake(MAXFLOAT, MAXFLOAT)
                                               options:NSStringDrawingUsesLineFragmentOrigin
                                            attributes:strAttributes
                                               context:nil];
            strRect.origin.x = (i - _minValue) / _valueStep * self.minorScaleSpacing * self.splitNumber + startX - strRect.size.width * 0.5;
            strRect.origin.y = 8;
            strRect.size.width = 20;
            strRect.size.height = 20;
            if (self.showRulerNumber) {
                [str drawInRect:strRect withAttributes:strAttributes];
            }
        }
    }
    UIImage *result = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return result;
}

// 计算最小值和指定 value 之间的步长,即:绘制刻度的总数量
- (CGFloat)stepsWithValue:(CGFloat)value {
    
    if (_minValue >= value || _valueStep <= 0) {
        return 0;
    }
    return (value - _minValue) / _valueStep * self.splitNumber;
}

// 以水平绘制方向计算 `最大数值的文字` 尺寸
- (CGSize)maxValueTextSize {
    
    NSString *scaleText = @(self.maxValue).description;
    CGSize size = [scaleText boundingRectWithSize:CGSizeMake(MAXFLOAT, MAXFLOAT)
                                          options:NSStringDrawingUsesLineFragmentOrigin
                                       attributes:[self scaleTextAttributes]
                                          context:nil].size;
    
    return CGSizeMake(floor(size.width), floor(size.height));
}

// 文本属性字典
- (NSDictionary *)scaleTextAttributes {
    
    CGFloat fontSize = 10 * [UIScreen mainScreen].scale * 0.8;
    
    return @{NSForegroundColorAttributeName: [UIColor lightGrayColor],
             NSFontAttributeName: [UIFont boldSystemFontOfSize:fontSize]};
}

#pragma mark - 设置界面
- (void)setupUI {

    // 标尺图像
    _rulerImageView = [[UIImageView alloc] init];
    [self addSubview:_rulerImageView];
    
    // 指示器视图
    _indicatorView = [[UIView alloc] init];
    [self addSubview:_indicatorView];
}

#pragma mark - 属性默认值
- (CGFloat)minorScaleSpacing {
    if (_minorScaleSpacing <= 0) {
        _minorScaleSpacing = self.frame.size.width /(self.maxValue * self.splitNumber);
    }
    return _minorScaleSpacing;
}

- (CGFloat)majorScaleLength {
    if (_majorScaleLength <= 0) {
        _majorScaleLength = kMajorScaleDefaultLength;
    }
    return _majorScaleLength;
}

- (CGFloat)middleScaleLength {
    if (_middleScaleLength <= 0) {
        _middleScaleLength = kMiddleScaleDefaultLength;
    }
    return _middleScaleLength;
}

- (CGFloat)minorScaleLength {
    if (_minorScaleLength <= 0) {
        _minorScaleLength = kMinorScaleDefaultLength;
    }
    return _minorScaleLength;
}

- (UIColor *)indicatorColor {
    if (_indicatorColor == nil) {
        _indicatorColor = [UIColor orangeColor];
    }
    return _indicatorColor;
}

- (UIColor *)rulerBackgroundColor {
    
    if (_rulerBackgroundColor == nil) {
        _rulerBackgroundColor = [UIColor lightGrayColor];
    }
    return _rulerBackgroundColor;
}

- (NSInteger)splitNumber {
    if (_splitNumber == 0) {
        _splitNumber = 3;
    }
    return _splitNumber;
}

使用姿势和第一种一样,但是第二种的灵活度还不是很好,在设置一个不合法的值的时候,显示会有问题, 这个我正在排查,等解决之后,我会更新一下这份代码。

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

推荐阅读更多精彩内容