我正在应对一个项目挑战。我之前制作过一个旗帜问答游戏,其中有由 ForEach 循环制作的三个按钮,并以图像作为旗帜。挑战在于为旗帜设置动画,以便点击的旗帜旋转 360 度,而其他旗帜则不旋转。我已成功完成挑战(以及后续挑战)。这是我的代码块(为了方便起见,我没有在这里使用标志图像):
import SwiftUI
struct FlagImage: View {
var imageName: String
var body: some View {
Image(imageName)
.clipShape(.capsule)
.shadow(radius: 5)
}
}
struct ContentView: View {
@State private var countries = ["Estonia", "France", "Germany", "Ireland", "Italy", "Nigeria", "Poland", "Spain", "UK", "Ukraine", "US"].shuffled()
@State private var correctAnswer = Int.random(in:0...2)
@State private var scoreTitle = ""
@State private var showingScore = false
@State private var score = 0
@State private var message = ""
@State private var numQuestionsAsked = 1
@State private var rotDegrees = 0.0
@State private var notPickedOpacity = 1.0
@State private var scaling = 1.0
@State private var whichFlagTapped = 0
@State private var isGameOver: Bool = false
var body: some View {
ZStack {
VStack{
VStack(spacing: 30){
VStack {
Text("Tap the flag of")
.foregroundStyle(.secondary)
.font(.subheadline.weight(.heavy))
Text(countries[correctAnswer])
.foregroundStyle(.black)
.font(.largeTitle.weight(.semibold))
}
ForEach(0..<3){ number in
Button{
flagTapped(number)
whichFlagTapped = number
withAnimation {
rotDegrees += 360
notPickedOpacity -= 0.75
scaling -= 0.2
}
} label: {
Color(.red).frame(width: 200, height: 200)
Text(countries[number])
}.rotation3DEffect(
whichFlagTapped == number ? .degrees(rotDegrees) : .degrees(0),
axis: (x:0.0, y:1.0, z:0.0)
)
.opacity(whichFlagTapped == number ? 1.0 : notPickedOpacity)
.scaleEffect(whichFlagTapped == number ? 1.0 : scaling)
}
}.frame(maxWidth: .infinity)
.padding(.vertical, 20)
.background(.regularMaterial)
.clipShape(.rect(cornerRadius: 20))
Text("Score: \(score)").foregroundStyle(.white).font(.title.bold())
Spacer()
}.padding()
}.alert(scoreTitle, isPresented: $showingScore) {
Button("Continue", action: askQuestion)
} message: {
Text(message)
}
.alert("Game Over!", isPresented: $isGameOver) {
Button("Restart", action: reset)
} message: {
Text("\(score) out of 8")
}
}
func flagTapped(_ number: Int){
if number == correctAnswer {
scoreTitle = "Correct!"
score += 1
message = "Well done! Your score is: \(score)"
} else {
scoreTitle = "Wrong!"
message = "That is the flag of \(countries[number]). \n Your score is: \(score)"
}
showingScore = true
}
func askQuestion(){
if numQuestionsAsked < 8 {
countries.shuffle()
correctAnswer = Int.random(in: 0...2)
numQuestionsAsked += 1
notPickedOpacity = 1.0
scaling = 1.0
} else {
isGameOver = true
}
}
func reset() {
score = 0
numQuestionsAsked = 0
askQuestion()
}
}
虽然这对于显式动画效果很好,但我试图找出为什么它比隐式动画效果更好。这是我在隐式动画循环中得到的内容(这次仅关注旋转)。
ForEach(0..<3){ number in
Button{
flagTapped(number)
whichFlagTapped = number
rotDegrees += 360
} label: {
FlagImage(imageName: countries[number])
}.rotation3DEffect(
whichFlagTapped == number ? .degrees(rotDegrees) : .degrees(0),
axis: (x:0.0, y:1.0, z:0.0)
)
.animation(.default, value: rotDegrees)
}
在这种情况下,点击的答案会旋转 360 度(有时更多?),但之前的答案也会旋转回 0。 所以我想知道,为什么在显式动画情况下不会发生这种旋转回 0 的情况?或者,也许标志的旋转实际上设置为 0(并且没有动画,因为它只是动画 rotDegrees 的变化?) - 但在这种情况下,不应稍后在该位置选择标志,导致其旋转 360 的某个倍数备份到 rotDegrees?从那时起,旗帜的旋转将需要从 0 到新的 rotDegrees 进行动画处理?我想我在这里遗漏了一些关于显式动画的重要内容。
这只是因为您没有将
whichFlagTapped = number
放入 withAnimation
闭包中。如果您将其放入,行为将是相同的 - 先前选择的标志将旋转回 0 度。
隐式动画
.animation(.default, value: rotDegrees)
每当 rotDegrees
发生变化时,都会对交易中的 all变化进行动画处理。当您按下按钮时,
rotDegrees
会发生变化,但之前选择的按钮的 whichFlagTapped == number
也会发生变化!该按钮的 whichFlagTapped == number
将更改为 false。因此,对于之前选择的按钮,其旋转将从 .degrees(rotDegrees)
变为 .degrees(0)
。
这也是为什么它有时会产生超过 360 度的动画。你每次都会将
rotDegrees
增加 360 度,所以对于后面的问题,当 whichFlagTapped == number
发生变化时,它会将角度从 0 度动画到 n * 360 度。
通过显式动画,您可以精确控制哪些变化需要动画化。您将
whichFlagTapped = number
放在 withAnimation
之外,因此 whichFlagTapped == number
更改为 false 不会产生动画效果。唯一的动画是rotDegrees
增加了360。
类似的情况,请参阅我的另一个答案。