Numeric Property:动画的生成办法

在上一节中,咱们通过运用 .animate() 修饰符和修正 changed 变量来动态改动小球的「偏移量」,从而创造出动画作用。实际上,SwiftUI 提供了许多相似的数值型特点,它们能够被用来操控和自界说各种动态交互和视觉表现。本节将详细介绍一些常见且极具实用性的特点,以协助开发者充分运用 SwiftUI 的强大功用,创造出愈加丰厚和流通的用户体验。

偏移量(Offset)

怎样「了解」偏移量?

每个 SwiftUI 视图在其父视图中都有一个「原始方位」,这个方位是依据布局系统(如仓库、网格、对齐等)决议的。

当运用一个偏移量到一个视图上时,适当所以在奉告 SwiftUI:在渲染这个视图时,应该在其「原始方位的基础上」,在屏幕上向「指定方向」移动「指定的间隔」。例如:

Text("Hello, World!")
    .offset(x: 20, y: 50)

在这个例子中,不管文本的原始方位在哪里,都会在屏幕上向右移动 20 个单位,向下移动 50 个单位。这种移动是视觉上的,不影响文本在布局系统中的「逻辑」方位。

重要的是要了解,偏移量的运用不会改动视图在其父视图中的布局特点。也便是说,尽管视觉上文本被移动了,它仍然「占据」着本来的空间方位。这意味着其他视图的布局不会由于这个视图的偏移而受到影响。

了解了这点后,再运用偏移动画时,能够放心大胆的运用,由于不管当时视图怎样偏移都不会影响到其它的视图布局。

除了offset(x:y:)的办法外,还有一种offset(_ offset:CGSize)办法可完成方位偏移。

Circle()
	.fill(.blue)
	.frame(width: 100)
	.offset(CGSize(width: changed ? 125 : -125, height: changed ? 0 : -500))
	.animation(.easeIn, value: changed)

这两者在运用上是等价的,「差异」便是CGSize能够作为一个单一的数据结构处理,而offset(x:y:) 更直观。

框架(Frame)

Numeric Property:动画的生成办法

在 SwiftUI 中,frame 修饰符用来指定视图的「尺度」和子视图的「对齐」。所以它在动画中的运用,也分为两种场景:

1. 设置尺度

frame 最常见的用途是界说视图的「宽度」和「高度」。当你为视图设置一个 frame,你能够指定宽度(width)、高度(height),或许两者都指定。假如不指定,视图将尽或许地习惯其内容或填满可用空间,当然这取决于其父视图的布局特性。

如下例,通过改动高和宽能够完成一个扩大和缩小的动画作用:

 Rectangle()
	.fill(.blue)
	.frame(width: changed ? 300 : 100, height: changed ? 400 : 150)
	.animation(.easeIn, value: changed)

2. 对齐办法

frame当用来设置容器视图的尺度时,其间的子视图默认居中展现,一般子视图的尺度会小于父容器的尺度,所以改动子视图在其间的「对齐办法」能够间接完成视图的「移动」作用。

如下例所示:

VStack {
	Circle()
		.fill(.blue)
		.frame(width: 100)
		.animation(.easeIn, value: changed)
}.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: changed ? .topLeading : .center)

Numeric Property:动画的生成办法

方位(Position)

Numeric Property:动画的生成办法
position指定当时视图「中心」的横纵坐标(x,y)时,是依据其地点的「父容器」进行核算的,如下例圆心的方位正坐落父容器(赤色矩形)的左上角(x:0,y:0)

Numeric Property:动画的生成办法

offset相似,它相同可用于移动视图,而且移动视图后不会影响到其它「兄弟」视图的布局,如下例:

Numeric Property:动画的生成办法

假如只需求移动当时视图,用offset最为简单直观;假如需求依据父容器的巨细或在父容器中的方位来判别移动带来的影响,那最好用position,如下例:

Numeric Property:动画的生成办法

整个浅蓝色布景是「父容器」,当将小球向下移动,假如纵坐标「超过 400」,则小球变为赤色并扩大 3 倍;不然当纵坐标小于 400,小球变为绿色并恢复原始巨细。

GeometryReader

许多时候咱们并不关注具体的坐标值,由于在不同的渠道(iPhone、iPad 或 Mac)上,由于设备尺度的不同,所以更需求一个相对的方位或坐标,比「居中」、「左下角」、「底部」等。

