自定義有多個按鈕節點的SliderView

XDChang發表於2018-07-10

###前言

前些天看到一個設計圖,關於分期付款選擇期數的,有多個節點。它像是一個sliderView,但是sliderView實現不了多個節點按鈕。所以,我就想到了自定義sliderView。DCSliderView

設計圖如下:

期數設計圖.png

最終效果圖如下:

DCSliderView.gif

###設計思路

  1. 先新增一個底層view,然後在底層view上畫出背景layer,這裡是六個小圓,和一個細長矩形。

  2. 小圓點是可點的,所以還要建立六個btn,並新增下標題。

  3. 在底層view的上方新增一個view,充當滑動控制器。

  4. 在滑動控制器上新增拖拽手勢,並且控制滑動時,只改變控制器的X座標,Y軸保持不變。

  5. 繪製綠色layer跟隨滑動控制器而動。

  6. 處理各個按鈕的點選事件,讓滑動控制器跟綠色layer隨之改變。

  7. 處理細節,吸附功能,點亮下標題,對滑動控制器最小和最大X軸位移的控制。

  8. 設定代理,在各個方法裡觸發代理方法。

###實現相關功能

  1. 建立底層view,在view上新增各種layer;建立btn和下標題。

自定義有多個按鈕節點的SliderView

#pragma mark --- 載入所有的layer
- (void)drawWholeShape
{
    CGFloat gapX = self.frame.origin.x; //父檢視距離螢幕左邊的距離(實現各個圓之間的間距逐漸增大,我自己設定了幾個引數,大家可以根據自己的實際情況去改變圓之間的間距。不是非要按照這個來,這裡只是提供思路。)
    // 用貝塞爾函式畫出細長矩形路徑
    UIBezierPath *recPath = [UIBezierPath bezierPath];
    [recPath moveToPoint:CGPointMake(8, 4)];//上起點
    [recPath addLineToPoint:CGPointMake(8, 8)];//下起點
    [recPath addLineToPoint:CGPointMake(8+WIDTH-2*gapX, 8)];//下結束點
    [recPath addLineToPoint:CGPointMake(8+WIDTH-2*gapX, 4)];//上結束點
// 用CAShapeLayer繪製細長矩形
    CAShapeLayer *tubeShape = [[CAShapeLayer alloc]init];
    tubeShape.path = recPath.CGPath;
    tubeShape.strokeColor = [UIColor colorWithRed:224/255.0 green:224/255.0 blue:224/255.0 alpha:1].CGColor;// 外邊框顏色
    tubeShape.fillColor = [UIColor colorWithRed:224/255.0 green:224/255.0 blue:224/255.0 alpha:1].CGColor;// 內部填充顏色

     [_holeShapeView.layer addSublayer:tubeShape];
    
    NSArray *title = TITLE;
    // for 迴圈繪製六個灰色小圓跟綠色小圓,建立六個btn,下標題並新增進陣列
    for (int i = 0; i <6; i ++) {
        
        //灰色小圓
        UIBezierPath *leftSemiPath1 = [UIBezierPath bezierPath];
        
        CGPoint pointR1 = CGPointMake(12 +(_yy+_xx*i)*i, 6);
        
        [leftSemiPath1 addArcWithCenter:pointR1 radius:6 startAngle:(0.0 * M_PI) endAngle:(2.0 * M_PI) clockwise:YES];
        
        
        CAShapeLayer *leftSemiShape1 = [[CAShapeLayer alloc]init];
        
        leftSemiShape1.path = leftSemiPath1.CGPath;
        
        leftSemiShape1.strokeColor = [UIColor colorWithRed:224/255.0 green:224/255.0 blue:224/255.0 alpha:1].CGColor;
        leftSemiShape1.fillColor = [UIColor colorWithRed:224/255.0 green:224/255.0 blue:224/255.0 alpha:1].CGColor;

        [_holeShapeView.layer addSublayer:leftSemiShape1];
        
        
        // 綠色小圓
        UIBezierPath *leftSemiPath2 = [UIBezierPath bezierPath];
        
        CGPoint pointR2 = CGPointMake(12 +(_yy+_xx*i)*i, 6);
        
        [leftSemiPath2 addArcWithCenter:pointR2 radius:4 startAngle:(0.0 * M_PI) endAngle:(2.0 * M_PI) clockwise:YES];
        
        
        CAShapeLayer *leftSemiShape2 = [[CAShapeLayer alloc]init];
        
        leftSemiShape2.path = leftSemiPath2.CGPath;
        
        leftSemiShape2.strokeColor = K_CGColor;
        leftSemiShape2.fillColor = K_CGColor;

        [self.btnLayerArr addObject:leftSemiShape2];
        
        if (i==0) {
           // 將第一個綠色小圓新增到底層view上
            [_holeShapeView.layer addSublayer:leftSemiShape2];
        }
        
        float x = 4 +(_yy+_xx*i)*i;
        // 建立btn
        UIButton *stepBtn = [[UIButton alloc]initWithFrame:CGRectMake(x, -2, 14, 14)];
        
        [_btnArr addObject:stepBtn];
        [self.btnOriginXArr addObject:@(x)];
        
        stepBtn.tag = i;
        
        [stepBtn addTarget:self action:@selector(onBtnClick:) forControlEvents:UIControlEventTouchUpInside];
        [self addSubview:stepBtn];
        
        // 建立下標題
        UILabel *qiShuLabel = [[UILabel alloc]init];
        
        qiShuLabel.center = CGPointMake(x-4, 20);
        qiShuLabel.text = title[i];
        qiShuLabel.textColor = [UIColor colorWithRed:153/255.0 green:153/255.0 blue:153/255.0 alpha:1];
        qiShuLabel.font = [UIFont systemFontOfSize:12];
        
        [qiShuLabel sizeToFit];
        
        [self addSubview:qiShuLabel];
        
        [self.titleLabelArr addObject:qiShuLabel];
    }
   
}

