为什么显式动画比隐式动画效果更好? (Swift UI 100 天:猜旗子)

问题描述 投票:0回答:1

我正在应对一个项目挑战。我之前制作过一个旗帜问答游戏,其中有由 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 进行动画处理?我想我在这里遗漏了一些关于显式动画的重要内容。

ios swift animation swiftui
1个回答
0
投票

这只是因为您没有将

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。

类似的情况,请参阅我的另一个答案

© www.soinside.com 2019 - 2024. All rights reserved.