SwiftUI 提供了一个容器视图 GeometryReader,它的构造器参数 「GeometryProxy」 能够用来读取其父视图的「尺度」和「方位」信息。这里边办法许多,不一一列举,只说一下与position合作常用的两个办法:

Numeric Property:动画的生成办法

  • size用来获取父容器的尺度,比方宽(width)或高(height)。
  • frame用来获取父容器的「边界」的坐标,比方minXmidXmaxY等。
    Numeric Property:动画的生成办法

两者某种情况下是等价的,比方「宽」等价于「maxX」,「宽」的一半便是「midX」等等。 如下例所示,两种办法都能够获取到父容器中心方位的坐标,黑球和白球都坐落父容器「ZStack」(橘色布景)的中心方位:

ZStack {
	GeometryReader { geometry in
		Circle().fill(.black)
			.frame(width: geometry.size.width / 2, height: 75)
			.position(x: geometry.size.width / 2, y: geometry.size.height / 2)
	}
	GeometryReader { geometry in
		Circle().fill(.white)
			.frame(width: geometry.frame(in: .local).midX, height: 50)
			.position(x: geometry.frame(in: .local).midX, y: geometry.frame(in: .local).midY)
	}
}.background(.orange)

Numeric Property:动画的生成办法

运用这种办法,前例中当小球纵坐标大于「400」(预估的一个中间值),就能够运用「midY」来替代,这样在不同设备、不同方向(横屏或竖屏)都能完美兼容。

色彩(Color)

Color在内部运用数值来界说色彩的各个组成部分,如红、绿、蓝和通明度(RGBA)或其他色彩模型。这些数值能够在动画中滑润地过渡,使得 Color 能够被动画化。 比方「赤色」转变为「绿色」时,是穿插了一些列的「插值」,然后在一定「间隔」时间内,慢慢从开始切换到停止(开始 -> 插值 1 -> 插值 2 -> …插值n -> 停止)。

Numeric Property:动画的生成办法

如下例,当点击「赤色」时,布景由「赤色」转变为「绿色」,产生一个突变的动画作用:

View
	.foregroundStyle(changed ? .green : .red)
	.animation(.easeIn, value: changed)	

运用色彩的修饰符,最常用的便是前景色(foregroundStyle)、布景色(background)、主题色(tint),如下例:

Text("foregroundStyle")
	.foregroundStyle(changed ? .green : .red)
Text("background")
	.foregroundStyle(.white)
	.padding(5)
	.background(changed ? .green : .red)
Toggle(isOn: .constant(true), label: {
	Text("tint")
}).tint(changed ? .green : .red)

Numeric Property:动画的生成办法

「前景色」、「布景色」都能够响应色彩突变的动画,「主题色」则不支持动画,所以假如想要设置某个视图的主题色,比方「按钮」、「开关」等,能够通过重写其Style办法来完成,如下例:

Toggle(isOn: $changed, label: {
	Text("开关")
})
.toggleStyle(MyToggle()) //重写 ToggleStyle
struct MyToggle: ToggleStyle {
  func makeBody(configuration: Configuration) -> some View {
    Circle()
	    .fill(configuration.isOn ? .green : .red)//改动色彩
      .frame(width: 100)
      .overlay(content: {
        configuration.label.foregroundStyle(.white)
      })
    .animation(.easeIn, value: configuration.isOn)//增加动画
  }
}

Numeric Property:动画的生成办法

通明度(Opacity)

opacity 操控视图的通明度,取值规模从 0.0(彻底通明)到 1.0(彻底不通明)。在动画中,能够用其来完成视图的渐进渐出作用,如下例:

View.opacity(changed ? 1 : 0)

尽管视觉上不可见了,但其仍然坚持空间的占用,不会影响到「兄弟」视图的布局,比方下例中,「赤色」不会由于「绿色」的消失而「浮动」(上浮)。

所以,假如想要完成相似于弹窗、模态窗、提示窗口等视图,能够运用ZStack容器或许.overlay修饰符来完成。

旋转(RotationEffect)

.rotationEffect 修饰符答应你对视图进行旋转变换,比方完成一个旋转的按钮:

View
	.rotationEffect(.degrees(tapped ? 45 : 0))
	.animation(.easeIn, value: tapped)

Numeric Property:动画的生成办法

再结合「偏移量」、「色彩」、「通明度」完成一个多功用按钮:


