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,一经查实,立即删除!

相关文章

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

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

Rust基础学习-Rust宏

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

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

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

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;圈子可以是基于兴趣、地域、职业等不同主题的。用户可以在圈子中发帖、评论、…

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

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

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

文章部分素材来源 | CSDN&#xff08;ID&#xff1a;CSDNnews&#xff09; 前言 在科技日新月异的今天&#xff0c;人工智能&#xff08;AI&#xff09;已不再是遥不可及的未来科技&#xff0c;而是逐渐融入我们日常生活的实用工具。从智能语音助手到自动驾驶汽车&#xff0c…

炫技来了!使用SDR设备成功抓到蓝牙air packet, 并且wireshark实时解析, 没错就是蓝牙空口抓包器

本文章主要介绍是用ZYNQ7020AD9361Gnu radio是搭建一个蓝牙抓包器的文章。 由于之前一直做蓝牙Host&#xff0c;对controller觉得是一个比较虚无缥缈的东西&#xff0c;得不到的总是在骚动&#xff0c;所以最近用我用吃灰了2年的SDR(Software Defined Radio&#xff09;设备研…

C语言scanf( ) 函数、fprintf( ) 函数与 scanf( ) 函数和printf( ) 函数有什么不同?

一、问题 fscanf( ) 函数、fprintf( ) 函数与 printf( ) 函数、scanf( ) 函数的作⽤相似&#xff0c;都是格式化读写函 数&#xff0c;那么这两个读写函数有什么不同呢&#xff1f; 二、解答 两者的区别就在于前⾯的字符“f”&#xff0c;即 fscanfQ函数和 fprintfD函数的读写…

【Java基础】OkHttp 超时设置详解

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

AddressSanitizer理论及实践:heap-use-after-free、free on not malloc()-ed address

AddressSanity&#xff1a;A Fast Address Sanity Checker 摘要 对于C和C 等编程语言&#xff0c;包括缓冲区溢出和堆内存的释放后重用等内存访问错误仍然是一个严重的问题。存在许多内存错误检测器&#xff0c;但大多数检测器要么运行缓慢&#xff0c;要么检测到的错误类型有…

Java基础——数组Array

系列文章目录 文章目录 系列文章目录前言一、数组基本概念二、一维数组三、数组的模型四、数组对象的创建五、元素为引用数据类型的数组 前言 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网…

leetcode 所有可能的路径(图的遍历)

leetcode 链接&#xff1a; 所有可能的路径 1 图的基本概念 1.1 有向图和无向图 左边是有向图&#xff0c;右边是无向图。对于无向图来说&#xff0c;图中的边没有方向&#xff0c;两个节点之间只可能存在一条边&#xff0c;比如 0 和 1 之间的边&#xff0c;因为是无向图&am…

【Vue】——组件的注册与引用

&#x1f4bb;博主现有专栏&#xff1a; C51单片机&#xff08;STC89C516&#xff09;&#xff0c;c语言&#xff0c;c&#xff0c;离散数学&#xff0c;算法设计与分析&#xff0c;数据结构&#xff0c;Python&#xff0c;Java基础&#xff0c;MySQL&#xff0c;linux&#xf…

Go微服务: 关于消息队列的选择和分类以及使用场景

消息队列概述 在分布式系统和微服务架构中&#xff0c;消息队列&#xff08;Message Queue&#xff09;是一个核心组件&#xff0c;用于在不同的应用程序或服务之间异步传递消息在 Go 语言中&#xff0c;有多种实现消息队列的方式&#xff0c;包括使用开源的消息队列服务&…