在 SwiftUI 中创建一个环形 Slider

news2024/11/24 1:24:33

在这里插入图片描述

文章目录

    • 前言
    • 初始化环形轮廓
    • 将进度值和拇指位置绑定
    • 添加触摸手势
    • 为不同的坐标值设置滑块位置
    • 总结

前言

Slider 控件是一种允许用户从一系列值中选择一个值的 UI 控件。在 SwiftUI 中,它通常呈现为直线上的拇指选择器。有时将这种类型的选择器呈现为一个圆圈,拇指绕着圆周移动可能会更好。本文介绍如何在 SwiftUI 中定义一个环形的 Slider。

初始化环形轮廓

ZStack中的三个圆环开始。一个灰色的圆环代表滑块的路径轮廓,一个淡红色的圆弧代表沿着圆环的进度,一个圆圈代表当前光标或拇指的位置。将滑块的范围设置为0.0到1.0,并硬编码一个直径和一个的当前位置进度 - 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)
    }
}

将进度值和拇指位置绑定

将进度变量更改为状态变量并添加默认 Slider。这个 Slider 用于修改进度值,并在圆形滑块上实现足够的代码以使拇指和进度弧响应。当前值显示在环形 Slider 的中心。

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()
        }
    }
}

添加触摸手势

DragGesture 被添加到滑块圆圈,并且使用临时文本视图显示拖动手势的当前位置。可以看到 x 和 y 坐标围绕包含环形 Slider 的位置中心的变化情况。

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()
        }
    }
}

为不同的坐标值设置滑块位置

圆形滑块上有两个表示进度的值,用于显示进度弧度的progress值和用于显示滑块光标的rotationAngle。应该只有一个属性来保存滑块进度。视图被提取到一个单独的结构中,该结构具有圆形滑块上进度的一个绑定值。

滑块的range的可选参数也是可用的。这需要对进度进行一些调整,以计算已设置的角度以及拇指在圆形滑块上位置的旋转角度。另外调用onAppear根据View出现前的进度值计算旋转角度。

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 的三种不同视图被添加到View中以测试和演示 Circular Slider 视图的不同功能。

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()
        }
    }
}

总结

本文展示了如何定义响应拖动手势的圆环滑块控件。可以设置滑块视图的大小,并且滑块按预期工作。可以向控件添加更多参数以设置颜色或圆环内显示的值的格式。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/893350.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

centos7、ky10_server(arm版、x86版)将程序做成系统服务

前提条件 提供启动脚本、停止脚本 启动脚本 生成app启动脚本 touch app_start.sh cat > app_start.sh << EOF chmod -R 777 /home/zenglg/appcd /home/zenglg/app/apache-tomcat/bin./startup.sh EOF 停止脚本 生成app.停止脚本 touch app_stop.sh cat > app_sto…

ssm学校运动会信息管理系统源码和论文

ssm学校运动会信息管理系统027 开发工具&#xff1a;idea 数据库mysql5.7 数据库链接工具&#xff1a;navcat,小海豚等 技术&#xff1a;ssm 摘 要 在当今社会上&#xff0c;体育运动越来越普及&#xff0c;参与运动会的人越来越多&#xff0c;但是目前对运动会信息管理还…

常见的Web安全漏洞有哪些,Web安全漏洞常用测试方法介绍

Web安全漏洞是指在Web应用程序中存在的可能被攻击者利用的漏洞&#xff0c;正确认识和了解这些漏洞对于Web应用程序的开发和测试至关重要。 一、常见的Web安全漏洞类型&#xff1a; 1、跨站脚本攻击(Cross-Site Scripting&#xff0c;XSS)&#xff1a;攻击者通过向Web页面注入…

C语言之extern “C“详解与使用方法

前言 在你工作过的系统里&#xff0c;不知能否看到类似下面的代码。 这好像没有什么问题&#xff0c;你应该还会想&#xff1a;“嗯⋯是啊&#xff0c;我们的代码都是这样写的&#xff0c;从来没有因此碰到过什么麻烦啊&#xff5e;”。 你说的没错&#xff0c;如果你的头文件…

如何使用Redis实现附近商家查询

导读 在日常生活中&#xff0c;我们经常能看见查询附近商家的功能。 常见的场景有&#xff0c;比如你在点外卖的时候&#xff0c;就可能需要按照距离查询附近几百米或者几公里的商家。 本文将介绍如何使用Redis实现按照距离查询附近商户的功能&#xff0c;并以SpringBoot项目…

偷偷告诉你,线上便宜的卡你就得忍受这些毛病!

很多人都觉得线上的流量卡资费便宜&#xff0c;都想随手申请一张&#xff0c;在这里&#xff0c;小编偷偷的告诉大家一下&#xff0c;线上的流量卡资费虽然便宜&#xff0c;但是缺点也有不少&#xff0c;看完你能接受吗&#xff1f; ​ 我们先说一说线上流量卡的优势&#xff1…

如何自定义IIS的页面

到C:\inetpub\wwwroot 文件夹下面有一个 iisstart.htm文件 修改它内容如下 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> <html xmlns"http://www.w3.org/1999/xhtml&…

