Animation
- 经过transition增加和删去视图
- 组合transition
- 创立不对称transition
- 自界说transition
- Text的Animation
- 运用transactions重写动画
- 当动画完结时运转一个回调函数
- 运用相位动画器创立多步动画
概述
文章主要分享SwiftUI Modifier的学习进程,将运用事例的方法进行说明。内容浅显易懂,Animation未做调试成果,不过测验代码是彻底的。假如想要运转成果,能够移步Github下载code -> github事例链接
1、经过transition增加和删去视图
能够在设计中包括和排除一个视图,只需要运用一个惯例的判别条件即可。
struct FFTransitionView: View {
@State private var showDetails = false
@State private var showDetails1 = false
var body: some View {
//例如,点击按钮增加或删去文本
VStack {
Button("Press to show details") {
withAnimation {
showDetails.toggle()
}
}
if showDetails {
Text("Details go here")
}
}
//默许状况下,SwiftUI运用淡出动画来插入或闪促视图,但是想要自界说作用,
//能够运用transition()修饰符来自界说
VStack {
Button("Press to show details") {
withAnimation {
showDetails1.toggle()
}
}
if showDetails1 {
Text("Details go here")
.transition(.move(edge: .bottom))
Text("Details go here")
.transition(.slide)
Text("Details go here")
.transition(.scale)
}
}
}
}
2、组合transition
当增加或删去视图时,SwiftUI能够运用combined(with:)
方法组合过度来制造新的动画。
AnyTransition扩展
为了使组合转化更简略运用和重用,能够在AnyTransition
上创立他们作为扩展
extension AnyTransition {
static var moveAndScale: AnyTransition {
AnyTransition.move(edge: .bottom).combined(with: .scale)
}
}
combined多组transition
struct FFTransitionsCombine: View {
@State private var showDetails = false
@State private var showDetails2 = false
var body: some View {
//例如,同时让视图移动和淡出
VStack {
Button("Press to show Details") {
withAnimation {
showDetails.toggle()
}
}
if showDetails {
Text("Details go here")
.transition(AnyTransition.opacity.combined(with: .slide))
}
}
VStack {
Button("Press to show Details") {
withAnimation {
showDetails2.toggle()
}
}
//已经运用扩展声明了函数moveAndscale
if showDetails2 {
Text("Details go here")
.transition(.moveAndScale)
}
}
}
}
3、创立不对称transition
SwiftUI能够在增加一个视图时指定transition,在删去时指定另一个transition。所有这些都是运用asymmetric()
完结的。
struct FFTransitionAsymmetric: View {
@State private var showDetails = false
var body: some View {
//创立一个运用不对称过渡的文本视图:增加时从左边呈现,删去时向下移动
VStack {
Button("Press to show details") {
withAnimation {
showDetails.toggle()
}
}
if showDetails {
Text("Details go here")
.transition(.asymmetric(insertion: .move(edge: .leading), removal: .move(edge: .bottom)))
}
}
}
}
4、自界说transition
虽然SwiftUI自带了一系列的转场动画,也能够自界说transition,这个进程分为三个过程:
- 创立一个ViewModifier来表明转化的任何状况
- 创立一个AnyTransition扩展,该扩展运用活动状况和标识状况的视图修饰符
- 运用transition()修饰符将动画应用到视图上
详细实操过程
编写一个形状和视图修饰符组合,仿照Keynote中的虹膜动画:
- 界说一个ScaledCircle形状,它在一个矩形中创立一个圆,该矩形根据一些可动画化的数据进行缩放。
- 创立一个自界说ViewModifier结构体,将任何形状(缩放后的圆)应用于另一个视图的剪辑形状。
- 在其包装在AnyTransition扩展中,以便将该修饰符包装在transition中,以便于调用。
ScaledCircle
struct ScaledCircle: Shape {
//操操控作矩形内圆的巨细。但为0时,圆不行见,当为1时,圆填充矩形
var animatableData: Double
func path(in rect: CGRect) -> Path {
let maximumCircleRadius = sqrt(rect.width * rect.width + rect.height * rect.height)
let circleRadius = maximumCircleRadius * animatableData
let x = rect.midX - circleRadius / 2
let y = rect.midY - circleRadius / 2
let circleRect = CGRect(x: x, y: y, width: circleRadius, height: circleRadius)
return Circle().path(in: circleRect)
}
}
ClipShapeModifier
通用修饰符,能够剪辑任何形状的视图。
struct ClipShapeModifier<T: Shape>: ViewModifier {
let shape: T
func body(content: Content) -> some View {
content.clipShape(shape)
}
}
AnyTransition
结合ScaledCircle和ClipShapeModifier的自界说transition
extension AnyTransition {
static var iris: AnyTransition {
.modifier(
active: ClipShapeModifier(shape: ScaledCircle(animatableData: 0)),
identity: ClipShapeModifier(shape: ScaledCircle(animatableData: 1)))
}
}
运用自界说transition
struct FFTransitionCustom: View {
@State private var isShowingRed = false
var body: some View {
ZStack {
Color.blue
.frame(width: 200, height: 200)
if isShowingRed {
Color.red
.frame(width: 200, height: 200)
.transition(.iris)
.zIndex(1)
}
}
.padding(50)
.onTapGesture {
withAnimation(.easeInOut) {
isShowingRed.toggle()
}
}
}
}
5、Text的Animation
从iOS16今后,SwiftUI能够将Text动画化。因此像这样的代码能够在两种不同的巨细之间苹果的显示动画,主动的重新渲染文本。
struct FFAnimateTextSize: View {
@State private var fontSize = 32.0
var body: some View {
Text("Hi, metaBBLv")
.font(.custom("Georgia", size: fontSize))
.onTapGesture {
withAnimation(.spring(response: 0.5, dampingFraction: 0.5, blendDuration: 1).repeatForever()) {
fontSize = 72
}
}
}
}
6、运用transactions重写动画
SwiftUI供给了一个withTransaction()
函数,能够在运转时重写动画,例如删去隐式动画并用自界说内容替换他。
struct FFAnimationsOverride: View {
@State private var isZoomed = false
@State private var isZoomed1 = false
@State private var isZoomed2 = false
var body: some View {
VStack {
Button("Toggle zoom") {
isZoomed.toggle()
}
Spacer()
.frame(height: 50)
Text("Zoom text")
.font(.title)
.scaleEffect(isZoomed ? 3 : 1)
.animation(.easeInOut(duration: 2), value: isZoomed)
}
//transactions能够掩盖现有的动画。例如,你可能决定在一个特定状况下,文本的动画以一种款素、线性的方法产生,而不是先有的动画。
//要做到这一点,首要运用想要的动画创立一个新的Transaction实例,然后将他的disablesAnimations值设置为true,这样就能够掩盖任何的现有动画。当全部准备好时,运用withTranscation()。然后持续调整你想要要变的状况。它将运用你的Transcation被动画化。
Spacer()
VStack {
Button("Toggle zoom") {
var transaction = Transaction(animation: .linear)
transaction.disablesAnimations = true
withTransaction(transaction) {
isZoomed1.toggle()
}
}
Spacer()
.frame(height: 50)
Text("Zoom text")
.font(.title)
.scaleEffect(isZoomed1 ? 3 : 1)
.animation(.easeInOut(duration: 2), value: isZoomed1)
}
Spacer()
//关于更多的操控,能够将transaction()修饰符附加到任何视图上。从而能够掩盖该应用在该视图的任何事物。
VStack {
Button("Toggle Zoom") {
var transaction = Transaction(animation: .linear)
transaction.disablesAnimations = true
withTransaction(transaction) {
isZoomed2.toggle()
}
}
Spacer()
.frame(height: 50)
Text("Zoom Text 1")
.font(.title)
.scaleEffect(isZoomed2 ? 3 : 1)
Spacer()
.frame(height: 50)
Text("Zoom Text 2")
.font(.title)
.scaleEffect(isZoomed2 ? 3 : 1)
.transaction { t in
t.animation = .none
}
}
}
}
7、当动画完结时运转一个回调函数
能够挑选为SwiftUI的withAnimation()
函数供给完结后的回调,并在动画完结时运转代码。这可能是调整某些程序状况的地方,但能够将其用作将动画衔接在一起的简略方法。对一个事物进行动画处理,然后对其他事物进行动画处理。
struct FFAnimationFinishCallback: View {
@State private var scaleUp = false
@State private var fadeOut = false
@State private var scaleUp1 = false
@State private var fadeOut1 = false
var body: some View {
//点击按钮然后扩大并淡出
Button("Tap Me!") {
withAnimation {
scaleUp = true
} completion: {
withAnimation {
fadeOut = true
}
}
}
.scaleEffect(scaleUp ? 3 : 1)
.opacity(fadeOut ? 0 : 1)
//这里有一个小细节可能会让你感觉到震惊,假如你运用弹簧动画,则最后可能会呈现很长的运动尾部,其中你的动画正在移动用户无法察觉的。
//默许行为withAnimation()是认为动画完结的,及时荏苒产生微笑运动的肠胃,但假如希望100%完结,能够掩盖默许值。
Button("Tap Me!") {
withAnimation(.bouncy, completionCriteria: .removed) {
scaleUp1 = true
} completion: {
withAnimation {
fadeOut1 = true
}
}
}
.scaleEffect(scaleUp1 ? 3 : 1)
.opacity(fadeOut1 ? 0 : 1)
//关于更杂乱的作用,请考虑运用相位动画器而不是动画完结的闭包
}
}
8、运用相位动画器创立多步动画
SwiftUI的phaseAnimator
视图和phaseAnimator修改器能够经过持续或触发时循环挑选动画段来执行多步动画,创立这些多阶动画需要三个过程:
- 界说阶段,能够是任何类型的序列,但运用枚举最简略CaseIterable。
- 读取相位动画中的一个相位,并调整视图以匹配想要该相位的外观。
- 增加一个触发器,使相位动画从头开端重复其序列
enum
//经过枚举设置
enum AnimationPhase: Double, CaseIterable {
case fadingIn = 0
case middle = 1
case zoomingOut = 3
}
//能够根据你的指令触发动画序列,而不是无休止的重复。为此,能够增加一个触发器值而且经过SwiftUI监督,例如随机数UUID或递增数,当值产生变化时,开端动画并完好播放。
enum AnimationPhase1: CaseIterable {
case start, middle, end
}
//向动画增加核算特点
enum AnimationPhase2: CaseIterable {
case fadingIn, middle, zoomingOut
var scale: Double {
switch self {
case .fadingIn: 0
case .middle: 1
case .zoomingOut: 3
}
}
var opacity: Double {
switch self {
case .fadingIn: 0
case .middle: 1
case .zoomingOut: 0
}
}
}
运用上面的enum做的多种比如
struct FFAnimationmulti_step: View {
@State private var animationStep = 0
@State private var animationStep1 = 0
var body: some View {
//例如,创立一个简略动画,使某些文本开端很小且不行见,扩大到天然巨细并彻底不透明,然后扩大到非常大且不行见。
Text("Hi, metaBBLv")
.font(.largeTitle)
.phaseAnimator([0,1,3]) { view, phase in
view
.scaleEffect(phase)
.opacity(phase == 1 ? 1 : 0)
}
//因为没有供给触发器,所以它将永远运转。
//运用包装视图PhaseAbunator来编写它,它的长处是多个视图能够在阶段之间一起移动
VStack(spacing: 50) {
PhaseAnimator([0,1,2]) { value in
Text("Hi, metaBBLv")
.font(.largeTitle)
.scaleEffect(value)
.opacity(value == 1 ? 1 : 0)
Text("Goodbye, metaBBLv")
.font(.largeTitle)
.scaleEffect(3 - value)
.opacity(value == 1 ? 1 : 0)
}
}
Text("Hi, metaBBLv")
.font(.largeTitle)
.phaseAnimator(AnimationPhase.allCases) { view, phase in
view
.scaleEffect(phase.rawValue)
.opacity(phase.rawValue == 1 ? 1 : 0)
}
//在下面的比如中,点击按钮会触发运用枚举状况的三步动画。首要,界说所需的各种动画阶段,然后每逢特点产生变化时都会遍历。
Button("Tap Me!") {
animationStep += 1
}
.font(.largeTitle)
.phaseAnimator(AnimationPhase1.allCases, trigger: animationStep) { content, phase in
content
.blur(radius: phase == .start ? 0 : 10)
.scaleEffect(phase == .middle ? 3 : 1)
}
//为了取得更多的操控,能够精确指定每个阶段运用那个动画。.bouncy在快速和慢速动画之间移动.easeInOut取得更多的变化。
Button("Tap Me 1") {
animationStep1 += 1
}
.font(.largeTitle)
.phaseAnimator(AnimationPhase1.allCases, trigger: animationStep1) { content, phase in
content
.blur(radius: phase == .start ? 0 : 10)
.scaleEffect(phase == .middle ? 3 : 1)
} animation: { phase in
switch phase {
case .start, .end: .bouncy
case .middle: .easeInOut(duration: 2)
}
}
//因为经过核算特点增加了动画,所以在调用时更加的简练
Text("Hi, metaBBLv")
.font(.largeTitle)
.phaseAnimator(AnimationPhase2.allCases) { content, phase in
content
.scaleEffect(phase.scale)
.opacity(phase.opacity)
}
}
}