问题引入:
最近做项目遇到一个功能,双向滑动选择器.要求有如下几点:
(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