美文网首页ios开发
简单刻度尺的实现

简单刻度尺的实现

作者: 思考的快与慢 | 来源:发表于2017-03-30 10:52 被阅读103次

    在新的开发版本中,遇到一个这样的需要,实现一个类似刻度尺的样式,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;
    }
    
    

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

    相关文章

      网友评论

        本文标题:简单刻度尺的实现

        本文链接:https://www.haomeiwen.com/subject/bpplgttx.html