複製程式碼

2 . 建立滑動控制器view,並新增滑動手勢。

- (void)initTargetView
{
    _targetView = [[UIImageView alloc]initWithFrame:CGRectMake(0, -6, 22, 22)];
    _targetView.image = [UIImage imageNamed:@"target"];
    
    _targetView.userInteractionEnabled = YES;
    
    UIPanGestureRecognizer *imageViewPanGesture = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(panGesture:)];
    
    [_targetView addGestureRecognizer:imageViewPanGesture];
    
    [self addSubview:_targetView];

}

複製程式碼
//在移動過程中,UIGestureRecognizerStateChanged 這個狀態會呼叫很多次,在這裡面處理綠色細長矩形的繪製,新增或刪除綠色小圓layer。

//在移動結束時,UIGestureRecognizerStateEnded 這個狀態只呼叫一次,在這裡處理最終的綠色細長矩形,綠色小圓,下標題的點亮,吸附功能。

- (void)panGesture:(UIPanGestureRecognizer *)gesture
{
    CGFloat y;
   
    switch (gesture.state) {
            
        case UIGestureRecognizerStateBegan:
        {
            
            CGRect rect = gesture.view.frame;
            y = rect.origin.y ;
        }
            break;
        case UIGestureRecognizerStateChanged:
        {
            // 獲得新增手勢的物件
            // 獲得滑動的距離  包含 x y 移動的數值
            CGPoint point  =[gesture translationInView:gesture.view];
            
            CGRect targetRect = _targetView.frame;
            
            CGFloat targetX = targetRect.origin.x;

           // 綠色的細長矩形
            [_recPath removeAllPoints];// 這個方法會呼叫很多次,每次呼叫都會繪製一條路徑,為了實現綠色路徑跟隨滑動控制器而動的效果,所有每次繪製之前都移除掉所有的點,其它地方有這樣的處理都是一個道理。
            [_recPath moveToPoint:CGPointMake(8, 5.8)];
            [_recPath addLineToPoint:CGPointMake(8, 7)];
            
            if (targetX>8) {// 避免超出最小範圍
                [_recPath addLineToPoint:CGPointMake(targetX, 7)];
                [_recPath addLineToPoint:CGPointMake(targetX, 5.8)];
            }
            
            [_recPath closePath];
            
            _tubeShape.path = _recPath.CGPath;
            [_tubeShape setNeedsDisplay];
            [self.layer addSublayer:_tubeShape];

            
            NSArray *titleArr = TITLE;
            
            for (int i = 0; i <6; i ++) {
                
                if (i!=5) {
                    // 滑動過程中新增和刪除綠色圓layer
                    if (targetX >= [self.btnOriginXArr[i]integerValue] && targetX < [_btnOriginXArr[i+1]integerValue]) {
                        // 刪除上一個綠色小圓layer
                        CAShapeLayer *layer = self.btnLayerArr[i+1];
                        if (layer) {
                            [layer removeFromSuperlayer];
                        }
                        // 新增新的綠色小圓layer
                        [_holeShapeView.layer addSublayer:self.btnLayerArr[i]];
                        [_shapeViewDelegate onShapeViewDelegateEventWithString:titleArr[i]];// 呼叫代理方法,回撥期數
                    }
                    
                }
                
            }
            //CGRectOffset是以試圖的原點為起始 移動 dx x移動距離  dy y移動距離
            
           gesture.view.frame =CGRectOffset(gesture.view.frame, point.x, y );// 改變滑動控制器的frame,只改變X,Y座標保持不變。
            
            //清空移動距離
            [gesture setTranslation:CGPointZero inView:gesture.view];
            
            
        }
            break;
        case UIGestureRecognizerStateEnded:
        {

            CGRect targetRect = _targetView.frame;
            
            CGFloat targetX = targetRect.origin.x;
            
            float btnX = [self.btnOriginXArr.lastObject integerValue];
           // targetView在第一個圓
            if (targetX<0) {
                
                targetRect.origin.x = 0;
                
                _targetView.frame = targetRect;
                
                [_shapeViewDelegate onShapeViewDelegateEventWithString:@"1期"];
                // 改變下標題顏色
                for (UILabel *label in self.titleLabelArr) {
                    
                    label.textColor = [UIColor colorWithRed:153/255.0 green:153/255.0 blue:153/255.0 alpha:1];
                }
                UILabel *firstLabel = self.titleLabelArr.firstObject;
                
                
                firstLabel.textColor = [UIColor colorWithCGColor:K_CGColor];
                break;
            }
            // targetView在最後一個圓
            if (targetX >btnX) {
                
                targetRect.origin.x = btnX;
                
                _targetView.frame = targetRect;
                [_shapeViewDelegate onShapeViewDelegateEventWithString:@"12期"];
                // 改變下標題顏色
                for (UILabel *label in self.titleLabelArr) {
                    
                    label.textColor = [UIColor colorWithRed:153/255.0 green:153/255.0 blue:153/255.0 alpha:1];
                }
                UILabel *firstLabel = self.titleLabelArr.lastObject;
                
                
                firstLabel.textColor = [UIColor colorWithCGColor:K_CGColor];
                break;
            }
            
            NSArray *titleArr = TITLE;
            // targetView 在中間各個圓
            for (int i = 0; i <6; i ++) {
                
                if (i!=5) {
                   
                    if (targetX >= [self.btnOriginXArr[i]integerValue] && targetX < [_btnOriginXArr[i]integerValue]+15.0 +_middleGap*i) {
                        
                        NSLog(@"%ld",(long)[_btnOriginXArr[i]integerValue]);
                        
                        targetRect.origin.x = [_btnOriginXArr[i]integerValue];
                        _targetView.frame = targetRect;
                        
                        [_shapeViewDelegate onShapeViewDelegateEventWithString:titleArr[i]];
                        
                        for (UILabel *label in self.titleLabelArr) {
                            
                            label.textColor = [UIColor colorWithRed:153/255.0 green:153/255.0 blue:153/255.0 alpha:1];
                        }
                        UILabel *firstLabel = self.titleLabelArr[i];
                        
                        firstLabel.textColor = [UIColor colorWithCGColor:K_CGColor];
                        
                    }
                    else if(targetX >=[_btnOriginXArr[i]integerValue]+10.0 + _middleGap*i)
                    {
                        targetRect.origin.x = [_btnOriginXArr[i+1]integerValue];
                        _targetView.frame = targetRect;
                        [_shapeViewDelegate onShapeViewDelegateEventWithString:titleArr[i+1]];
                        
                        // 改變下標題顏色
                        for (UILabel *label in self.titleLabelArr) {
                            
                            label.textColor = [UIColor colorWithRed:153/255.0 green:153/255.0 blue:153/255.0 alpha:1];
                        }
                        UILabel *firstLabel = self.titleLabelArr[i+1];
                        firstLabel.textColor = [UIColor colorWithCGColor:K_CGColor];
                    }  
                }
            }
           // 先移除貝塞爾所有的點,然後重新繪製貝塞爾路徑
            [_recPath removeAllPoints];
            [_recPath moveToPoint:CGPointMake(8, 5.8)];
            [_recPath addLineToPoint:CGPointMake(8, 7)];
            
            [_recPath addLineToPoint:CGPointMake(_targetView.frame.origin.x, 7)];
            [_recPath addLineToPoint:CGPointMake(_targetView.frame.origin.x, 5.8)];
            
            [_recPath closePath];
            _tubeShape.path = _recPath.CGPath;
            [_tubeShape setNeedsDisplay];
            [self.layer addSublayer:_tubeShape];
        }      
            break;
        default:
            break;
    }
}

