仿UISlider,双向滑动,可控制滑动单位

问题引入:

最近做项目遇到一个功能,双向滑动选择器.要求有如下几点:

(1)滑动单位为一整个单元格,即滑块不能偏移单元格,必须至少与其中一个单元格位置是重叠的.不能停在某个单元格中间

(2)上端和下端分别有一个指示器,可以同时上下滑动,但上滑块不能比下滑块位置低,下滑块也不能比上滑块位置高.


效果如图:


期间因为时间比较紧迫,准备找个三方来直接调用的,奈何没有跟项目这个需求比较贴合的三方,想加点代码进去作修改,但找的两个三方都是代码很复杂.里面各种view,而且连个注释都没有.想来想去,有这个修改的时间自己早都做一个出来了.

具体解决步骤:

1.创建一个自定义view,根据接收到的数组创建单元格,数组有多少个元素就创建多少个单元格.每个单元格section的高度等于view的高度除以数组元素个数.

2.要能上下滑动,就要创建两个滑块,记录每次滑完的位置.上滑块topSlider,下滑块bottomSlider.

3.最关键的点:每次根据手势panGesture移动的偏移量来更新滑块的位置,这样就达到滑块随手滑动的效果了.还要注意的一点是,每次进入panGesture的方法时,需要重置手势的点,否则会出现滑块越滑越快的问题.

 //每次滑动时都应对这个点复位,否则会累加

     [panGesture setTranslation:CGPointMake(0, 0) inView:self];

4.最后还需要控制一下滑块的位置,当滑块超出最上部和最下部时,不更新滑块位置.当上滑块超出下滑块的位置时不更新位置,同理,下滑块超出上滑块时也是一样的处理方式.


总结:

代码应该不算很复杂.功能也很简单,留给有需要的人.代码里有些不合理的地方因为项目比较忙也没时间优化.基本思路就上面说的几条.理清了,大家可以按照自己意愿随意修改.

疑问:

顺便问一下,有没有好心人告诉我截图中那个云层的雷达图怎么绘制的.后台给的数据是一个二维点阵数据数组.每一个点的颜色都是一个对应的值,有蓝色,绿色,红色,透明色等等.我目前的画图方式是根据每一个点,绘制一个长宽为1的rect,颜色为fillColor.但问题是这个绘制出来的图片像素太低了,这个点阵数据是225*185的,我绘制出来的图片也只有225*185个像素点.叠加到地图上放大之后,变得很模糊,不知道有没有人知道有什么好的解决办法,或者更好的绘图思路.在此先谢过大哭

//
//  EZSegmentSliderView.m
//  jiangsutianqi
//
//  Created by 张彤 on 2018/6/5.
//  Copyright © 2018年 zhangtong.cn. All rights reserved.
//
#define kSlideWidth      (self.bounds.size.width)
#define kSliderHight     (self.bounds.size.height)
#define kSectionWidth     20


#import "EZSegmentSliderView.h"
@interface EZSegmentSliderView()
//上端滑块view
@property (nonatomic, strong) UIView *topSliderView;
//下端滑块view
@property (nonatomic, strong) UIView *bottomSliderView;



//记录上部滑块所在的位置,初始值为传进来的参数值
@property (nonatomic, assign) NSInteger topSliderIndex;
//记录下部滑块所在的位置,初始值为传进来的参数值
@property (nonatomic, assign) NSInteger bottomSliderIndex;

//每个单元格为一个section,单元格高度等于 总体高度/单元格个数
@property (nonatomic, assign) CGFloat sectionHeight;

//title数组
@property (nonatomic, strong) NSArray *dataArray;

//标示线,连接顶部滑块和底部滑块
@property (nonatomic, strong) UIView *sliderLineView;


//每次选择完毕的回调block
@property (nonatomic, copy) EZSliderBlock actionBlock;

@end

