[SwiftUI-Lab] SwiftUI高级过渡动画

文章源地址:https://swiftui-lab.com/advanced-transitions/

作者: Javier

翻译: Liaoworking

SwiftUI高级过渡动画

本文我们将从简单到复杂的去探索过渡的不同形式,讨论一些不同的方面。如何去配置、组合和触发过渡。我们将学习到什么是提前出现的过渡动画。但首先要确保你对之前提到的一些概念已经熟悉。

什么是过渡动画?

过渡动画决定了在层次结构中插入或者删除一个视图的效果。过渡动画本身不起作用,需要配合动画才行。例如:

image

注意,在XCode11.2以后, 过渡动画不在对隐式动画起作用。所以下面的代码只针对于老版本的xcode起作用,还好其他的例子和版本无关,谢谢Tyler Casselman在留言中提醒。

struct ContentView: View {
    @State private var show = false
    
    var body: some View {
        
        VStack {
            Spacer()
            
            if show {
                LabelView()
                    .animation(.easeInOut(duration: 1.0))
                    .transition(.opacity)
            }
            
            Spacer()
            
            Button("Animate") {
                self.show.toggle()
            }.padding(20)
        }
    }
}

struct LabelView: View {
    var body: some View {
        Text("Hi there!")
            .padding(10)
            .font(.title)
            .foregroundColor(.white)
            .background(RoundedRectangle(cornerRadius: 8).fill(Color.green).shadow(color: .gray, radius: 3))
    }
}

你如果对什么是隐式动画和显示动画没有具体的了解,可以看我的上一篇文章《Advanced SwiftUI Animations》,对于上面的例子,你可以用这两种动画方式。如下。

if show {
    LabelView()
        .transition(.opacity)
}

Spacer()

Button("Animate") {
    withAnimation(.easeInOut(duration: 1.0)) {
        self.show.toggle()
    }
}.padding(20)

另外一种方式就是将动画和过渡关联,请注意动画是作用于过渡上的,并不是视图(例如这里是在.transition()里)

if show {
    LabelView()
        .transition(AnyTransition.opacity.animation(.easeInOut(duration: 1.0)))
}

Spacer()

Button("Animate") {
    self.show.toggle()
}.padding(20)

不对称的过渡

一般情况下,当视图添加到视图层级时,过渡会起作用。 当视图被移除的时候,会有一个相反的效果。opacity将会在添加视图的时候淡入。移除的时候就会淡出。这都可以改变的。

如果我们想特定添加或者移除的动画,我们可以使用 .asymmetric选项。

image

.transition(.asymmetric(insertion: .opacity, removal: .scale))

组合过渡

如果你想要在过渡过程中使用几种转场,例如滑动的时候添加渐入效果。你可以使用下面这种过渡:


image
.transition(AnyTransition.opacity.combined(with: .slide))

注意你可以同时使用 .asymmetric.combined

.transition(.asymmetric(insertion: AnyTransition.opacity.combined(with: .slide), removal: .scale))

带参数的转换

我们前面用的过渡.opacity, .slide, .scale 都是没有参数的,然而一些转换可以添加一些额外的参数,如下:

.scale(scale: 0.0, anchor: UnitPoint(x: 1, y: 0))
.scale(scale: 2.0)
.move(edge: .leading)
.offset(x: 30)
.offset(y: 50)
.offset(x: 100, y: 10)

自定义过渡,一个有趣的开始

让我们开始吧~

过渡是怎样工作的?

系统内部,自定义过渡和标准库的过渡都是以同样的原理工作,都需要为动画的开始和结尾添各加一个修饰器(modifier)。只要两个修饰器组件的差异是可变的,swiftUI都会自己处理。
我们假设.opacity过渡不存在,我们需要去自定义一个,先取名叫.myOpacity. 这里是具体实现。

 extension AnyTransition {
    static var myOpacity: AnyTransition { get {
        AnyTransition.modifier(
            active: MyOpacityModifier(opacity: 0),
            identity: MyOpacityModifier(opacity: 1))
        }
    }
}