没有放入回收站的文件如何找回?这个方法可以帮你

在我们平常使用电脑时&#xff0c;经常需要删除一些文件或文件夹。通常情况下&#xff0c;这些文件会被移动到回收站&#xff0c;以便我们在需要时可以轻松恢复。然而&#xff0c;有时候我们希望直接永久删除文件&#xff0c;而不经过回收站。不过&#xff0c;在使用这些方法之…

九耶丨阁瑞钛伦特-Spring boot与Spring cloud 之间的关系

Spring Boot和Spring Cloud是两个相互关联的项目&#xff0c;它们可以一起使用来构建微服务架构。 Spring Boot是一个用于简化Spring应用程序开发的框架&#xff0c;它提供了自动配置、快速开发的特性&#xff0c;使得开发人员可以更加轻松地创建独立的、生产级别的Spring应用程…

Azure Bastion的简单使用

什么是Azure Bastion Azure Bastion 是一个提供安全远程连接到 Azure 虚拟机&#xff08;VM&#xff09;的服务。传统上&#xff0c;访问 VM 需要使用公共 IP 或者设立 VPN 连接&#xff0c;这可能存在一些安全风险。Azure Bastion 提供了一种更安全的方式&#xff0c;它是一个…

SB-B300、CKN1262、CKN55160楔块式单向离合器

SB-A130 S8-8130 SB-A150 SB-B150 SB-B180 SB-B180 SB-B200 SB-B200 SB-B250 SB-B250 SB-B300 SB-B300 CKN1262 CKN1568 CKN2075 CKN2590 CKN30100 CKN35110 CKN40125 CKN45130 CKN40150 CKN55160 CKN60170 CKN70190 CKN80210 CKN90230 CKN系列为楔块…

jmeter实用随机字符串常用变量 生成指定位数汇总

前言 在日常接口测试、压力测试时&#xff0c;我们需要生成大量的随机变量。例如&#xff1a;姓名、手机号、随机字符串等。这时候使用Jmeter内置的一些方法的随机生成变量&#xff0c;便成了解决问题的一种方式。 一、姓名随机生成 1.引入代码 ${__RandomString(1,赵钱孙李…

【Java转Go】快速上手学习笔记(三)之基础篇二

【Java转Go】快速上手学习笔记&#xff08;二&#xff09;之基础篇一 了解了基本语法、基本数据类型这些使用&#xff0c;接下来我们来讲数组、切片、值传递、引用传递、指针类型、函数、map、结构体。 目录 数组和切片值传递、引用传递指针类型defer延迟执行函数map结构体匿名…

历时数月钻研推流/对比各种流媒体服务程序/PK总结

1 前言 大量测试下来&#xff0c;网页显示视频流实时性从高到低依次是 webrtc > ws-flv > flv > hls。播放器打开rtsp/rtmp视频流实时性由具体的播放器控制&#xff0c;比如缓存大小和缓存时间&#xff0c;是否音视频同步等。由于flv拉流同源地址最大支持6路同时播放…

docker-php扩展

生成扩展骨架 环境&#xff1a;docker-compose、php74 1.本地要有一份 php-src git clone https://github.com/php/php-src.git cd php-src git checkout PHP-7.4.52.\www\php-src\ext可以看到有一个 ext_skel.php 文件 3.通过ext_skel.php脚本创建了一个hello扩展&#xf…

不用+号算加法(位运算实现)

最近在LeetCode上刷题看到一道非常有意思的题&#xff0c;如何不用号算加法&#xff1f;我觉得挺有意思的故而分享给大家。 在不能使用 号的情况下其实很容易想到运用位运算去解决问题&#xff0c;也就是用二进制去表示十进制加法的逻辑。所以我们可以先拆分十进制加法来帮助理…

「2024」预备研究生mem-三角形内心立体几何相接与相切

一、三角形内心&立体几何相接与相切 现场推算&#xff1a; 二、练习题

花费400元,我DIY了一台全志A133平板电脑

项目作者&#xff1a;flyn 简介&#xff1a;DIY爱好者&#xff0c;在立创开源平台开源了个人的DIY项目4G手机MiniPhone以及焊接工具焊台、恒温加热台和多功能控制台。 这是一款基于全志A133处理器DIY的平板电脑&#xff0c;可运行android和linux系统。平板搭载一块7寸1024X600…

sip网络号角喇叭 sip音柱 POE供电广播音箱 ip网络防水对讲终端 sip网络功放

SV-7042TP网络号角喇叭 一、描述 SV-7042TP是我司的一款SIP网络号角喇叭&#xff0c;具有10/100M以太网接口&#xff0c;内置有一个高品质扬声器&#xff0c;将网络音源通过自带的功放和喇叭输出播放&#xff0c;可达到功率30W。SV-7042TP作为SIP系统的播放终端&#xff0c;可…

Docker:Windows container和Linux container

点击"Switch to Windows containers"菜单时&#xff1a; 提示 然后 实际上是运行&#xff1a;com.docker.admin.exe start-service