@implementation EZSegmentSliderView
- (instancetype)initWithFrame:(CGRect)frame
                 sliderTitles:(NSArray *)titleArray
            defaultFirstIndex:(CGFloat)defaultFirstIndex
             defaultLastIndex:(CGFloat)defaultLastIndex
            changedAction:(EZSliderBlock)block{
    self = [super initWithFrame:frame];
    if (self) {
    
        self.sectionHeight = kSliderHight/(titleArray.count);//设置两个滑块间的高度
        
        
        self.dataArray = titleArray;//设置数据;
        
        self.topSliderIndex = defaultFirstIndex;//设置默认下滑块指标
        
        self.bottomSliderIndex = defaultLastIndex;//设置默认上滑块指标
        
        self.actionBlock = block;
        [self setupUI];
        
        
    
    
    }
    return self;
}
-(void)setupUI{
   
  

    NSArray *imgColorArray = @[ZH_RGBAColor(255,255,255,1),
                               RGB(0,173,165),
                               RGB(193,193,255),
                               RGB(122,114,239),
                               RGB(30,38,209),
                               RGB(167,253,169),
                               RGB(0,235,0),
                               RGB(16,147,26),
                               RGB(253,245,100),
                               RGB(201,201,2),
                               RGB(141,141,0),
                               RGB(255,173,173),
                               RGB(255,100,92),
                               RGB(239,2,48),
                               RGB(213,143,255),
                               RGB(171,36,251)];
    for(int i=0;i<self.dataArray.count;i++){
       
        UIView *segView = [UIView new];
        [self addSubview:segView];
        segView.backgroundColor = imgColorArray[i];
        [segView mas_makeConstraints:^(MASConstraintMaker *make) {
            make.left.equalTo(@(0));
            make.width.equalTo(@(20));
            make.height.equalTo(@(self.sectionHeight));
            make.top.equalTo(@(i*self.sectionHeight));
        }];
        
        
        UILabel *indexLabel = [UILabel new];
        indexLabel.font = ZH_FONT_SIZE(14);
        indexLabel.textColor = kBlackColor;
        indexLabel.text = self.dataArray[i];
//        indexLabel.backgroundColor = kBlueColor;
        [self addSubview:indexLabel];
        [indexLabel mas_makeConstraints:^(MASConstraintMaker *make) {
            make.left.equalTo(segView.mas_right).offset(5);
            make.bottom.equalTo(segView.mas_bottom).offset(7);
        }];
        
        
        if(i==self.dataArray.count-1){
            
            indexLabel.hidden = YES;//标示值最后一个不显示
            
        }
        
    }
    
    [self addSubview:self.topSliderView];
    self.topSliderView.frame = CGRectMake(0, self.topSliderIndex*self.sectionHeight,kSectionWidth , self.sectionHeight);
    
    
    [self addSubview:self.bottomSliderView];
    self.bottomSliderView.frame = CGRectMake(0, self.bottomSliderIndex*self.sectionHeight,kSectionWidth , self.sectionHeight);
    
    
    
    /**
     
     标示线的位置是从上面滑块的中心点开始的,所以要对self.topSliderIndex加0.5
     标示线高度就是上下滑块的index差值乘上每个section的高度
     */
    [self addSubview:self.sliderLineView];
    self.sliderLineView.y = (self.topSliderIndex+0.5)*self.sectionHeight;
    self.sliderLineView.height = (self.bottomSliderIndex-self.topSliderIndex)*self.sectionHeight;
    
    [self addObserver:self forKeyPath:@"topSliderIndex" options:NSKeyValueObservingOptionNew context:nil];
    [self addObserver:self forKeyPath:@"bottomSliderIndex" options:NSKeyValueObservingOptionNew context:nil];
    
}


/**
 
    监听上下滑块所处的index值,每次滑动结束时,上下滑块的index值都会变化,只要完成变化,就更新标示线的位置和高度
 
 */
-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
    
    //不论是上滑块还是下滑块的index值,只要变化了,都会对标示线的位置和高度产生影响,应当及时更新标示线的frame
    if([keyPath isEqualToString:@"topSliderIndex"]){
        
        self.sliderLineView.y = (self.topSliderIndex+0.5)*self.sectionHeight;
        self.sliderLineView.height = (self.bottomSliderIndex-self.topSliderIndex)*self.sectionHeight;
        
        self.actionBlock(self.topSliderIndex, self.bottomSliderIndex);
    }
    
    if([keyPath isEqualToString:@"bottomSliderIndex"]){
        
        self.sliderLineView.y = (self.topSliderIndex+0.5)*self.sectionHeight;
        self.sliderLineView.height = (self.bottomSliderIndex-self.topSliderIndex)*self.sectionHeight;
        self.actionBlock(self.topSliderIndex, self.bottomSliderIndex);

    }
    
    
    
}
-(void)dealloc{
    
    [self removeObserver:self forKeyPath:@"topSliderIndex"];
    [self removeObserver:self forKeyPath:@"bottomSliderIndex"];
  
    
}

