SwiftUI五视图动画和转场

代码下载

使用SwiftUI可以把视图状态的改变转成动画过程,SwiftUI会处理所有复杂的动画细节。在这篇中,会给跟踪用户徒步的图表视图添加动画,使用animation(_:)修改器给一个视图添加动画效果非常容易。

下载起步项目并跟着本篇教程一步步实践,或者查看本篇完成状态时的工程代码去学习,项目文件。

添加 Hiking 数据到应用程序

在添加动画之前,需要一些东西来做动画。在本节中,将导入和建模 Hiking 数据,然后添加一些预构建的视图,以便在图中静态地显示该数据。
请添加图片描述

1、从下载文件的“Resources”文件夹将 hikeData.json 文件拖动放入项目。在单击 Finish 之前,请确保选择 “Copy items if need”。

2、新建 Hike.swift 文件,与Landmark结构体一样,Hike结构体也遵守Codable协议,并且具有与相应数据文件中的键匹配的属性:

import Foundationstruct Hike: Codable, Hashable, Identifiable {var id: Intvar name: Stringvar distance: Doublevar difficulty: Intvar observations: [Observation]static var formatter = LengthFormatter()var distanceText: String {Hike.formatter.string(fromValue: distance, unit: .kilometer)}struct Observation: Codable, Hashable {var distanceFromStart: Doublevar elevation: Range<Double>var pace: Range<Double>var heartRate: Range<Double>}
}

3、新建 ModelData.swift 文件:

import Foundation@Observable
class ModelData {var hikes: [Hike] = load("hikeData.json")
}func load<T: Decodable>(_ filename: String) -> T {guard let path = Bundle.main.url(forResource: filename, withExtension: nil),let data = try? Data(contentsOf: path),let result = try? JSONDecoder().decode(T.self, from: data) else {fatalError("数据加载失败!")}return result
}

4、将已下载文件的Resources文件夹中的hike文件夹拖到项目中。在单击Finish之前,请确保选择“Copy items if need”和“Create groups”。熟悉这些新的视图,它们一起工作来显示加载到模型中的 hike 数据。

5、在HikeView.swift中,打开实时预览,体验一下图表的打开和隐藏,此时的状态改变时是没有添加动画效果的。在本篇的实践中,保持实时预览一直打开,每一步修改的效果就可以实时的看到。

给每个视图单独添加动画

在视图上使用animation(_:)修改器时,SwiftUI会在视图的任何可进行动画的属性发生改变时产生对应的动画效果。视图的颜色、不透明度、旋转角度、大小及一些其它属性都是可进行动画的。
请添加图片描述

1、在HikeView.swift中,给显示/隐藏切换的箭头按钮添加旋转动画,会发现现在按钮点击时的旋转有一个动画过渡的效果了。当视图从隐藏到展示时,让切换按钮变大1.5倍,把动画的类型从easeInOut改为spring()。SwiftUI包含一些预设或可自定义的动画类型,像弹簧(spring)动画和类型液体(fluid)动画类型。可以调整动画开始前的等待时长、动画的速度也可以指定让动画循环重复的进行:

struct HikeView: View {var hike: Hike@State private var showDetail = falsevar body: some View {VStack {HStack {HikeGraph(hike: hike, path: \.elevation).frame(width: 50, height: 30)VStack(alignment: .leading) {Text(hike.name).font(.headline)Text(hike.distanceText)}Spacer()Button {showDetail.toggle()} label: {Label("Graph", systemImage: "chevron.right.circle").labelStyle(.iconOnly).imageScale(.large).rotationEffect(.degrees(showDetail ? 90 : 0)).scaleEffect(showDetail ? 1.5 : 1).padding().animation(.spring())}}if showDetail {HikeDetail(hike: hike)}}}
}

2、如果只想让按钮具有缩放动画而不进行旋转动画,可以在scaleEffect前面添加animation(nil)来实现。可以在这里做一些实验,如果把其它的一些动画效果结合在一起,会怎么样:

