prefácio
Um controle Slider é um controle de interface do usuário que permite ao usuário selecionar um valor em um intervalo de valores. No SwiftUI, geralmente é renderizado como um seletor de polegar em uma linha reta. Às vezes, pode ser melhor apresentar esse tipo de seletor como um círculo com o polegar se movendo ao redor do círculo. Este artigo descreve como definir um Slider circular no SwiftUI.
Inicializar o perfil circular
ZStack
Comece com os três anéis nele Um anel cinza representa o contorno do caminho do controle deslizante, um arco avermelhado representa o progresso ao longo do anel e um círculo representa o cursor atual ou a posição do polegar. Defina o intervalo do controle deslizante de 0,0 a 1,0 e codifique um diâmetro e um progresso de posição atual de - 0,33.
struct CircularSliderView1: View {
let progress = 0.33
let ringDiameter = 300.0
private var rotationAngle: Angle {
return Angle(degrees: (360.0 * progress))
}
var body: some View {
VStack {
ZStack {
Circle()
.stroke(Color(hue: 0.0, saturation: 0.0, brightness: 0.9), lineWidth: 20.0)
Circle()
.trim(from: 0, to: progress)
.stroke(Color(hue: 0.0, saturation: 0.5, brightness: 0.9),
style: StrokeStyle(lineWidth: 20.0, lineCap: .round)
)
.rotationEffect(Angle(degrees: -90))
Circle()
.fill(Color.white)
.frame(width: 21, height: 21)
.offset(y: -ringDiameter / 2.0)
.rotationEffect(rotationAngle)
}
.frame(width: ringDiameter, height: ringDiameter)
Spacer()
}
.padding(80)
}
}
复制代码
Vincule o valor do progresso à posição do polegar
Altere a variável de progresso para a variável de estado e adicione o Slider padrão. Este controle deslizante é usado para modificar o valor do progresso e implementar código suficiente no controle deslizante circular para tornar o polegar e o arco de progresso responsivos. O valor atual é exibido no centro do anel deslizante.
struct CircularSliderView2: View {
@State var progress = 0.33
let ringDiameter = 300.0
private var rotationAngle: Angle {
return Angle(degrees: (360.0 * progress))
}
var body: some View {
ZStack {
Color(hue: 0.58, saturation: 0.04, brightness: 1.0)
.edgesIgnoringSafeArea(.all)
VStack {
ZStack {
Circle()
.stroke(Color(hue: 0.0, saturation: 0.0, brightness: 0.9), lineWidth: 20.0)
.overlay() {
Text("\(progress, specifier: "%.1f")")
.font(.system(size: 78, weight: .bold, design:.rounded))
}
Circle()
.trim(from: 0, to: progress)
.stroke(Color(hue: 0.0, saturation: 0.5, brightness: 0.9),
style: StrokeStyle(lineWidth: 20.0, lineCap: .round)
)
.rotationEffect(Angle(degrees: -90))
Circle()
.fill(Color.white)
.shadow(radius: 3)
.frame(width: 21, height: 21)
.offset(y: -ringDiameter / 2.0)
.rotationEffect(rotationAngle)
}
.frame(width: ringDiameter, height: ringDiameter)
VStack {
Text("Progress: \(progress, specifier: "%.1f")")
Slider(value: $progress,
in: 0...1,
minimumValueLabel: Text("0.0"),
maximumValueLabel: Text("1.0")
) {}
}
.padding(.vertical, 40)
Spacer()
}
.padding(.vertical, 40)
.padding()
}
}
}
复制代码
Adicionar gestos de toque
Um DragGesture é adicionado ao círculo do controle deslizante e uma exibição de texto temporária é usada para exibir a posição atual do gesto de arrastar. Você pode ver como as coordenadas x e y mudam em torno do centro da posição que contém o anel deslizante.
struct CircularSliderView3: View {
@State var progress = 0.33
let ringDiameter = 300.0
@State var loc = CGPoint(x: 0, y: 0)
private var rotationAngle: Angle {
return Angle(degrees: (360.0 * progress))
}
private func changeAngle(location: CGPoint) {
loc = location
}
var body: some View {
ZStack {
Color(hue: 0.58, saturation: 0.04, brightness: 1.0)
.edgesIgnoringSafeArea(.all)
VStack {
ZStack {
Circle()
.stroke(Color(hue: 0.0, saturation: 0.0, brightness: 0.9), lineWidth: 20.0)
.overlay() {
Text("\(progress, specifier: "%.1f")")
.font(.system(size: 78, weight: .bold, design:.rounded))
}
Circle()
.trim(from: 0, to: progress)
.stroke(Color(hue: 0.0, saturation: 0.5, brightness: 0.9),
style: StrokeStyle(lineWidth: 20.0, lineCap: .round)
)
.rotationEffect(Angle(degrees: -90))
Circle()
.fill(Color.blue)
.shadow(radius: 3)
.frame(width: 21, height: 21)
.offset(y: -ringDiameter / 2.0)
.rotationEffect(rotationAngle)
.gesture(
DragGesture(minimumDistance: 0.0)
.onChanged() { value in
changeAngle(location: value.location)
}
)
}
.frame(width: ringDiameter, height: ringDiameter)
Spacer().frame(height:50)
Text("Location = (\(loc.x, specifier: "%.1f"), \(loc.y, specifier: "%.1f"))")
Spacer()
}
.padding(.vertical, 40)
.padding()
}
}
}
复制代码
Defina a posição do controle deslizante para diferentes valores de coordenadas
Existem dois valores no controle deslizante circular que representam o progresso, um valor que mostra o progresso em radianos progress
e um valor que mostra o cursor do controle deslizante rotationAngle
. Deve haver apenas uma propriedade para manter o progresso do controle deslizante. A exibição é extraída em uma estrutura separada que possui um valor vinculado para o progresso no controle deslizante circular.
Parâmetros opcionais para controles deslizantes range
também estão disponíveis. Isso requer alguns ajustes no progresso para levar em conta o ângulo que foi definido e a rotação da posição do polegar no controle deslizante circular. Também chamado para calcular o ângulo de rotação onAppear
com base no View
valor do progresso antes de aparecer.
struct CircularSliderView: View {
@Binding var progress: Double
@State private var rotationAngle = Angle(degrees: 0)
private var minValue = 0.0
private var maxValue = 1.0
init(value progress: Binding<Double>, in bounds: ClosedRange<Int> = 0...1) {
self._progress = progress
self.minValue = Double(bounds.first ?? 0)
self.maxValue = Double(bounds.last ?? 1)
self.rotationAngle = Angle(degrees: progressFraction * 360.0)
}
private var progressFraction: Double {
return ((progress - minValue) / (maxValue - minValue))
}
private func changeAngle(location: CGPoint) {
// 为位置创建一个向量(在 iOS 上反转 y 坐标系统)
let vector = CGVector(dx: location.x, dy: -location.y)
// 计算向量的角度
let angleRadians = atan2(vector.dx, vector.dy)
// 将角度转换为 0 到 360 的范围(而不是负角度)
let positiveAngle = angleRadians < 0.0 ? angleRadians + (2.0 * .pi) : angleRadians
// 根据角度更新滑块进度值
progress = ((positiveAngle / (2.0 * .pi)) * (maxValue - minValue)) + minValue
rotationAngle = Angle(radians: positiveAngle)
}
var body: some View {
GeometryReader { gr in
let radius = (min(gr.size.width, gr.size.height) / 2.0) * 0.9
let sliderWidth = radius * 0.1
VStack(spacing:0) {
ZStack {
Circle()
.stroke(Color(hue: 0.0, saturation: 0.0, brightness: 0.9),
style: StrokeStyle(lineWidth: sliderWidth))
.overlay() {
Text("\(progress, specifier: "%.1f")")
.font(.system(size: radius * 0.7, weight: .bold, design:.rounded))
}
// 取消注释以显示刻度线
//Circle()
// .stroke(Color(hue: 0.0, saturation: 0.0, brightness: 0.6),
// style: StrokeStyle(lineWidth: sliderWidth * 0.75,
// dash: [2, (2 * .pi * radius)/24 - 2]))
// .rotationEffect(Angle(degrees: -90))
Circle()
.trim(from: 0, to: progressFraction)
.stroke(Color(hue: 0.0, saturation: 0.5, brightness: 0.9),
style: StrokeStyle(lineWidth: sliderWidth, lineCap: .round)
)
.rotationEffect(Angle(degrees: -90))
Circle()
.fill(Color.white)
.shadow(radius: (sliderWidth * 0.3))
.frame(width: sliderWidth, height: sliderWidth)
.offset(y: -radius)
.rotationEffect(rotationAngle)
.gesture(
DragGesture(minimumDistance: 0.0)
.onChanged() { value in
changeAngle(location: value.location)
}
)
}
.frame(width: radius * 2.0, height: radius * 2.0, alignment: .center)
.padding(radius * 0.1)
}
.onAppear {
self.rotationAngle = Angle(degrees: progressFraction * 360.0)
}
}
}
}
复制代码
CircularSliderView
Três exibições diferentes foram adicionadas à exibição para testar e demonstrar diferentes recursos da exibição do controle deslizante circular.
struct CircularSliderView5: View {
@State var progress1 = 0.75
@State var progress2 = 37.5
@State var progress3 = 7.5
var body: some View {
ZStack {
Color(hue: 0.58, saturation: 0.06, brightness: 1.0)
.edgesIgnoringSafeArea(.all)
VStack {
CircularSliderView(value: $progress1)
.frame(width:250, height: 250)
HStack {
CircularSliderView(value: $progress2, in: 1...10)
CircularSliderView(value: $progress3, in: 0...100)
}
Spacer()
}
.padding()
}
}
}
复制代码
Resumir
本文展示了如何定义响应拖动手势的圆环滑块控件。可以设置滑块视图的大小,并且滑块按预期工作。可以向控件添加更多参数以设置颜色或圆环内显示的值的格式。
本文正在参加「金石计划」