-(void)panFirstSelectedView:(UIPanGestureRecognizer *)panGesture{
    
    CGFloat originalY = self.topSliderView.y;//滑块初始值
    CGFloat gesturePointY = [panGesture translationInView:self.topSliderView].y;//手势移动值
    CGFloat realY = originalY+gesturePointY;//滑块初始y值和手势移动值的总和
    
    NSLog(@"初始y值:%f",originalY);
    NSLog(@"手势y值:%f",gesturePointY);
    NSLog(@"实际值:%f",realY);
  
   
    switch (panGesture.state) {
        case UIGestureRecognizerStateBegan:
            NSLog(@"触摸开始:%f",originalY);
            self.topSliderView.y = originalY;
            break;
        case UIGestureRecognizerStateChanged:{
            
            /**
             1.滑块的位置不能超过最上端,也不能超过最下端
                (1)realY的值加上一个单元格高度才等于kSliderHight,判断当前滑块是否移出view的范围需要注意下
             
             2.在有效范围内,实时更新滑块的位置,但这个位置不是最终位置,最终位置应当是单元格高度的
               整数倍(这个复位操作在手势ended中完成)
             
             */
            
            
            self.topSliderView.y = self.topSliderView.y+gesturePointY;
            
            if(realY<0){
                
                self.topSliderView.y = 0;
                
            }
            if(realY+self.sectionHeight>kSliderHight){
                
                self.topSliderView.y = kSliderHight - self.sectionHeight;
            }
            
            //当上滑块超出下滑块时,即上滑块的底部超过了下滑块的上部,这时候就不应该更新上滑块的位置了
            if((realY+self.sectionHeight)>=self.bottomSliderIndex*_sectionHeight){
                
                self.topSliderView.y = (self.bottomSliderIndex-1)*_sectionHeight;
                
            }

        }
            break;
            
        case UIGestureRecognizerStateEnded:{
            /**
             1.滑动结束时进入该代码块,对手势结束时的位置进行向上取整的做法设置index,即使向下移动了0.1,也算移动了一个方格,
             如果有需要可以自行修改.
             2.处理index完毕后,以index为基准,对滑块位置进行更新
             
            */
            
                
                self.topSliderIndex = ceilf(self.topSliderView.y/self.sectionHeight);
                self.topSliderView.y = self.topSliderIndex*self.sectionHeight;
            
            
        
            
        }
            break;
        default:
            break;
    }
    
    
    
    //每次滑动时都应对这个点复位,否则会累加
     [panGesture setTranslation:CGPointMake(0, 0) inView:self];
    
}
-(void)panLastSelectedView:(UIPanGestureRecognizer *)panGesture{

    CGFloat originalY = self.bottomSliderView.y;
    
    CGFloat gesturePointY = [panGesture translationInView:self.bottomSliderView].y;
    CGFloat realY = originalY+gesturePointY;
    
//    NSLog(@"初始y值:%f",originalY);
//    NSLog(@"手势y值:%f",gesturePointY);
//    NSLog(@"实际值:%f",realY);
    
    
    switch (panGesture.state) {
        case UIGestureRecognizerStateBegan:
//            NSLog(@"触摸开始:%f",originalY);
            /*
             触摸开始
             **/
            self.bottomSliderView.y = originalY;
            break;
        case UIGestureRecognizerStateChanged:{
            self.bottomSliderView.y = self.bottomSliderView.y+gesturePointY;
            if(realY<0){
                
                self.bottomSliderView.y = 0;
                
            }
             if(realY+self.sectionHeight>kSliderHight){
                
                self.bottomSliderView.y = kSliderHight - self.sectionHeight;
            }
            
            //当下滑块超出上滑块时,即下滑块的上部超过了上滑块的底部,这时候就不应该更新下滑块的位置了
            if((realY-_sectionHeight)<=self.topSliderIndex*_sectionHeight){
                
                self.bottomSliderView.y = (self.topSliderIndex+1)*_sectionHeight;
                
            }
        }
            break;
        case UIGestureRecognizerStateEnded:{
            self.bottomSliderIndex = ceilf(self.bottomSliderView.y/self.sectionHeight);
            self.bottomSliderView.y = self.bottomSliderIndex*self.sectionHeight;
            
           
        }
            break;
        default:
            break;
    }
    
    
    [panGesture setTranslation:CGPointMake(0, 0) inView:self];
}

-(UIView *)topSliderView{
    
    if(!_topSliderView){
        
        
        _topSliderView = [UIView new];
        UIPanGestureRecognizer *panFirstCenterImage = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(panFirstSelectedView:)];
        [_topSliderView addGestureRecognizer:panFirstCenterImage];
        _topSliderView.layer.borderColor = [UIColor redColor].CGColor;
        _topSliderView.layer.borderWidth = 2;
    }
    return _topSliderView;
}
-(UIView *)bottomSliderView{
    
    if(!_bottomSliderView){
        
        _bottomSliderView = [UIView new];
        UIPanGestureRecognizer *panLastCenterImage = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(panLastSelectedView:)];
        [_bottomSliderView addGestureRecognizer:panLastCenterImage];
        _bottomSliderView.layer.borderColor = [UIColor redColor].CGColor;
        _bottomSliderView.layer.borderWidth = 2;
        
    }
    return _bottomSliderView;
    
}
-(UIView *)sliderLineView{
    
    if(!_sliderLineView){
        
        
        _sliderLineView = [UIView new];
        _sliderLineView.width = 2;
        _sliderLineView.backgroundColor = [UIColor redColor];
        _sliderLineView.centerX = kSectionWidth/2.0;
        
        /*
            指标线的宽度和x值不变,变的是高度和y值(在panGesture手势方法中更新)
         **/
        
    }
    return _sliderLineView;
    
}
@end


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值