美文网首页
Swift 自定义带气泡 可渐变色 seekbar

Swift 自定义带气泡 可渐变色 seekbar

作者: 微笑中的你 | 来源:发表于2020-12-01 20:19 被阅读0次

    代码仅供参考 全部在文末

    语法为 swift 5

    无图无真相!

    第二个版本

    需求分析

    温度在 [37 , 57] 区间 ,气泡只在该区间提示
    当滑动到 < 37 时,滑动到 OFF 处


    未实现部分:

    • 刻度条上小短线(指示线)
    • 滑块形状等
    • 气泡形状等

    因为设计不同,请自行参考修改


    本代码存在问题

    • 同一个温度值,滑块存在多个frame(滑块移动到相近的两个位置,温度值是一个, 在block返回之前判断如果相同就不执行Block)

    使用栗子

        lazy var seekBar1: LzSeekBar = {
            let style = LzSeekBarStyle(seekBarUnitPt: 8, seekBarHeight: 4, valMin: 37, valMax: 57, seekBarColor: .blue, seekBarBgColor: .gray, gradientColor: nil, gradientPosition: [0, 0.2, 0.4, 0.6, 0.8, 1], slideRadius: 8, sectionCount: 5, sectionTitles: ["OFF", "37℃", "42℃", "47℃", "52℃", "57℃"], sectionPosition: .top, showPop: true, marginLR: 30)
            let v = LzSeekBar()
            v.style = style
            return v
        }()
        lazy var seekBar2: LzSeekBar = {
            let style = LzSeekBarStyle(seekBarUnitPt: 8, seekBarHeight: 4, valMin: 37, valMax: 57, seekBarColor: .blue, seekBarBgColor: .gray, gradientColor: [.gray, AppConst.color_green, AppConst.color_orange, AppConst.color_purple, AppConst.color_red], gradientPosition: [0, 0.2, 0.4, 0.6, 0.8, 1], slideRadius: 8, sectionCount: 5, sectionTitles: ["OFF", "37℃", "42℃", "47℃", "52℃", "57℃"], sectionPosition: .top, showPop: true, marginLR: 30)
            let v = LzSeekBar(frame: .zero, style: style)
            return v
        }()
        ///
        func addSeekbar() {
    
            view.addSubview(seekBar1)
            seekBar1.frame = CGRect(x: 20, y: 100, width: AppConst.width_screen - 40, height: 50)
            seekBar1.slideBlock = { (val, sliding) in
                debugPrint("纯色SeekBar的值:\(val)")
            }
            
            view.addSubview(seekBar2)
            seekBar2.frame = CGRect(x: 20, y: 200, width: AppConst.width_screen - 40, height: 50)
            seekBar2.slideBlock = { (val, sliding) in
                debugPrint("渐变色SeekBar的值:\(val)")
    
            }
        }
    
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            addSeekbar()
        }
        
    

    类文件

    //
    //  LzSeekBar.swift
    //  xczn
    //
    //  Created by lg on 2020/10/9.
    //  Copyright © 2020 lxf. All rights reserved.
    //
    
    import UIKit
    
    
    /// 滑动回调
    typealias LzSeekBarBlock = (_ val: Int, _ sliding: Bool) -> Void
    
    
    /// 分段标记位置
    enum LzSeekBarSectionPosition {
        case top
        case bottom
    }
    
    
    /// 样式设置
    struct LzSeekBarStyle {
        var seekBarUnitPt: CGFloat = 8
        var seekBarHeight: CGFloat = 4
        var valMin = 37
        var valMax = 57
        
        var seekBarColor: UIColor = .blue
        var seekBarBgColor: UIColor = .lightGray
        
        var gradientColor: [UIColor]? = nil
        var gradientPosition: [NSNumber]? = nil
        
        var slideRadius: CGFloat = 8
        
        var sectionCount = 1
        var sectionTitles: [String]? = nil
        var sectionPosition: LzSeekBarSectionPosition = .top
        var showPop: Bool = false
        
        var marginLR: CGFloat = 30
    }
    
    
    
    /// seekbar
    class LzSeekBar: UIView {
        
        fileprivate var value: Int = 0
        fileprivate var startValue: Int = 32
        fileprivate var disInMaxAndMin: Int = 1
        fileprivate var beginTouch = false
        
        fileprivate var markLabels: Array<UILabel> = Array()
        fileprivate var startPos: CGFloat = 0
        fileprivate var endPos: CGFloat = 0
        
        fileprivate var seekBarLen: CGFloat = 0
        
        var slideBlock: LzSeekBarBlock? = nil
        /// 用来编辑是否可以滑动
        var slidEnable = false
        var style: LzSeekBarStyle! {
            didSet {
                configByStyle()
            }
        }
        
        //圆形滑块
        lazy var slider: UIView = {
            let v = UIView()
            v.backgroundColor = .white
            v.layer.borderWidth = 1
            v.layer.borderColor = UIColor(red: 0xcc/255.0, green: 0xcc/255.0, blue: 0xcc/255.0, alpha: 1).cgColor
            return v
        }()
        
        //灰色
        lazy var bgSeek: UIView = {
            let v = UIView()
            return v
        }()
        
        lazy var mainSeek: UIView = {
            let v = UIView()
            return v
        }()
        
        lazy var lblPop: UILabel = {
            let lbl = UILabel()
            lbl.frame = CGRect(origin: .zero, size: CGSize(width: 30, height: 30))
            lbl.layer.masksToBounds = true
            lbl.layer.cornerRadius = 15
            lbl.backgroundColor = .black
            lbl.textAlignment = .center
            lbl.textColor = .white
            lbl.adjustsFontSizeToFitWidth = true
            lbl.isHidden = true
            return lbl
        }()
        
        override init(frame: CGRect) {
            super.init(frame: frame)
            addViews()
        }
        
        
        convenience init(frame: CGRect, style: LzSeekBarStyle) {
            self.init(frame: frame)
            self.style = style
        }
        
        required init?(coder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
        
        
        
        fileprivate func addViews() {
            addSubview(mainSeek)
            addSubview(bgSeek)
            addSubview(slider)
            addSubview(lblPop)
        }
        
        fileprivate func configByStyle() {
        
            startPos = style.marginLR
            endPos = startPos + seekBarLen
            bgSeek.layer.cornerRadius = style.seekBarHeight/2
            bgSeek.backgroundColor = style.seekBarBgColor
            mainSeek.layer.cornerRadius = style.seekBarHeight/2
            mainSeek.backgroundColor = style.seekBarColor
            slider.layer.cornerRadius = style.slideRadius
            
            disInMaxAndMin = style.valMax - style.valMin
            seekBarLen = CGFloat(disInMaxAndMin + 1) * style.seekBarUnitPt
            
    
            if let count = style.sectionTitles?.count {
                
                //如果有分组
                assert(count-1 == style.sectionCount, "分组数和名称不一致")
                startValue = style.valMin - disInMaxAndMin/(style.sectionCount - 1)
                
                seekBarLen = CGFloat(style.valMax - startValue + 1) * style.seekBarUnitPt
                
                if (markLabels.count == 0 ) {
                    for i in 0 ..< count {
                        let t: String = style.sectionTitles![I]
                        let lbl = createMarkLabel(title: t)
                        addSubview(lbl)
                            
                        markLabels.append(lbl)
                    }
                }
    
            }
    
        }
    
        
        /// 根据名称创建label
        /// - Parameter title: 名称
        fileprivate func createMarkLabel(title: String) -> UILabel {
            
            let lbl = UILabel()
            lbl.textAlignment = .center
            lbl.text = title
            lbl.font = .systemFont(ofSize: 12)
            lbl.adjustsFontSizeToFitWidth = true
            return lbl
        }
        
        
        
        //渐变色线条
        fileprivate func addGradient() {
            guard let colors = style.gradientColor else { return  }
            var cgcolors : [CGColor] = [CGColor]()
            for color in colors {
                cgcolors.append(color.cgColor)
            }
            let gl_start = CGPoint(x: 0, y: 0)
            let gl_end = CGPoint(x: 1, y: 0)
            let gl_rect = CGRect(x: 0, y: 0, width: seekBarLen, height: style.seekBarHeight)
            
            let gl = CAGradientLayer()
            gl.colors = cgcolors
            gl.locations = style.gradientPosition
            gl.startPoint = gl_start
            gl.endPoint = gl_end
            gl.frame = gl_rect
            mainSeek.layer.insertSublayer(gl, at: 1)
        
        }
    
        fileprivate var isInit: Bool = true
        fileprivate func updateAllFrame() {
            
            if isInit  {
                
                configByStyle()
    
                mainSeek.frame = CGRect(x: startPos, y: 30, width: seekBarLen, height: style.seekBarHeight)
                bgSeek.frame = CGRect(x: startPos, y: 30, width: seekBarLen, height: style.seekBarHeight)
                slider.frame = CGRect(x: 0, y: 0, width: style.slideRadius*2, height: style.slideRadius*2)
                slider.center = CGPoint(x: startPos, y: 30 + 2)
                
                if markLabels.count > 0 {
                    let w = seekBarLen / CGFloat(style.sectionCount)
                    for i in 0 ..< markLabels.count {
                        let lbl = markLabels[I]
                        lbl.frame = CGRect(x: 0, y: 0, width: w, height: 15)
                        lbl.center = CGPoint(x: startPos + w*CGFloat(i), y: (style.sectionPosition == .top ? 15 : 45) )
                    }
                    
                }
                isInit = false
            }
    
        }
        
        /// 设置 value 值 供外部调用
        func setSlideCenter(val: Int) {
            value = val
            let centerX = centerXFromValue(val: val)
            slider.center.x = centerX
            changeSeekBgFrame()
        }
        
        
        /// 修改 灰色条位置
        fileprivate func changeSeekBgFrame() {
            let x = slider.center.x
            bgSeek.frame = CGRect(x: x, y: bgSeek.frame.origin.y, width: seekBarLen - x + startPos, height: bgSeek.frame.size.height)
        }
        
        
        /// 显示气泡
        fileprivate func showPopView() {
            if style.showPop && value >= style.valMin && value <= style.valMax {
                lblPop.text = String(value)
                lblPop.center = CGPoint(x: slider.center.x, y: 0)
                lblPop.isHidden = false
            } else {
                lblPop.isHidden = true
            }
        }
        
        /**
                通过 设置 value 计算 ceter X 值
         */
        fileprivate func centerXFromValue(val: Int) -> CGFloat {
            //(center.x - startPos) / vLen = val / (config.maxValue - startValue)
                
            let vLen = endPos - startPos
            let x = (CGFloat(val) - CGFloat(startValue))/CGFloat(style.valMax - startValue) * vLen + startPos
            return x
        }
        
        
        /**
                通过 center x 计算 value 值
         */
        fileprivate func valueFromSlidePosition() -> Int {
    
            let vPos = slider.center.x - startPos
            let v = CGFloat(style.valMax - startValue) * vPos/seekBarLen + CGFloat(startValue)
            let vInt: Int = lroundf(Float(v))
            return vInt
        }
        
        
        override func layoutSubviews() {
            super.layoutSubviews()
            
            ///重新计算尺寸
            let needH = style.seekBarHeight + 2*30
            let needW = seekBarLen + style.marginLR*2
            
            var w = self.frame.size.width
            var h = self.frame.size.height
            if needW > w {
                w = needW
            }
            if needH > h {
                h = needH
            }
            debugPrint("宽度=\(w), 高度=\(h)")
            let newSize = CGSize(width: w, height: h)
            let newRect = CGRect(origin: self.frame.origin, size: newSize)
            self.frame = newRect
            
            configByStyle()
            updateAllFrame()
            addGradient()
    
        }
        
        override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
            debugPrint("------触摸开始")
            if slidEable {
                for touch in touches {
                    if touch.view == slider {
                        beginTouch = true
                    }
                }
            }
    
        }
        
        override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
            debugPrint("------触摸移动")
    
            if beginTouch && slidEable {
                for touch in touches {
                    let loc = touch.location(in: self)
                    let preLoc = touch.previousLocation(in: self)
                    let x = loc.x - preLoc.x
    
                    if slider.center.x >= endPos && x > 0 {
                        slider.center.x = endPos
                    } else if slider.center.x <= startPos && x < 0 {
                        slider.center.x = startPos
                    } else {
                        slider.center.x += x
                    }
                    
                    let val = valueFromSlidePosition()
                    if value != val {
                        value = val
                        showPopView()
                        if let block = slideBlock {
                            block(val, true)
                        }
                    }
                    changeSeekBgFrame()
    
                }
            }
    
        }
    
        
        override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
            debugPrint("------触摸结束")
            if beginTouch && slidEable  {
                if let block = slideBlock {
                    block(value, false)
                }
                beginTouch = false
                lblPop.isHidden = true
    
            }
        }
        
    }
    
    

    相关文章

      网友评论

          本文标题:Swift 自定义带气泡 可渐变色 seekbar

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