struct HikeView: View {var hike: Hike@State private var showDetail = falsevar body: some View {VStack {HStack {HikeGraph(hike: hike, path: \.elevation).frame(width: 50, height: 30)VStack(alignment: .leading) {Text(hike.name).font(.headline)Text(hike.distanceText)}Spacer()Button {showDetail.toggle()} label: {Label("Graph", systemImage: "chevron.right.circle").labelStyle(.iconOnly).imageScale(.large).rotationEffect(.degrees(showDetail ? 90 : 0)).animation(nil).scaleEffect(showDetail ? 1.5 : 1).padding().animation(.spring())}}if showDetail {HikeDetail(hike: hike)}}}
}

3、进行下一节之前,把本节中添加的animation(_:)修改器都去掉。

把视图的状态改态转化成动画效果

已经学会了给单个视图添加动画的方法,现在可以学习怎么在视图的状态发生改变时添加动画效果。当用户点击按钮时会切换showDetail状态的值,在视图变化过程中添加动画效果。
请添加图片描述

1、把showDetail.toggle()包裹在withAnimation函数调用块中。showDetail的改变影响了视图HikeDetail和详情切换按钮,在显示/隐藏详情的过程中都有了过滤动画效果。

2、放慢动画速度,可以观察SwiftUI动画在被中断下是怎么运作的。给withAnimation传入一个时长4秒的基本动画参数.easeInOut(duration:4),可以指定动画过程时长,给withAnimation传入的动画参数与.animation(_:)修改器可用参数一致。

struct HikeView: View {var hike: Hike@State private var showDetail = falsevar body: some View {VStack {HStack {HikeGraph(hike: hike, path: \.elevation).frame(width: 50, height: 30)VStack(alignment: .leading) {Text(hike.name).font(.headline)Text(hike.distanceText)}Spacer()Button {withAnimation(.easeInOut(duration: 4)) {showDetail.toggle()}} label: {Label("Graph", systemImage: "chevron.right.circle").labelStyle(.iconOnly).imageScale(.large).rotationEffect(.degrees(showDetail ? 90 : 0)).scaleEffect(showDetail ? 1.5 : 1).padding()}}if showDetail {HikeDetail(hike: hike)}}}
}

3、在动画过程进行中点击按钮切换视图状态,查看对应的动画被中断时的效果。进行下一节之前,把动画时长参数(.easeInOut(duration: 4))去掉,让动画不再缓慢进行。

定制视图转场动画

默值情况下,视图离屏和入屏时的动画效果是渐隐/渐现, 这个默认的转场效果可以使用transition(_:)修改器进行定制。

1、给HikeView视图添加transition(_:)修改器,并定制转场参数为.slide,转场动画为滑入/滑出:

struct HikeView: View {var hike: Hike@State private var showDetail = falsevar body: some View {VStack {HStack {HikeGraph(hike: hike, path: \.elevation).frame(width: 50, height: 30)VStack(alignment: .leading) {Text(hike.name).font(.headline)Text(hike.distanceText)}Spacer()Button {withAnimation {showDetail.toggle()}} label: {Label("Graph", systemImage: "chevron.right.circle").labelStyle(.iconOnly).imageScale(.large).rotationEffect(.degrees(showDetail ? 90 : 0)).scaleEffect(showDetail ? 1.5 : 1).padding()}}if showDetail {HikeDetail(hike: hike).transition(.slide)}}}
}

2、可以把滑入/滑出这种转场动画封装起来,方便其它视图复用同样的转场效果:

extension AnyTransition {static var moveAndFade: AnyTransition {.slide}
}struct HikeView: View {var hike: Hike@State private var showDetail = falsevar body: some View {VStack {HStack {HikeGraph(hike: hike, path: \.elevation).frame(width: 50, height: 30)VStack(alignment: .leading) {Text(hike.name).font(.headline)Text(hike.distanceText)}Spacer()Button {withAnimation {showDetail.toggle()}} label: {Label("Graph", systemImage: "chevron.right.circle").labelStyle(.iconOnly).imageScale(.large).rotationEffect(.degrees(showDetail ? 90 : 0)).scaleEffect(showDetail ? 1.5 : 1).padding()}}if showDetail {HikeDetail(hike: hike).transition(AnyTransition.moveAndFade)}}}
}

