SwiftUI之深入解析如何定制视图的动画和转场

一、前言

  • 使用 SwiftUI 可以把视图状态的改变转成动画过程,SwiftUI 会处理所有复杂的动画细节。
  • 本文中,会给跟踪用户徒步的图表视图添加动画,使用 animation(_: ) 修改器给一个视图添加动画效果非常容易。
  • 可以下载文末的示例代码并跟着本篇教程一步步实践,或者查看本篇完成状态时的工程代码去学习,就可以轻松完成视图的动画添加和转场动画设定。

二、给每个视图单独添加动画

  • 在视图上使用 animation(_: ) 修改器时,SwiftUI 会在视图的任何可进行动画的属性发生改变时产生对应的动画效果。视图的颜色、不透明度、旋转角度、大小及一些其它属性都是可进行动画的:

在这里插入图片描述

  • 在 HikeView.swift 中,打开实时预览,体验一下图表的打开和隐藏,此时的状态改变时是没有添加动画效果的。在本文中,保持实时预览一直打开,每一步修改的效果就可以实时的看到:

在这里插入图片描述

  • 给显示/隐藏切换的箭头按钮添加旋转动画,会发现现在按钮点击时的旋转有一个动画过渡的效果:
struct HikeView: View {

    var hike: Hike
    @State private var showDetail = false
    
    var body: some View {
        VStack {
            HStack {
                HikeGraph(hike: hike, path: \.elevation)
                    .frame(width: 50, height: 30)
                    .animation(nil)
                
                VStack(alignment: .leading) {
                    Text(hike.name)
                        .font(.headline)
                    Text(hike.distanceText)
                }
                
                Spacer()

                Button(action: {
                    withAnimation {
                    	self.showDetail.toggle()
                    }
                }) {
                    Image(systemName: "chevron.right.circle")
                        .imageScale(.large)
                        .rotationEffect(.degrees(showDetail ? 90 : 0))
                        .scaleEffect(showDetail ? 1.5 : 1)
                        .padding()
                        .animation(.easeInOut)
                }
            }

            if showDetail {
                HikeDetail(hike: hike)
            }
        }
    }
}

struct HikeView_Previews: PreviewProvider {
    static var previews: some View {
        VStack {
            HikeView(hike: hikeData[0])
                .padding()
            Spacer()
        }
    }
}

在这里插入图片描述

  • 当视图从隐藏到展示时,让切换按钮变大 1.5 倍:

在这里插入图片描述

  • 把动画的类型从 easeInOut 改为 spring(),SwiftUI 包含一些预设或可自定义的动画类型,像弹簧(spring)动画和类型液体(fluid)动画类型,可以调整动画开始前的等待时长、动画的速度也可以指定让动画循环重复的进行:

在这里插入图片描述

  • 如果只想让按钮具有缩放动画而不进行旋转动画,可以在 scaleEffect 添加 animation(nil) 来实现。可以在这里做一些实验,如果把其它的一些动画效果结合在一起,会怎么样呢?

在这里插入图片描述

三、把视图的状态改态转化成动画效果

  • 学会给单个视图添加动画的方法,现在可以学习怎么在视图的状态发生改变时添加动画效果,当用户点击按钮时会切换 showDetail 状态的值,在视图变化过程中添加动画效果:

在这里插入图片描述

  • 把 showDetail.toggle() 包裹在 withAnimation 函数调用块中。showDetail 的改变影响了视图 HikeDetail 和详情切换按钮,在显示/隐藏详情的过程中都有了过滤动画效果:

在这里插入图片描述

  • 放慢动画速度,可以观察 SwiftUI 动画在被中断下是怎么运作的,给 withAnimation 传入一个时长4秒的基本动画参数 .easeInOut(duration:4),可以指定动画过程时长,给 withAnimation 传入的动画参数与 .animation(_😃 修改器可用参数一致:

在这里插入图片描述

  • 在动画过程进行中点击按钮切换视图状态,查看对应的动画被中断时的效果:

在这里插入图片描述

四、定制视图转场动画

  • 开始之前,先把动画时长参数 (.easeInOut(duration: 4)) 相关的代码去掉,让动画不再缓慢进行。
  • 默值情况下,视图离屏和入屏时的动画效果是渐隐/渐现, 这个默认的转场效果可以使用 transition(_: ) 修改器进行定制:

在这里插入图片描述

  • 给 HikeView 视图添加 transition(_: ) 修改器,并定制转场参数为 .slide,转场动画为滑入/滑出:

在这里插入图片描述

  • 可以把滑入/滑出这种转场动画封装起来,方便其它视图复用同样的转场效果:
extension AnyTransition {
    static var moveAndFade : AnyTransition {
        AnyTransition.slide
    }
}

struct HikeView: View {
    var hike: Hike
    @State private var showDetail = false
    var body: some View {
        VStack {
            HStack {
                HikeGraph(hike: hike, path: \.elevation)
                    .frame(width: 50, height: 30)
                    .animation(nil)
                
                VStack(alignment: .leading) {
                    Text(hike.name)
                        .font(.headline)
                    Text(hike.distanceText)
                }
                Spacer()
                Button(action: {
                    withAnimation() {
                    	self.showDetail.toggle()
                    }
                }) {
                    Image(systemName: "chevron.right.circle")
                        .imageScale(.large)
                        .rotationEffect(.degrees(showDetail ? 90 : 0))
                        .scaleEffect(showDetail ? 1.5 : 1)
                        .padding()
                }
            }
            if showDetail {
                HikeDetail(hike: hike)
                    .transition(.moveAndFade)
            }
        }
    }
}
  • 在 moveAndFade 转场效果的定义中使用 move(edge:),让滑入/滑出从屏幕的同一边进行:

在这里插入图片描述

  • 使用 asymmetric(insertion:removal: ) 修改器来定制视图显示/消失时的转场动画效果:

在这里插入图片描述

五、组合复杂的动画效果

  • 点击图表下面的三个按钮,会在三个不同的数据集间进行切换并展示。现在使用组合动画,让图表在不同数据集间切换时的转换动画流畅自然:

在这里插入图片描述

  • 把 showDetail 的默认值改为 true,并把 HikeView 的预览模式视图固定在画布上。这样可以在编辑其它文件时,依然看到动画效果的变化:

在这里插入图片描述

  • 在 HikeGraph.swift 中定义了一个新的波动动画,并把它与滑入/滑出动画一起应用到图表视图上:
func magnitude(of range: Range<Double>) -> Double {
    return range.upperBound - range.lowerBound
}

extension Animation {
    static func ripple() -> Animation {
        Animation.default
    }
}

struct HikeGraph: View {
    var hike: Hike
    var path: KeyPath<Hike.Observation, Range<Double>>
    
    var color: Color {
        switch path {
        case \.elevation:
            return .gray
        case \.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.observations
        let overallRange = rangeOfRanges(data.lazy.map { $0[keyPath: self.path] })
        let maxMagnitude = data.map { magnitude(of: $0[keyPath: path]) }.max()!
        let heightRatio = (1 - CGFloat(maxMagnitude / magnitude(of: overallRange))) / 2

        return GeometryReader { proxy in
            HStack(alignment: .bottom, spacing: proxy.size.width / 120) {
                ForEach(data.indices) { index in
                    GraphCapsule(
                        index: index,
                        height: proxy.size.height,
                        range: data[index][keyPath: self.path],
                        overallRange: overallRange)
                    .colorMultiply(self.color)
                    .transition(.slide)
                    .animation(.ripple())
                }
                .offset(x: 0, y: proxy.size.height * heightRatio)
            }
        }
    }
}
  • 把动画切换为弹簧动画(spring),并设置弹簧阻尼系数为 0.5,动画过程中产生了逐渐回弹效果:

在这里插入图片描述

  • 加速弹簧动画的执行速度,缩短切换图表的时间:

在这里插入图片描述

  • 以当条形在图表中的位置为参数,添加延迟效果,图表中的每个条形会顺序动起来:

在这里插入图片描述

  • 观察一下自定义波动(rippling)效果是怎么作用在视图转场中的。
  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

╰つ栺尖篴夢ゞ

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值