SwiftUI学习(6)-SwiftUI动画入门Part2

SwiftUI学习(6)-SwiftUI动画入门Part2

2021, Oct 20    

コニクマル撰写

本文使用目前最新版本的xcode 13 + macOS Big sur + iOS15

在做过渡动效果最好使用模拟器或者真机进行测试, Xcode 的Previews 渲染的效果经常出错

过渡(Transition)

基本使用

过渡效果用于View出现或者消失的动画效果, 举个简单例子点击一个按钮出现一个圆, 代码如下

@State var isShow = false
var body: some View {
  VStack(alignment:.center, spacing: 50) {
      Button("Clicked ME") {
          self.isShow.toggle()
      }
      if isShow {
          Circle()
              .frame(width: 100, height: 100)
              .foregroundColor(Color.blue)
      }
  }
  .frame(maxWidth:.infinity, maxHeight: .infinity, alignment: .top)
}

运行下:

2021-10-20-21.54.32

上面没有添加过渡效果, 给圆添加一个淡入淡出的效果, 修改代码:

...
Circle()
  .frame(width: 100, height: 100)
  .foregroundColor(Color.blue)
  .transition(.opacity)
...

并且给isShow变化的地方加上withAnimation,如果不加上withAnimation也是没有效果的, 修改如下

...
withAnimation(.easeIn(duration: 1)) {
    self.isShow.toggle()
}
...

运行效果:

2021-10-20-21.54.32

非对称过渡(Asymmetric)

可以通过asymmetric来分别设置出现和消失的过渡效果, 修改下代码

...
Circle()
  .frame(width: 100, height: 100)
  .foregroundColor(Color.blue)
  .transition(.asymmetric(insertion: .slide, removal: .scale))
...

这样圆形出现的时候是一个滑动slide的效果,消失的时候又是一个缩放scale的效果:

2021-10-20-21.54.32

合并过渡(Combines)

可以通过combined(with:)将多个过渡效果合并起来同时显示,比如做一个同时滑如和淡入淡出的过渡动画, 修改代码:

...
Circle()
  .frame(width: 100, height: 100)
  .foregroundColor(Color.blue)
  .transition(.slide.combined(with: .opacity))
...

运行效果:

2021-10-20-21.54.35

MatchedGeometryEffect

MatchedGeometryEffect类似Hero动画,是两个元素切换的时候形成过渡。

先做一个列表界面如下

struct FirstView: View{
    @Binding var isShowSecond: Bool
    @Binding var shardId: String
    var shared: Namespace.ID
    var body: some View{
        ScrollView{
            LazyVStack(alignment:.leading, spacing: 0) {
                ForEach(0 ..< 10){ i in
                    let id =  "shard\(i)"
                    HStack{
                        Image("Image")
                            .resizable()
                            .matchedGeometryEffect(id: id, in: shared)
                            .aspectRatio(contentMode: .fit)
                            .clipShape(Circle())
                            .frame(width: 50, height: 50)
                            .foregroundColor(Color.red)
                            .animation(.easeOut)
                        Text("星のカービィ")
                        Spacer()
                    }
                    .onTapGesture {
                        shardId = id
                        isShowSecond.toggle()
                    }
                    .padding()
                    Divider()
                }
            }
        }
    }
}

在做一个详情页面:

struct SecondView: View{
    var shared: Namespace.ID
    var sharedId: String
    @State var isShow = false
    var body: some View{
        VStack(alignment:.center, spacing: 50) {
            Image("Image")
                .resizable()
                .aspectRatio(contentMode: .fit)
                .clipShape(Circle())
                .matchedGeometryEffect(id: sharedId, in: shared)
                .frame(width: 200, height: 200)
                .foregroundColor(Color.green)
                .animation(.easeOut)
                .onAppear {
                    withAnimation(.easeOut.delay(0.5)){
                        isShow.toggle()
                    }
                }
            
            if isShow {
            Text("桜井政博が生みの親で、彼が開発に関わった作品を「桜井カービィ」と呼ぶこともある。第1作はゲームボーイ対応ソフトとして日本で1992年4月27日に発売し世界売上で500万本以上を記録。シリーズ累計販売本数は全世界で2016年時点で3800万本以上にも及ぶ[1]。漫画やアニメ、小説といったメディアミックス作品も多数製作されている。また、星のカービィのテーマカフェである「KIRBY CAFÉ」が2016年8月8日より順次、大阪、名古屋、東京、博多で期間限定店舗としてオープンし、2019年12月12日には東京ソラマチ4Fで常設店舗がオープンした。さらに、2020年12月3日より、阪急三番街のB1F キデイランド大阪梅田店において、カービィのグッズ常設売り場となるKIRBY’S PUPUPU MARKET[2]がオープンした。")
                .animation(.easeOut)
                .padding()
            }
        }
        .frame(maxWidth:.infinity, maxHeight: .infinity, alignment: .top)
    }
}

两个界面的Image设置matchedGeometryEffect当切换时候id和Namespace.ID相同时就会触发类似Hero的过渡效果。

添加ContentView代码:

struct ContentView: View {
    @Namespace var shared
    @State var isShowSecond = false
    @State var sharedID = ""
    var body: some View {
        ZStack {
            if isShowSecond {
                SecondView(shared: shared, sharedId: sharedID)
            }else{
                FirstView(isShowSecond: $isShowSecond, shardId: $sharedID, shared: shared)
            }
        }
    }
}

运行效果如下:

2021-10-20-21.54.35