3、在moveAndFade转场效果的定义中使用move(edge:),让滑入/滑出从屏幕的同一边进行:

extension AnyTransition {static var moveAndFade: AnyTransition {.move(edge: .trailing)}
}

4、使用asymmetric(insertion:removal:)修改器来定制视图显示/消失时的转场动画效果:

extension AnyTransition {static var moveAndFade: AnyTransition {let insertion = self.move(edge: .trailing).combined(with: .opacity)let removal = self.scale.combined(with: .opacity)return self.asymmetric(insertion: insertion, removal: removal)}
}

组合复杂的动画效果

点击图表下面的三个按钮,会在三个不同的数据集间进行切换并展示。本节中会使用组合动画,让图表在不同数据集间切换时的转换动画流畅自然。
请添加图片描述
1、把showDetail的默认值改为true,并把HikeView的预览模式视图固定在画布上。这样可以在编辑其它文件时,依然看到动画效果的变化。

2、在HikeGraph.swift中定义了一个新的波动动画,并把它与滑入/滑出动画一起应用到图表视图上:

extension Animation {static func ripple() -> Animation {Animation.default}
}struct HikeGraph: View {var hike: Hikevar path: KeyPath<Hike.Observation, Range<Double>>var color: Color {switch path {case \.elevation:return .graycase \.heartRate:return Color(hue: 0, saturation: 0.5, brightness: 0.7)case \.pace:return Color(hue: 0.7, saturation: 0.4, brightness: 0.7)default:return .black}}var body: some View {let data = hike.observationslet overallRange = rangeOfRanges(data.lazy.map { $0[keyPath: path] })let maxMagnitude = data.map { magnitude(of: $0[keyPath: path]) }.max()!let heightRatio = 1 - CGFloat(maxMagnitude / magnitude(of: overallRange))return GeometryReader { proxy inHStack(alignment: .bottom, spacing: proxy.size.width / 120) {ForEach(Array(data.enumerated()), id: \.offset) { index, observation inGraphCapsule(index: index,color: color,height: proxy.size.height,range: observation[keyPath: path],overallRange: overallRange).animation(.ripple())}.offset(x: 0, y: proxy.size.height * heightRatio)}}}
}

3、把动画切换为弹簧动画(spring),并设置弹簧阻尼系数为0.5,动画过程中产生了逐渐回弹效果:

extension Animation {static func ripple() -> Animation {Animation.spring(dampingFraction: 0.5)}
}

4、加速弹簧动画的执行速度,缩短切换图表的时间:

extension Animation {static func ripple() -> Animation {Animation.spring(dampingFraction: 0.5).speed(2)}
}

5、以当条形在图表中的位置为参数,添加延迟效果,图表中的每个条形会顺序动起来:

extension Animation {static func ripple(index: Int) -> Animation {Animation.spring(dampingFraction: 0.5).speed(2).delay(Double(index)*0.03)}
}struct HikeGraph: View {var hike: Hikevar path: KeyPath<Hike.Observation, Range<Double>>var color: Color {switch path {case \.elevation:return .graycase \.heartRate:return Color(hue: 0, saturation: 0.5, brightness: 0.7)case \.pace:return Color(hue: 0.7, saturation: 0.4, brightness: 0.7)default:return .black}}var body: some View {let data = hike.observationslet overallRange = rangeOfRanges(data.lazy.map { $0[keyPath: path] })let maxMagnitude = data.map { magnitude(of: $0[keyPath: path]) }.max()!let heightRatio = 1 - CGFloat(maxMagnitude / magnitude(of: overallRange))return GeometryReader { proxy inHStack(alignment: .bottom, spacing: proxy.size.width / 120) {ForEach(Array(data.enumerated()), id: \.offset) { index, observation inGraphCapsule(index: index,color: color,height: proxy.size.height,range: observation[keyPath: path],overallRange: overallRange).animation(.ripple(index: index))}.offset(x: 0, y: proxy.size.height * heightRatio)}}}
}

