TL;DR
简而言之,没有UISlider 的简单 API 可以重现此行为。
前提条件:根据问题,我只是演示如何使拇指显示为透明而不显示轨迹。我没有尝试完全重新创建相机的滑块。
理想的解决方案
理想的解决方案是创建最小和最大轨道图像,利用cap insets 来防止轨道的一部分被填充。我说这是理想的,因为它简单且相对高效。不幸的是,UISlider 做了两件事阻止我们使用这种方法。
-
最大轨道图像实际上与整个轨道的宽度相同,但它包含在 clipsToBounds 设置为 true 的视图中。最大轨道的超级视图已调整大小并剪辑最大轨道,以确保它在拇指的前导侧永远不可见。
在整个层次结构上禁用clipsToBounds 让我们在视图调试器中看到这一点。
-
最小轨道UIImageView 被调整大小,但是当一个人在UISlider 上调用setMinimumTrackImage 时,它实际上会创建一个不同的大小调整图像。这个需要一段时间才能弄清楚,但可以通过检查内存地址来证明。
替代解决方案:更改图像以匹配值
根据我从最初调查中学到的知识,我们可以随着值的变化生成图像。这不是最有效的方法,因为它需要一遍又一遍地创建许多图像。但是,它符合原始问题的参数。请注意,您只能使用“连续”滑块有效地做到这一点,因为它依赖于值更新。从好的方面来说,这可以轻松扩展以绘制更有趣的轨迹图像(如相机中的图像)。
这是结果,我把拇指做得很大,这样你就可以很容易地看到它是透明的。
课程如下:
class TransparentThumbSlider: UISlider {
let thumbWidth: CGFloat = 40
override init(frame: CGRect) {
super.init(frame: frame)
configure()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
configure()
}
func configure() {
setThumbImage(TransparentThumbSlider.thumbImage(diameter: thumbWidth, color: .blue), for: .normal)
addTarget(self, action: #selector(updateTrackImages), for: .valueChanged)
updateTrackImages()
}
static func thumbImage(diameter: CGFloat, color: UIColor) -> UIImage {
let strokeWidth: CGFloat = 4
let halfStrokeWidth = strokeWidth / 2
let totalRect = CGRect(x: 0, y: 0, width: diameter, height: diameter)
let strokeRect = CGRect(x: halfStrokeWidth,
y: halfStrokeWidth,
width: diameter - strokeWidth,
height: diameter - strokeWidth)
let renderer = UIGraphicsImageRenderer(size: totalRect.size)
let image = renderer.image { context in
context.cgContext.setStrokeColor(color.cgColor)
context.cgContext.setLineWidth(strokeWidth)
context.cgContext.strokeEllipse(in: strokeRect)
}
return image
}
static func trackImage(colored color: UIColor,
thumbWidth: CGFloat,
trackWidth: CGFloat,
value: CGFloat,
flipped: Bool) -> UIImage {
let fraction = flipped ? 1 - value : value
let adjustableWidth = (trackWidth - thumbWidth)
let halfThumbWidth = thumbWidth / 2
let fudgeFactor: CGFloat = 2
var totalRect: CGRect
var coloredRect: CGRect
if flipped {
totalRect = CGRect(x: 0, y: 0, width: trackWidth, height: 2)
coloredRect = CGRect(
x: adjustableWidth * value + thumbWidth - fudgeFactor,
y: 0,
width: adjustableWidth * fraction + (halfThumbWidth - fudgeFactor),
height: 2)
} else {
totalRect = CGRect(x: 0, y: 0, width: adjustableWidth * fraction + halfThumbWidth, height: 2)
coloredRect = CGRect(x: 0, y: 0, width: adjustableWidth * fraction, height: 2)
}
let renderer = UIGraphicsImageRenderer(size: totalRect.size)
let image = renderer.image { context in
context.cgContext.setFillColor(color.cgColor)
context.fill(coloredRect)
}
return image
}
@objc func updateTrackImages() {
let trackWidth = trackRect(forBounds: bounds).width
let minimumImage = TransparentThumbSlider.trackImage(
colored: .green,
thumbWidth: thumbWidth,
trackWidth: trackWidth,
value: CGFloat(value),
flipped: false)
setMinimumTrackImage(minimumImage, for: .normal)
let maximumImage = TransparentThumbSlider.trackImage(
colored: .yellow,
thumbWidth: thumbWidth,
trackWidth: trackWidth,
value: CGFloat(value),
flipped: true)
setMaximumTrackImage(maximumImage, for: .normal)
}
var previousBounds: CGRect = .zero
override func layoutSubviews() {
super.layoutSubviews()
// Prevent layout loop
if previousBounds != bounds {
previousBounds = bounds
updateTrackImages()
}
}
}