ZStack {
	//「图片按钮」
	Circle().stroke(.blue, lineWidth: 2.5).frame(width: 70)
		.overlay {
			Image(systemName: "photo")
				.font(.system(size: 36))
				.bold()
				.foregroundStyle(.blue)
		}
		//向左移动 100
		.offset(x: tapped ? -100 : 0)
		//逆时针旋转 90 度
		.rotationEffect(.degrees(tapped ? 0 : -90))
		//渐进
		.opacity(tapped ? 1 : 0)
	//「视频按钮」
	// ...
		//向左移动 100,向上移动 100
		.offset(x: tapped ? -100 : 0, y: tapped ? -100 : 0)
		//渐进
		.opacity(tapped ? 1 : 0)
	//「语音按钮」
	// ...
		//向上移动 100
		.offset(y: tapped ? -100 : 0)
		//顺时针旋转 90 度
		.rotationEffect(.degrees(tapped ? 0 : 90))
		//渐进
		.opacity(tapped ? 1 : 0)
	//「多功用按钮」
	Button(action: {
		// tapped false => true
		tapped.toggle()
	}, label: {
		//赤色变为蓝色
		Circle().fill(tapped ? .red : .blue).frame(width: 75)
			.overlay {
				Image(systemName: "plus")
					.font(.system(size: 48))
					.bold()
					.foregroundStyle(.white)
			}
	})
	//顺时针旋转 45 度
	.rotationEffect(.degrees(tapped ? 45 : 0))
}

三维旋转(Rotation3DEffect)

运用 rotation3DEffect 能够为视图增加立体旋转作用,这种作用特别合适制作翻转动画。如下例:

View
	.rotation3DEffect(
		.degrees(changed ? 0 : 180),axis: (x: 0.0, y: 1.0, z: 0.0)
	)
	.animation(.easeIn, value: changed)

缩放(ScaleEffect)

scaleEffect 用于改动视图的尺度,当结合动画运用时,能够创造出视图缩放进入或退出屏幕的动态作用。如下例:

View
	.scaleEffect(changed ? 1 : 0)
	.animation(.easeIn, value: changed)

Numeric Property:动画的生成办法

scaleEffectframe都能够完成视图的缩放作用,差异在于frame缩放后会影响兄弟视图的布局,如下例:

Numeric Property:动画的生成办法

字体(Font)

动画化字体巨细改变是一个很好的办法来招引用户的注意或许提供动态的视觉反应。如下例:

Text("Hello, SwiftUI")
	.font(changed ? .largeTitle : .title3)
	.animation(.easeIn, value: changed)

仔细观察会发现,字体在扩大的过程中有些诡异的左右抖动,这是由于字体的缩放过程中,也会缩放它占用的空间,换句话说,它的缩放也会影响到其它兄弟视图的布局,所以解决的办法便是预先设置好它的frame尺度(能装得下最大的字体)。

Text("Hello, SwiftUI")
	.font(changed ? .largeTitle : .title3)
	//提前奉告 SwiftUI 它的尺度
	.frame(width: 200, height: 100)
	.animation(.easeIn, value: changed)

字体权重也能够动画化,如下例:

Text("Hello, SwiftUI")
	.fontWeight(changed ? .black : .ultraLight)

Numeric Property:动画的生成办法

字体类型

Text("Hello, SwiftUI")
	.font(.custom(changed ? "Academy Engraved LET" : "Papyrus", size: 36))

字体风格

Text("Hello, SwiftUI")
	.fontDesign(changed ? .rounded : .monospaced)

裁剪(Trim) trim 修饰符通常与 Shape 结构体一同运用,比方 Circle, Rectangle, Path 等,它的功用是依据开始和完毕点来截取形状的一部分。所以能够动画化这些开始和完毕点的参数来创造出动态的作用,例如一个进度条。

Circle()
	.trim(from: 0, to: progress)
	.stroke(.blue, lineWidth: 25)
	.animation(.linear(duration: 2), value: progress)

Numeric Property:动画的生成办法

网格布局(Grid)

当在网格布局中,依据特定条件显示或隐藏某个网格视图,也能够增加一些动画作用,例如完成一个帷幕。

Grid {
	GridRow {
		if changed {
			Color.blue
		}
		Color.green
		if changed {
			Color.red
		}
	}
}.animation(.easeIn, value: changed)

Numeric Property:动画的生成办法