複製程式碼

3 . 處理按鈕的點選事件。

- (void)onBtnClick:(UIButton *)btn
{
    NSArray *titleArr = TITLE;
    
    [_shapeViewDelegate onShapeViewDelegateEventWithString:titleArr[btn.tag]];// 回撥代理
    
// 滑動控制器frame動畫
    [UIView animateWithDuration:0.3 animations:^{
        
        NSInteger x   = [_btnOriginXArr[btn.tag]integerValue];
        CGRect rect   = _targetView.frame;
        rect.origin.x = x;
        _targetView.frame = rect;
       
    } completion:^(BOOL finished) {
        // 改變下標題顏色
        for (UILabel *label in self.titleLabelArr) {
            
            label.textColor = [UIColor colorWithRed:153/255.0 green:153/255.0 blue:153/255.0 alpha:1];
        }
        UILabel *firstLabel = self.titleLabelArr[btn.tag];
        
        firstLabel.textColor = [UIColor colorWithCGColor:K_CGColor];
    }];

// layer的動畫沒處理好,這裡通過延遲處理,實現相關功能,下次layer動畫處理好了再補充上來。
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.15 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        
//綠色小圓layer的新增和刪除
        for (CAShapeLayer *layer in self.btnLayerArr) {
            [layer removeFromSuperlayer];
        }
        for (int i = 0; i < btn.tag+1; i ++) {
            
            
            [_holeShapeView.layer addSublayer:self.btnLayerArr[i]];
            
        }
        // 先移除貝塞爾所有的點,然後重新繪製貝塞爾路徑
        [_recPath removeAllPoints];
        [_recPath moveToPoint:CGPointMake(8, 5.8)];
        [_recPath addLineToPoint:CGPointMake(8, 7)];
        
        if (_targetView.frame.origin.x > 8) {// 控制最小距離
            
            [_recPath addLineToPoint:CGPointMake(_targetView.frame.origin.x, 7)];
            [_recPath addLineToPoint:CGPointMake(_targetView.frame.origin.x, 5.8)];
            
        }
        
        [_recPath closePath];
        
        _tubeShape.path = _recPath.CGPath;
        [_tubeShape setNeedsDisplay];
        [self.layer addSublayer:_tubeShape];
        
        
    });

}