观察一下自定义波动(rippling)效果是怎么作用在视图转场中的。

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

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

相关文章

Linux下tcpwrappers防火墙介绍

tcpwrappers&#xff08;防火墙&#xff09; &#xff0d;&#xff0d;过滤TCP包头(/usr/sbin/tcpd) /etc/hosts.allow 允许 /etc/hosts.deny 拒绝 匹配顺序 tcp包头----<wrappers.so> ------- /etc/hosts.allow ------- /etc/hosts.deny 匹配规则< 规则马上写&…

AI 写高考作文丨10 款大模型 “交卷”,实力水平如何?

前言 在科技日新月异的今天&#xff0c;人工智能&#xff08;AI&#xff09;已不再是遥不可及的未来科技&#xff0c;而是逐渐融入我们日常生活的实用工具。从智能语音助手到自动驾驶汽车&#xff0c;从智能家居系统到精准医疗诊断&#xff0c;AI技术正以其强大的计算能力和数…

算法之链表知识

一、链表的概念 链表是一种常见的数据结构&#xff0c;用于存储数据元素的集合。它由一系列节点组成&#xff0c;每个节点包含数据和指向下一个节点的指针。这种数据结构在动态内存分配中非常有用&#xff0c;因为它不需要连续的内存空间。 二、单向链表和双向链表 单向链表&a…

Rust基础学习-Rust宏

Rust中的宏是生成另一段代码的一段代码。可以根据输入生成代码&#xff0c;简化重复模式&#xff0c;使得代码更加简洁。比如我们一直在用的println!,vec!,panic!都是宏。 创建宏 可以使用macro_rules!创建一个宏&#xff1a; macro_rules! macro_name {(...) > {...} }这…

Java中类初始化的奥秘

一、概述 在 Java 语言中&#xff0c;类的加载、链接&#xff08;验证、准备、解析&#xff09;和初始化过程都是在程序运行期间完成的。 其中加载、验证、准备、初始化和卸载这五个阶段的顺序是确定的。解析阶段可以在初始化之前也可以在初始化之后再开始(又叫作运行时绑定、动…

c#与汇川plc通信 使用官网API库

前言 上位机开发中有时会要求与PLC进行通信&#xff0c;汇川官网也有好用的API库方便大家使用。记录一下开发过程。 1.下载资料 汇川官网地址&#xff1a;汇川技术 - 推进工业文明 共创美好生活 打开后选择&#xff1a;服务与支持-》资料下载-》 资料下载 这里可以直接搜索&am…

六级作文---3.图画类

六级作文—3.图画类 范文 As is illustrated in the graph, the share of urban population increased from 19.39% to 60.6% between 1980 and 2019. From my perspective, the above figures reveal a currently prevailing tendency and represent China’s achievements i…

C++学习插曲:“name“的初始化操作由“case“标签跳过

问题 "name"的初始化操作由"case"标签跳过 问题代码 case 3: // 3、删除联系人string name;cout << "请输入删除联系人姓名&#xff1a;" << endl;cin >> name;if (isExistPerson(&abs, name) -1){cout << "…

【刷题篇】分治-归并排序

文章目录 1、排序数组2、交易逆序对的总数3、计算右侧小于当前元素的个数4、翻转对 1、排序数组 给你一个整数数组 nums&#xff0c;请你将该数组升序排列。 class Solution { public:vector<int> tmp;void mergeSort(vector<int>& nums,int left,int right){…

cocos creator3.7版本拖拽事件处理

前言&#xff1a;网上能找到的资料都太落后了&#xff0c;导致哥们用AI去写&#xff0c;全是瞎B写&#xff0c;版本都不对。贴点实际有用的。别老捣鼓你那破convertToNodeSpaceAR或者convertToNodeSpace了。 核心代码 touch.getDeltaX() touch.getDeltaY() 在cocoscreator3…

