代码仅供参考 全部在文末
语法为 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 = 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, with event: UIEvent?) {
debugPrint("------触摸开始")
if slidEable {
for touch in touches {
if touch.view == slider {
beginTouch = true
}
}
}
}
override func touchesMoved(_ touches: Set, 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, with event: UIEvent?) {
debugPrint("------触摸结束")
if beginTouch && slidEable {
if let block = slideBlock {
block(value, false)
}
beginTouch = false
lblPop.isHidden = true
}
}
}