struct MyOpacityModifier: ViewModifier {
    let opacity: Double
    
    func body(content: Content) -> some View {
        content.opacity(opacity)
    }
}

下面你就像正常的过渡去用就行了。

.transition(.myOpacity)

正如你所看到的,我们为AnyTransition创建了一个extension. 这里我们指定了两个修饰符来创建过渡。一个用于开始,另外一个用于结束,当移除视图时SwiftUI将倒过来使用这些修饰符。

Transitions at Full Throttle

利用现在我们所掌握的,我们就可以创建一下新的过渡了, 为现有的.rotationEffect().transformEffect() 开辟了新的可能性。
不过当你打算去写新的过渡时,你可能会发现你其实无从下手。。不过"SwiftUI 高级动画"中的所有知识你都可以用起来了。这时候GeometryEffect 和 Shapes会变的很有用。

带有GeometryEffect的自定义过渡。

过渡对于弹出和收起一个面板都特别的合适。如果你想要创建自己的modal体系,你应该好好的阅读下面的内容。

我们下面的练习是创建一个简单的转场去演示怎么去弹出和收起一个视图

<video width='500px' autoplay preload='true' controls src="https://swiftui-lab.com/wp-content/uploads/2019/09/transition-modal.mp4">
</video>

extension AnyTransition {
    static var fly: AnyTransition { get {
        AnyTransition.modifier(active: FlyTransition(pct: 0), identity: FlyTransition(pct: 1))
        }
    }
}

struct FlyTransition: GeometryEffect {
    var pct: Double
    
    var animatableData: Double {
        get { pct }
        set { pct = newValue }
    }
    
    func effectValue(size: CGSize) -> ProjectionTransform {

        let rotationPercent = pct
        let a = CGFloat(Angle(degrees: 90 * (1-rotationPercent)).radians)
        
        var transform3d = CATransform3DIdentity;
        transform3d.m34 = -1/max(size.width, size.height)
        
        transform3d = CATransform3DRotate(transform3d, a, 1, 0, 0)
        transform3d = CATransform3DTranslate(transform3d, -size.width/2.0, -size.height/2.0, 0)
        
        let affineTransform1 = ProjectionTransform(CGAffineTransform(translationX: size.width/2.0, y: size.height / 2.0))
        let affineTransform2 = ProjectionTransform(CGAffineTransform(scaleX: CGFloat(pct * 2), y: CGFloat(pct * 2)))
        
        if pct <= 0.5 {
            return ProjectionTransform(transform3d).concatenating(affineTransform2).concatenating(affineTransform1)
        } else {
            return ProjectionTransform(transform3d).concatenating(affineTransform1)
        }
    }
}

代码在:transiftion-present-dismiss.swift

通过 Shapes创建的自定义过渡

另外一个有用的使用场景就是在两个View之间做转场动画,一个渐入,另外一个渐出。
我们第一直觉是把两个视图放在一个Zstack中,并改变它们的透明度。这样的确OK,不过我们可以使用其他方法做出更炫酷的东西。

<video width='500px' autoplay preload='true' controls src="https://swiftui-lab.com/wp-content/uploads/2019/09/transitions-short-version.mp4">
</video>

代码在: transiftion-present-dismiss.swift
示例代码需要你添加4张图片到你的asset catalog中(命名:photo1, photo2, photo3 和 photo4)这个示例专为iPad的横竖屏设计。

你可以在gist文件中查看代码,其实所有的过渡都遵循相同的模式, 它们都使用了可动形状(animated shape)来裁剪进入和退出的图片。因为它们是z-stacked,我们有一个好看的交叉效果。

总结

这个文章中,我们并没有去讲你需要创建你自己的SwiftUI过渡,你需要的就是释放你的想象力去创作你自己的炫酷特效。

如果想要知道最新文章,欢迎评论和关注我的twitter,下期见。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,002评论 6 509
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,777评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,341评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,085评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,110评论 6 395
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,868评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,528评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,422评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,938评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,067评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,199评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,877评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,540评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,079评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,192评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,514评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,190评论 2 357

推荐阅读更多精彩内容