複製程式碼

###具體使用方法 下載好我的demo,在工程中匯入DCSliderView類,設定代理ShapeViewDelegate,具體程式碼如下:

 // 1.
    DCSliderView *shapeView = [[DCSliderView alloc]initWithFrame:CGRectMake(10, 60, self.view.frame.size.width -20, 30) WithLayerColor:[UIColor colorWithRed:0/255.0 green:210/255.0 blue:87/255.0 alpha:1]];
    // DCSliderView 的左右間距10 ,寬度self.view.frame.size.width -20,最好不要變。
    // 2.
    shapeView.shapeViewDelegate = self;
    
    //3.
    [self.view addSubview:shapeView];

    _qiShuLabel = [[UILabel alloc]init];
    
    _qiShuLabel.center = CGPointMake(self.view.frame.size.width/2-30, 160);
    
    _qiShuLabel.textColor = [UIColor colorWithRed:153/255.0 green:153/255.0 blue:153/255.0 alpha:1];
    
    _qiShuLabel.font = [UIFont systemFontOfSize:14];
    
    _qiShuLabel.text = @"1期" ;
    [_qiShuLabel sizeToFit];
    
    [self.view addSubview:_qiShuLabel];

// 4.代理方法
- (void)onShapeViewDelegateEventWithString:(NSString *)str
{
    _qiShuLabel.text = str ;
    [_qiShuLabel sizeToFit];
    
}
複製程式碼

######需要注意的一點是,由於各個小圓之間的間距是逐漸增大的,所以我根據螢幕的寬度設定了幾個不同的係數去適配,如果你沒有使用我程式碼中的寬度,適配就會出現問題。其實本文只是一個引子,主講設計思路,你可以按照自己的實際情況去具體設計。當然,如果你不想動手修改的話,那就得按照我設計的來。


轉載請註明出處 © XDChang

相關文章