python-自幂数判断

[题目描述]&#xff1a; 自幂数是指&#xff0c;一个N 位数&#xff0c;满足各位数字N 次方之和是本身。例如&#xff0c;153153 是 33 位数&#xff0c;其每位数的 33 次方之和&#xff0c;135333153135333153&#xff0c;因此 153153 是自幂数&#xff1b;16341634 是 44 位数…

简单快速设置Windows和Ubuntu双系统双引导

一、参考资料 Windows和Ubuntu双系统安装教程 二、设置引导 1. 安装EasyBCD 下载并安装 EasyBCD 2. 设置Windows引导 3. 设置Ubuntu引导 4. 启动系统 遇到这种情况&#xff0c;直接Enter回车。 三、修复引导 如果引导区损坏&#xff0c;导致无法进入系统&#xff0c;可以…

FuTalk设计周刊-Vol.041

&#x1f525;AI漫谈 热点捕手 1、国产GPTs来了&#xff0c;基于智谱第4代大模型 全自研第四代基座大模型GLM-4&#xff0c;且所有更新迭代的能力全量上线。GLM-4性能相比GLM-3提升60%&#xff0c;逼近GPT-4&#xff08;11月6日最新版本效果&#xff09;。而同时推出的GLM-4-…

【漏洞复现】多客圈子论坛系统 httpGet 任意文件读取漏洞

0x01 产品简介 多客圈子论坛系统是一种面向特定人群或特定话题的社交网络&#xff0c;它提供了用户之间交流、分享、讨论的平台。在这个系统中&#xff0c;用户可以创建、加入不同的圈子&#xff0c;圈子可以是基于兴趣、地域、职业等不同主题的。用户可以在圈子中发帖、评论、…

生活中的人工智能

生活中的人工智能应用广泛&#xff0c;涵盖了多个领域&#xff0c;以下是一些主要的应用场景和详细介绍&#xff1a; 智能家居&#xff1a; 智能家居控制&#xff1a;利用AI技术&#xff0c;实现对智能家居设备的智能化控制&#xff0c;如智能灯光、智能窗帘、智能音响、智能门…

六级作文---2.选择与平衡类

六级作文—2.选择与平衡类 范文一&#xff08;选择理科还是文科专业&#xff1f;&#xff09; It is universally acknowledged that, all other factors being equal, a proper choice on major could be the difference between one’s success of failure. But when it com…

算法分析与设计期末考试复习(更新ing)

重点内容&#xff1a; 绪论&#xff1a; 简单的递推方程求解 1.19(1)(2) 、 教材例题 多个函数按照阶的大小排序 1.18 分治法&#xff1a; 分治法解决芯片测试问题 计算a^n的复杂度为logn的算法&#xff08;快速幂&#xff09; 分治法解决平面最近点对问…

[个人博客]喊你来收藏下我的个人博客

最近整了两个博客 一个用来专门记录技术相关: Developer.lulu 的个人技术博客 一个用来记录平时的一些思考: 有启发 之所以决定要重新搞两个博客,是因为我个人还是喜欢一个主题,一件事情 说实话,每次在 csdn 上写碎碎念的时候,都有些不好意思,毕竟大家来这里都是抱着学技术知识…

根据反射获取枚举值集合

1、获取枚举集合&#xff08;根据枚举类名通过反射实现&#xff09; /// <summary> /// 枚举下拉列表接口 /// </summary> /// <returns></returns> [HttpGet("enumerations/{enumCode}")] [AllowAnonymous] public…

43.bug:mapper接口参数使用@param重命名导致的错误

错误信息&#xff1a;Nested exception is org.apache.ibatis.binding.bindingException:parameter inVo not found public interface UserMapper{ //查询用户列表User queryUserList(Param ("inVo") UserInVo userInVo); } 对应的UserMapper如下&#xff1a; &l…