Посетите мой блог www.fatbobman.com[1] Получите лучший опыт чтения
Большинство новичков будут поражены первым SwiftUI Возможность легко добиться различных анимационных эффектов, но через некоторое время использования они найдут SwiftUI В анимации не так легко ориентироваться, как кажется. Разработчикам часто приходится сталкиваться: как двигаться, как двигаться, что можно двигать, почему нет, почему оно так движется, как не допустить его перемещения и т. д. верно SwiftUI Недостаточное понимание логики обработки анимации является основной причиной вышеперечисленных проблем. В этой статье мы постараемся Механизм анимации SwiftUI представлен, чтобы помочь каждому лучше учиться и осваивать работу. SwiftUI анимацию для создания удовлетворительных интерактивных эффектов.
Прежде чем читать эту статью, читатели должны иметь SwiftUI Опыт использования программирования анимации в SwiftUI Иметь определенное понимание основных методов использования анимации. Можно найти в Получите весь код для этой статьи здесь[2]
SwiftUI использует декларативный синтаксис для описания представления пользовательского интерфейса в разных состояниях, и то же самое справедливо и для анимации. Официальная документация определяет анимацию SwiftUI (Анимации) как: создание плавного перехода из одного состояния в другое.
В SwiftUI мы не можем приказать представлению перемещаться из одной позиции в другую. Чтобы добиться вышеуказанного эффекта, нам нужно объявить положение представления в состоянии A и положение состояния B. Когда состояние изменяется с помощью When. A переходит к B, SwiftUI будет использовать указанную функцию алгоритма для предоставления данных, необходимых для создания плавного перехода для конкретного виджета (если виджет анимируемый).
В SwiftUI для реализации анимации требуются следующие три элемента:
animationThreeElements
SwiftUI дает функции алгоритма временной кривой запутанное имя — Анимация. Возможно, было бы более уместно назвать ее временной кривой или кривой анимации (например, CAMediaTimingFunction в Core Animation).
Эта функция определяет ритм анимации как временную кривую и преобразует данные начальной точки в данные конечной точки вдоль временной кривой.
Text("Hello world")
.animation(.linear(duration: 0.3), value: startAnimation)
.opacity(startAnimation ? 0 : 1)
функция временной кривой( Animation )linear(duration:0.3)
означает в 0.3 Линейное преобразование данных за секунды (в данном случае из 0 приезжать 1)。
linear_value_sheet
image-20220504111032123
функция временной кривой( Animation )easeInOut(duration:0.3)
переписыватьсяизчисловое изменение:
easeInOut_value_sheet
image-20220504110821144
Функция временной кривойз работает только для интерполяционного преобразования данных.,Что касается использования для интерполяции данных, Анимируемые частииз Работа。
Только если оно соответствует VectorArithmetic Тип данных протоколиз можно использовать в функции. временной кривой。SwiftUI По умолчанию предоставляет нам несколько типов данных, таких как: Float, Double, CGFloat. ждать.
Text("Hello world")
.animation(.linear(duration: 0.3), value: startAnimation)
.opacity(startAnimation ? 0 : 1) // Double тип,соответствовать VectorArithmetic протокол
Другие типы данных также могут предоставлять данные анимации для анимируемых компонентов, реализуя требования протокола VectorArithmetic.
Majid из The magic of Animatable values in SwiftUI[3] В этой статье мы покажем, как сделать так, чтобы пользовательские типы удовлетворяли требованиям. VectorArithmetic протокол.
Только в той или иной форме будет функционировать временной кривая (анимация) и после того, как связана определенная (или множественная) зависимость, SwiftUI будет в штате( Быть связанным из-за зависимостей ) для анимации генерирует данные интерполяции при их изменении. Связанные способы: видмодификатор animation
или глобальная функция withAnimation
。
Аномалии анимации SwiftUI (не соответствующие ожиданиям разработчиков) часто связаны с неправильными методами ассоциации и неправильными местоположениями ассоциации.
Код один:
@State var animated = false
VStack {
Text("Hello world")
.offset(x: animated ? 200 : 0)
.animation(.easeInOut, value: animated) // animation издляиспользовать домен для текущего уровня вида и его дочерних видов
Text("Fat")
.offset(x: animated ? 200 : 0)
}
single_animation_2022-05-04_14.08.25.2022-05-04 14_09_34
Код второй:
VStack {
Text("Hello world")
.offset(x: animated ? 200 : 0)
Text("Fat")
.offset(x: animated ? 200 : 0)
}
.animation(.easeInOut, value: animated)
both_animation_2022-05-04_14.05.54.2022-05-04 14_06_58
Вышеупомянутые два фрагмента кода связаны с тем, что animation
Расположение из в коде существования различно, в результате чего существует связанная с ним из зависимостей ( animated
)когда происходят изменения,анимацияиз ХОРОШОдляизменил ситуацию。существоватькод одинсередина,только Hello world
Произведет плавную анимацию, код 2; Hello world
и Fat
Оба будут производить плавную анимацию.
То же, что и все SwiftUI Как и извидмодификатор, расположение существующего объекта в коде определяет область применения объекта модификаторизпользования. animation
Объект ограничен уровнем, на котором он расположен, и его дочерними узлами.
На приведенные выше два фрагмента кода нет правильного или неправильного ответа.。существовать в некоторых ситуациях,Нам может понадобиться существование, когда определенная зависимость (статус) изменится.,Все зависимости от этого проектаиз内容都产生平滑анимация(Например Код два),существуют в других сценах,可能又仅需部分内容产生平滑анимация(Напримеркод один),проходить Корректирование animation
из положения вы можете получить желаемый эффект.
SwiftUI Предусмотрены две версии animation
Модификатор:
// Версия первая, без указания конкретных зависимостей
func animation(_ animation: Animation?) -> some View
// Версия2, укажите конкретные зависимости, метод useiz используется в предыдущем разделе кода.
func animation<V>(_ animation: Animation?, value: V) -> some View where V : Equatable
Первый способ SwiftUI 3.0 помечен как устаревший, он находится в старой версии SwiftUI Один из виновников аномалий анимации. Эта Версияиз animation
会и Местосуществоватьвидуровеньи Долженвидуровеньиздочерний узелизвсе зависимости进ХОРОШО状态ассоциация。видиэтодочерний узелсерединаизлюбые зависимости меняются,Все будет удовлетворять условиям расчета интерполяции анимации.,并анимация数据传递Даватьделатьиспользоватьв пределах досягаемости(видиэтодочерний узел)из Место有Анимируемые части。
например,由于下面代码серединаиз animation
Нет отображения конкретных зависимостей,поэтому,После нажатия кнопки,Положение и цвет обеспечат плавную анимацию.
struct Demo2: View {
@State var x: CGFloat = 0
@State var red = false
var body: some View {
VStack {
Spacer()
Circle()
.fill(red ? .red : .blue)
.frame(width: 30, height: 30)
.offset(x: x)
.animation(.easeInOut(duration: 1)) // Связанные одновременно x и red две зависимости
// .animation(.easeInOut(duration: 1), value: x) // Рекомендуется использовать использовать, чтобы связать их отдельно.
// .animation(.easeInOut(duration: 1), value: red)
Spacer()
Button("Animate") { // 闭包середина改变了две зависимостиизценить
if x == 0 {
x = 100
} else {
x = 0
}
red.toggle()
}
}
.frame(width: 500, height: 300)
}
}
Используя animation<V>(_ animation: Animation?, value: V)
Версия,У нас может быть только одно из двух, положение и цвет, для создания плавной анимации. существовать При одновременном изменении нескольких зависимостей,animation(_ animation: Animation?)
Очень легко создать ненужную анимацию, и это основная причина, по которой от нее отказались.
В этом примере используйте withAnimation
Это также может привести к тому же эффекту, что и приезжать, подвергать существованию. withAnimation
Измените определенные зависимости в замыкании, чтобы добиться индивидуального управления анимацией.
struct Demo2: View {
@State var x: CGFloat = 0
@State var red = false
var body: some View {
VStack {
Spacer()
Circle()
.fill(red ? .red : .blue)
.frame(width: 30, height: 30)
.offset(x: x)
Spacer()
Button("Animate") {
if x == 0 {
x = 100
} else {
x = 0
}
withAnimation(.easeInOut(duration: 1)) { // Только цвета будут переходить плавно
red.toggle()
}
}
}
.frame(width: 500, height: 300)
}
}
Осторожные друзья могут Наружно существовать, как указано выше, когда им нужно работать. временной кривой При создании ассоциаций я использовал словоиспользоватьиз "зависимость" вместо "статус", что потому чтовидизстатус заключается в том, что он имеетиз全部Зависимостииз Общая презентация。witAnimation
Позволяет нам использовать один и тот же анимируемый виджет в разных настройках зависимостей. временной кривой。
struct Demo4: View {
@State var x: CGFloat = 0
@State var y: CGFloat = 0
var body: some View {
VStack {
Spacer()
Circle()
.fill(.orange)
.frame(width: 30, height: 30)
.offset(x: x, y: y) // x、y Связано с различными функциями временной кривой
Spacer()
Button("Animate") {
withAnimation(.linear) {
if x == 0 { x = 100 } else { x = 0 }
}
withAnimation(.easeInOut) {
if y == 0 { y = 100 } else { y = 0 }
}
}
}
.frame(width: 500, height: 500)
}
}
dual_timing_function_2022-05-04_15.25.59.2022-05-04 15_27_18
потому что offset(x: x, y: y)
серединаиз x и y проходить withAnimation
Связано с различными функциями временной кривая, поэтому анимация существует в процессе, горизонтальная ось и вертикальная ось в режиме движения отличаются ( x линейна из, y Он медленно входит и выходит из).
в настоящий момент
animation<V>(_ animation: Animation?, value: V)
Различные ассоциации зависимостей для одного и того же анимируемого виджета пока не поддерживаются. временной кривой
Помимо возможности связывать различные типы функций временной кривойснаружи,SwiftUI Также позволяет функцию ассоциации временной кривой имеет разную продолжительность. Когда один и тот же компонент анимации связан с разными зависимостями с разными функциями продолжительности ( duration Несовместимо или включено repeatForever ),Логика интерполяции и вычислений станет более сложной,Различные комбинации будут иметь разные результаты.,Будьте осторожны при использовании.
Button("Animate") {
withAnimation(.linear) {
if x == 0 { x = 100 } else { x = 0 }
}
withAnimation(.easeInOut(duration: 1.5)) {
if y == 0 { y = 100 } else { y = 0 }
}
}
different_duration_2022-05-09_12.44.24.2022-05-09 12_45_01
существовать SwiftUI Не предусмотрено animation<V>(_ animation: Animation?, value: V)
(иконкретные зависимостиассоциация)модификаторчас,withAnimation
по сравнению с animation(_ animation: Animation?)
Или Может быть, это лучший выбор, по крайней мере, он может дать понять, что конкретная функция зависимостей временной связанное с электричеством.
Однако, если это не необходимо (например, вам нужно связать разные функции временной кривой), приоритет следует отдавать использованиюиспользовать animation<V>(_ animation: Animation?, value: V)
. Это потому что чтохотя withAnimation
Зависимости можно указать, но этого не хватает. animation(_ animation: Animation?, value: V)
из Размер позиции кода, withAnimation
Повлияет на отображение всех зависимостей, связанных с извидом, например, его сложно использовать. withAnimation
Реализовать код одиниз Эффект。
Еще одна вещь, на которую следует обратить внимание: использовать withAnimation
Когда зависимость должна быть явно включена в замыкание, в противном случае withAnimation
Воля不起делатьиспользовать。Например:
struct Demo3: View {
@State var items = (0...3).map { $0 }
var body: some View {
VStack {
Button("In withAnimation") {
withAnimation(.easeInOut) {
items.append(Int.random(in: 0...1000))
}
}
Button("Not in withAnimation") { // делатьиспользовать Array из Расширенный метод
items.appendWithAnimation(Int.random(in: 0...1000), .easeInOut)
}
List {
ForEach(items, id: \.self) { item in
Text("\(item)")
}
}
.frame(width: 500, height: 300)
}
}
}
extension Array {
mutating func appendWithAnimation(_ newElement: Element, _ animation: Animation?) {
withAnimation(animation) {
append(newElement)
}
}
}
Хотя,существовать Array из Расширенный метод appendWithAnimation
используется в withAnimation
, но потому что withAnimation
из闭包серединабез有包含идентификацияиз Зависимости,поэтому не активируется Механизм анимации SwiftUI。
Связывание конкретных зависимостей,только что завершенонастраиватьанимация开启条件(конкретные зависимости发生改变)иобозначение插ценить算Закон这一步骤。О том, как получить выгодуиспользовать这些анимация数据(插ценить数据)生成анимация,Это вызваноиконкретные зависимостиассоциацияиз Анимируемые части Решатьиз。
проходитьследовать Animatable соглашение позволяет View или ViewModifier Возможность получения данных анимации ( AnimatableModifier устарел). много SwiftUI из官方часть都уже预先满足了Долженпротокол,Например:offset
、frame
、opacity
、fill
ждать.
Animatable Требования к протоколизму очень просты, достаточно реализовать вычисляемое свойство animatableData
public protocol Animatable {
/// The type defining the data to animate.
associatedtype AnimatableData : VectorArithmetic
/// The data to animate.
var animatableData: Self.AnimatableData { get set }
}
Обратите внимание, что в соглашении указано animatableData
этот тип должен удовлетворять VectorArithmetic протокол,Это потому что чтофункция временной может быть удовлетворен только VectorArithmetic протоколизтип Выполнить интерполяционные расчеты。
когда Анимируемые частиассоциацияиз Зависимости Когда происходят изменения, SwiftUI Воляпроходитьобозначениеизфункция временной кривой Выполнить интерполяционные расчеты и постоянно корректировать ассоциации зависимостей с анимированными виджетами. animatableData
свойство.
struct AnimationDataMonitorView: View, Animatable {
static var timestamp = Date()
var number: Double
var animatableData: Double { // SwiftUI существовать渲染час Обнаружить Долженвиддля Анимируемый, он будет существовать после изменения статуса в соответствии с функцией. временной кривая обеспечивает непрерывную корректировку значений animableData
get { number }
set { number = newValue }
}
var body: some View {
let duration = Date().timeIntervalSince(Self.timestamp).formatted(.number.precision(.fractionLength(2)))
let currentNumber = number.formatted(.number.precision(.fractionLength(2)))
let _ = print(duration, currentNumber, separator: ",")
Text(number, format: .number.precision(.fractionLength(3)))
}
}
struct Demo: View {
@State var startAnimation = false
var body: some View {
VStack {
AnimationDataMonitorView(number: startAnimation ? 1 : 0) // Объявите форму из в двух состояниях
.animation(.linear(duration: 0.3), value: startAnimation) // Связанные зависимости временной кривой
Button("Show Data") {
AnimationDataMonitorView.timestamp = Date()
startAnimation.toggle() // Изменение зависимостей
}
}
.frame(width: 300, height: 300)
}
}
Приведенный выше код ясно показывает этот процесс.
Процесс декларирования:
Процесс анимации:
В выводе на печать мы видим, как прибывают различные временные узлы из интерполяционных данных:
animatable_data_demo_2022-05-04_17.32.01.2022-05-04 17_34_12
Таблица числовых изменений функции временной кривой, приведенная выше, генерируется этим кодом.
Порекомендуйте несколько представлений Animatable использовать Законизсообщение в блоге:
Advanced SwiftUI Animations – Part 1: Paths[4]
AnimatableModifier in SwiftUI[5]
Если анимируемый элемент имеет несколько изменяемых зависимостей, вам необходимо animatableData
установлен на AnimatablePair напечатай так, чтобы SwiftUI Вы можете передавать данные интерполяции анимации, принадлежащие различным зависимостям.
AnimatablePair тип соответствует VectorArithmetic протокол, и его тип упаковки также должен соответствовать цифровому типу. VectorArithmetic протокол
Следующий код демонстрирует AnimatablePair Как сделать использование и как увидеть две разные функции временной кривые интерполированные данные:
struct AnimationDataMonitorView: View, Animatable {
static var timestamp = Date()
var number1: Double // изменится
let prefix: String
var number2: Double // изменится
var animatableData: AnimatablePair<Double, Double> {
get { AnimatablePair(number1, number2) }
set {
number1 = newValue.first
number2 = newValue.second
}
}
var body: some View {
let duration = Date().timeIntervalSince(Self.timestamp).formatted(.number.precision(.fractionLength(2)))
let currentNumber1 = number1.formatted(.number.precision(.fractionLength(2)))
let currentNumber2 = number2.formatted(.number.precision(.fractionLength(2)))
let _ = print(duration, currentNumber1, currentNumber2, separator: ",")
HStack {
Text(prefix)
.foregroundColor(.green)
Text(number1, format: .number.precision(.fractionLength(3)))
.foregroundColor(.red)
Text(number2, format: .number.precision(.fractionLength(3)))
.foregroundColor(.blue)
}
}
}
struct Demo: View {
@State var startNumber1 = false
@State var startNumber2 = false
var body: some View {
VStack {
AnimationDataMonitorView(
number1: startNumber1 ? 1 : 0,
prefix: "Hi:",
number2: startNumber2 ? 1 : 0
)
Button("Animate") {
AnimationDataMonitorView.timestamp = Date()
withAnimation(.linear) {
startNumber1.toggle()
}
withAnimation(.easeInOut) {
startNumber2.toggle()
}
}
}
.frame(width: 300, height: 300)
}
}
animatable_dual_data_demo_2022-05-04_18.17.39.2022-05-04 18_18_51
SwiftUI существование очень умно работает при передаче интерполированных данных и меняет только зависимости animatableData
Передать Давать анимационные элементы. Например, в приведенном выше коде параметры prefix
не меняется, поэтому существование синтезируется AnimatablePair
Данные будут автоматически пропущены и будут только синтезированы number1
и number2
。
Если вам нужно передать больше параметров, вы можете вложить их, используя тип AnimatablePair, например:
AnimatablePair<CGFloat, AnimatablePair<Float, AnimatablePair<Double, CGFloat>>>
// newValue.second.second.first.
использовать SwiftUI изофициальный язык для описания【Свяжите функции временной кривой с состояниямииз Процесс] должно быть: длявид Декларация транзакции ( Transaction)。Транзакции обеспечивают большую гибкостьизфункция кривойтипнастраивать方式以及анимация开关и临час状态标识。
Любой модификатор animation
Или глобальная функция withAnimation
, фактически указаны в существованиивид Transaction из метода ярлыка, который внутренне соответствует transaction
и withTransaction
。
например,withAnimation
Фактически, соответствующее из:
withAnimation(.easeInOut){
show.toggle()
}
// Соответствует для
let transaction = Transaction(animation: .easeInOut)
withTransaction(transaction) {
show.toggle()
}
animation(_ animation: Animation?)
То же самоепроходить Transaction Для достижения из:
// Код взят из swiftinterface
extension SwiftUI.View {
@_disfavoredOverload @inlinable public func animation(_ animation: SwiftUI.Animation?) -> some SwiftUI.View {
return transaction { t in
if !t.disablesAnimations {
t.animation = animation
}
}
}
}
Transaction Предоставить из disablesAnimations и isContinuous Это может помочь разработчикам лучше управлять анимацией, например:
Text("Hi")
.offset(x: animated ? 100 : 0)
.transaction {
if position < 0 || position > 100 {
$0.animation = .easeInOut
} else {
$0.animation = .linear
}
}
transaction
издляиспользовать область действия и ассоциации из зависимостей и не указывать конкретные зависимости Версияизanimation
Это то же самоеиз,Он не имеет возможности связывать конкретные зависимости。
// не означает только x Ассоциация: если в пределах области домена изменяются другие зависимости, анимация также будет создана.
.transaction {
if x == 0 {
$0.animation = .linear
} else {
$0.animation = nil
}
}
// Эквивалентно
.animation(x == 0 ? .linear : nil)
struct Demo: View {
@State var position: CGFloat = 40
var body: some View {
VStack {
Text("Hi")
.offset(x: position, y: position)
.animation(.easeInOut, value: position)
Slider(value: $position, in: 0...150)
Button("Animate") {
var transaction = Transaction() // Не указана функция временной кривая, сохранит первоначальные настройки (в данном случае для easeInOut)
if position < 100 { transaction.disablesAnimations = true }
withTransaction(transaction) { // withTransaction Может запрещать оригинальные транзакции из функции временной кривой(由 animation связанный), но не может быть заблокирован transaction Связанная функция временной кривой
position = 0
}
}
}
.frame(width: 400, height: 500)
}
}
withTransaction
(проходитьнастраивать disablesAnimations заблокировать анимацию) +animation<V>(_ animation: Animation?, value: V)
Это более зрелый матч.
struct Demo: View {
@GestureState var position: CGPoint = .zero
var body: some View {
VStack {
Circle()
.fill(.orange)
.frame(width: 30, height: 50)
.offset(x: position.x, y: position.y)
.transaction {
if $0.isContinuous {
$0.animation = nil // При перетаскивании не настраиватьфункцию временной кривой
} else {
$0.animation = .easeInOut(duration: 1)
}
}
.gesture(
DragGesture()
.updating($position, body: { current, state, transaction in
state = .init(x: current.translation.width, y: current.translation.height)
transaction.isContinuous = true // При перетаскивании настраивается логотип
})
)
}
.frame(width: 400, height: 500)
}
}
isContinuous_2022-05-06 11.26.20.2022-05-06 11_27_42
Слайдер При перетаскивании существование будет автоматически настраиваться как Непрерывное, но фактическое измерение не соответствует описанию. Но мы можем сами существовать в коде в 利использовать его настройку временного статуса.
кроме того,существовать в некоторых ситуациях,Вы можете использовать транзакцию для получения или настройки информации об анимации из,нравиться:
Например, установите транзакцию для привязки:
struct Demo: View {
@State var animated = false
let animation: Animation?
var animatedBinding: Binding<Bool> { // Генерировать содержит указанные Transaction из Binding тип
let transaction = Transaction(animation: animation)
return $animated.transaction(transaction)
}
var body: some View {
VStack {
Text("Hi")
.offset(x: animated ? 100 : 0)
Toggle("Animated", isOn: animatedBinding) // Анимационные эффекты будут автоматически возникать при нажатии
}
.frame(width: 400, height: 500)
}
}
PlaygroundPage.current.setLiveView(Demo(animation: .easeInOut))
binding_transaction_2022-05-06_11.33.10.2022-05-06 11_34_38
Circle()
.fill(red ? .red : .blue)
.animation(.easeInOut(duration: 1),value: red) // делатьиспользоватьэто .animation(.linear(duration: 3), value: red)
Circle()
.fill(red ? .red : .blue)
.animation(.easeInOut(duration: 1), value: red) // делатьиспользоватьэтоButton("Change red"){
withAnimation(.linear(duration:3)){ // Рабочая область самая большая, что означает максимальное расстояние от анимированного компонента.
red.toggle()
}
}
// Первый метод и конкретная ассоциация зависимостей более подходят, когда есть только две ситуации.
.animation(red ? .linear : .easeIn , value: red)
// Способ второй, Может обрабатывать больше логики, но не конкретные зависимости.
.transaction{
switch status{
case .one:
$0.animation = .linear
case .two:
$0.animation = .easeIn
case .three:
$0.animation = nil
}
}
// Метод 3 поддерживает сложную логику и связан с конкретными состояниями.
var animation:Animation?{
// Даже если в замыкании есть несколько разных зависимостей, это не повлияет animation Только указывает, что из зависит от атрибута связанныйиз.
switch status{
case .one:
$0.animation = .linear
case .two:
$0.animation = .easeIn
case .three:
$0.animation = nil
}
}
.animation(animation , value: status)
// Способ 4, объем работ большой
var animation:Animation?{
switch status{
case .one:
$0.animation = .linear
case .two:
$0.animation = .easeIn
case .three:
$0.animation = nil
}
}
withAnimation(animation){
...
}
// Способ пятый, объем работ большой
var animation:Animation?{
switch status{
case .one:
$0.animation = .linear
case .two:
$0.animation = .easeIn
case .three:
$0.animation = nil
}
}
var transaction = Transaction(animation:animation)
withTransaction(transaction){
...
}
// ждатьждать
SwiftUI из переходатип( AnyTransition ) для Анимируемых частиизпереупаковка。когда状态из Изменение приводит квиддеревоиз веткиКогда происходят изменения, SwiftUI Воляделатьиспользоватьего пакетиз Анимируемые частиверновид进ХОРОШОанимация处理。
Чтобы осуществить переход, вам также необходимо соответствовать трем элементам анимации SwiftUI.
struct TransitionDemo: View {
@State var show = true
var body: some View {
VStack {
Spacer()
Text("Hello")
if show {
Text("World")
.transition(.slide) // Оживляемые части (среди них существует упаковка)
}
Spacer()
Button(show ? "Hide" : "Show") {
show.toggle()
}
}
.animation(.easeInOut(duration:3), value: show) // Создайте связанные зависимости и установите функцию временной кривой
.frame(width: 300, height: 300)
}
}
Следовательно, То же, что и всеиз SwiftUI Как и анимированные элементы, переходы также поддерживают прерываемую анимацию. Например, когда выполняется анимация появления существующего объекта, измените статус show Вернуться к true ,SwiftUI сохранит текущее состояние ветки (не будет воссоздавать его),См. пример, прикрепленный к этой статье).
существовать SwiftUI середина实现Пользовательский переход не сложный, если только вам не нужно создавать крутые визуальные эффекты, что возможно в большинстве случаев. SwiftUI уже Предоставить из Анимируемые частикомбинированный。
struct MyTransition: ViewModifier { // Пользовательский переходиз包装верно象要求соответствовать ViewModifier протокол
let rotation: Angle
func body(content: Content) -> some View {
content
.rotationEffect(rotation) // Анимируемые части
}
}
extension AnyTransition {
static var rotation: AnyTransition {
AnyTransition.modifier(
active: MyTransition(rotation: .degrees(360)),
identity: MyTransition(rotation: .zero)
)
}
}
struct CustomTransitionDemo: View {
@State var show = true
var body: some View {
VStack {
VStack {
Spacer()
Text("Hello")
if show {
Text("World")
.transition(.rotation.combined(with: .opacity))
}
Spacer()
}
.animation(.easeInOut(duration: 2), value: show) // существовать Заявление здесь,Button из текста не будет иметь эффекта анимации
Button(show ? "Hide" : "Show") {
show.toggle()
}
}
// .animation(.easeInOut(duration: 2), value: show) // Если здесь указано существование, то верно Button из текста также имеет влияние, результат такой, как показано ниже
.frame(width: 300, height: 300)
.onChange(of: show) {
print($0)
}
}
}
custom_transition_2022-05-04_19.55.51.2022-05-04 19_56_55
Хотя MyTransition На первый взгляд не совпадает Animatable протокол, но откуда rotationEffect
(можно анимировать ViewModifier ) помог нам добиться эффекта анимации.
кроме того,我们也可以делатьиспользоватьсоответствовать Animatable из GeometryEffect( соответствовать ViewModifier и Animatable ) для создания сложных перехода Эффект。
Более крутоиз перехода定制方Закон请阅读 Javier из Статей Advanced SwiftUI Transitions[6] 。
Теперь SwiftUI из анимации предназначен для создания плавного перехода из одного состояния в другое, тогда мы должны иметь правильное понимание состояния (зависимости) от изменений, которые могут привести к таким результатам.
SwiftUI Существует два способа определения виддиспользовать: структурная идентификацияи Явная идентификация. В анимации разные методы маркировки требуют разных точек внимания.
Хотя в следующих двух фрагментах кода используются структурные теги вида ( 以Местосуществоватьизвидуровень位置итип进ХОРОШО标识 ), но намерения у них совершенно разные.
// код один
if show {
Text("Hello") // Филиал один
} else {
Text("Hello") // Филиал второй
.offset(y : 100)
}
// Код два
Text("Hello")
.offset(y : show ? 100 : 0) // Два статуса для одного и того же вида
код одинописалсуществовать Зависимости show Когда происходят изменения, SwiftUI Волясуществовать Филиал одини Филиал второйсередина进ХОРОШО切换。В этом случае,Мы можем применить transition Установить Филиал отдельно одини Филиал второйиз进出场анимация( Вы также можете выбрать ветку и установить ее равномерно снаружи. Transition ),但无Закон要求Филиал одиндвигатьсяприезжать Филиал второйвыше。
// код один
VStack{ // Контейнер макета makeuse
if !show {
Text("Hello") // Филиал один
.transition(.scale)
} else {
Text("Hello") // Филиал второй
.offset(y : 100)
.transition(.move(edge: .bottom))
}
}
.animation(.easeIn, value: show)
status_for_transition_2022-05-09_15.11.26.2022-05-09 15_12_10
В приведенном выше коде есть две вещи, на которые следует обратить внимание:
animation
,потому чтотолькосуществовать if - else
оператор изOutside, только для использования домена show
решение действительноКод дваописалсуществовать show Когда происходят изменения, один и тот же видиз находится в разных состояниях ( offset
из y значения разные). Следовательно, по сравнению с существующей функцией временной После привязки кривой статус вида изменится с первого ( y : 0 ) из позиции движения приехать в штат два ( y : 100) из положения.
// Код два
Text("Hello")
.offset(y : show ? 100 : 0) // Два статуса для одного и того же вида
.animation(.spring(), value: show)
status_offset_2022-05-09_15.14.12.2022-05-09 15_14_45
О видизструктурной Содержание идентификации можно найти в ViewBuilder Исследования (Часть 2) —— Учитесь на подражании[7]
существовать SwiftUI В языке существует два способа установить явную идентификацию представлений: ForEach. и id модификатор.
struct Demo: View {
@State var items = (0...100).map { $0 }
var body: some View {
VStack {
List {
ForEach(items,id: \.self) { item in // id: \.self делатьиспользовать element делатьдля identifier
Text("\(item)")
}
}
.animation(.easeInOut, value: items)
Button("Remove Second") {
items.remove(at: 1)
}
Button("add Second") { // существовать items В них появятся одни и те же элементы, разрушающие уникальность логотипа.
items.insert(Int.random(in: 0...100), at: 1)
}
}
.frame(width: 400, height: 500)
}
}
items представляет собой массив целых чисел. Выше из кода используется в \.self
Основание для идентификации. Это означает, что когда в массиве появляются два одинаковых элемента (нажмите кнопку «Добавить»), SwiftUI не сможет правильно определить наши намерения —— С каким элементом вы хотите работать (то же значение означает тот же идентификатор). Таким образом, существует высокая вероятность того, что что неправильно распознает видиз, что приводит к аномалиям анимации. В анимации ниже, когда появляется тот же элемент When, SwiftUI Предупреждение вынесено.
foreach_id_error_2022-05-09_16.41.18.2022-05-09 16_43_22
для ForEach Оснащен уникальными идентификаториз Источники данных могут эффективно избежатьпоэтомуи производитьизанимация异常。
struct Item: Identifiable, Equatable {
let id = UUID() // уникальный идентификатор
let number: Int
}
struct Demo: View {
@State var items = (0...100).map { Item(number: $0) }
var body: some View {
VStack {
List { // в настоящий момент无Закондля List внутрииз item обозначение transition , еще один, не очень совместимый с оригинальным управлением SwiftUI Анимация из примера. Заменить на ScrollView Может поддерживать изображение item из перехода
ForEach(items, id: \.id) { item in
Text("\(item.number)")
}
}
.animation(.easeInOut, value: items) // List Вместо этого используйте эту ассоциацию для обработки анимации. ForEach
Button("Remove Second") {
items.remove(at: 1)
}
Button("add Second") {
items.insert(Item(number: Int.random(in: 0...100)), at: 1)
}
}
.frame(width: 400, height: 500)
}
}
модификатор id
это еще один видид, который позволяет отображать логотип. Когда модификатор id изценить Когда происходят изменения, SwiftUI Удалите его какиспользоватьизвид из текущей структуры извида и создайте новый извид, добавив приезжать к исходной иерархической позиции существуизвида. Таким образом, вы также можете влиять на прибытие с помощью анимированных виджетов. AnyTransaction 。
struct Demo: View {
@State var id = UUID()
var body: some View {
VStack {
Spacer()
Text("Hello \(UUID().uuidString)")
.id(id) // id когда происходят изменения Исходный вид удаляется и вставляется новый вид.
.transition(.slide)
.animation(.easeOut, value: id)
Button("Update id") {
id = UUID()
}
Spacer()
}
.frame(width: 300, height: 300)
}
}
id_transition_2022-05-09_16.58.42.2022-05-09 16_59_17
SwiftUI в настоящий моментсуществовать处理因 id
ценить变化и производитьизвид Конвертироватьиз Эффект一般,Поддерживает только некоторые из перехода Эффект。
Информацию о явной идентификации см. оптимизациясуществовать SwiftUI List Отображение большого набора данных об эффективности реагирования [8] одно предложение
Теоретически, как только вы освоите Механизм анимации Благодаря SwiftUI вы сможете легко управлять кодом и свободно управлять анимацией. Но реальность жестока. потому что SwiftUI Это молодой фреймворк, и многие базовые реализации все еще полагаются на другие фреймворки. API из Пакет,поэтому Во многих ситуацияхизделатьиспользовать Опыт все еще полон фрагментации。
SwiftUI Многие элементы управления в Китае изготовлены из использованных материалов. UIKit( AppKit ) элемент управления инкапсулирован для реализации, а текущая обработка анимации отсутствует.
существовать ViewBuilder Исследования (Часть 2) —— Учитесь на подражании[9] одно В предложении мы показываем SwiftUI из Text 是如何处理этоиз Расширенный методиз。хотя UIViewRepresentableContext Базовые элементы управления уже предоставляют необходимые элементы управления анимацией. Transaction информация, но в настоящее время SwiftUI Официальные органы управления на это не отреагировали. Например, следующий код не может обеспечить плавный переход.
Text("Hello world")
.foregroundColor(animated ? .red : .blue) // на основе Инкапсуляция UIKit (AppKit) элементов управления и расширений практически невозможна для достижения управления анимацией
.font(animated ? .callout : .title3)
Хотя мы можем предложить некоторые способы решения этих проблем.,Но это не только увеличит рабочую нагрузку,При этом часть производительности будет потеряна.
Paul Hudson существовать How to animate the size of text[10] одно предложение демонстрирует, как создать плавную анимацию перехода между размерами и размерами шрифта.
Следующий код может помочь Text добиться плавного перехода цвета текста.
extension View {
func animatableForeground(_ color: Color) -> some View {
self
.overlay(Rectangle().fill(color))
.mask {
self
.blendMode(.overlay)
}
}
}
struct Demo: View {
@State var animated = false
var body: some View {
VStack {
Button("Animate") {
animated.toggle()
}
Text("Hello world")
.font(.title)
.animatableForeground(animated ? .green : .orange)
.animation(.easeInOut(duration: 1), value: animated)
}
}
}
animatable_color_of_text_2022-05-05_14.35.19.2022-05-05 14_36_15
по сравнению с Управление анимацией, Проблемы с анимацией контроллера решить еще сложнее. Навигациявиев, табвиев, лист Компонент ожидания вообще нельзя найти в собственном решении для управления анимацией, даже если его скорректировать. UIKit( AppKit ) Код может вносить лишь незначительные изменения в анимацию (например, управлять включением анимации). Средства и эффекты равны SwiftUI Существует огромный пробел в возможностях встроенной анимации.
с нетерпением надеюсь SwiftUI существуют могут совершить прорыв в этом отношении. Помимо логики анимации, вы можете изменить SwiftUI экстернализация, лучше всего также AnyTransition использовать в контроллере в настройках перехода.
Почти неизбежно, что адаптивная анимация будет немного менее отзывчивой, чем императивная. SwiftUI существуют Некоторые усилия были предприняты для оптимизации производительности анимации (например: Canvas, DrawingGroup ). Я надеюсь, что по мере дальнейшей оптимизации кода и совершенствования оборудования этот разрыв будет становиться все меньше и меньше.
Механизм анимации Дизайн SwiftUI по-прежнему на высоте.,Я считаю, что по мере того, как степень завершенности продолжает улучшаться,Разработчики могут добиться лучших интерактивных эффектов с меньшим количеством кода.
Надеюсь, эта статья может быть вам полезна. Также приветствуем вас Twitter[11]、 Дискорд-канал[12]или Блог нижеиздоска объявленийи我进ХОРОШО交流。
[1] www.fatbobman.com: https://www.fatbobman.com
[2] Получите все коды для этой статьи здесь: https://github.com/fatbobman/BlogCodes/tree/main/Animation
[3] The magic of Animatable values in SwiftUI: https://swiftwithmajid.com/2020/06/17/the-magic-of-animatable-values-in-swiftui/
[4] Advanced SwiftUI Animations – Part 1: Paths: https://swiftui-lab.com/swiftui-animations-part1/
[5] AnimatableModifier in SwiftUI: https://swiftwithmajid.com/2021/01/11/animatablemodifier-in-swiftui/
[6] Advanced SwiftUI Transitions: https://swiftui-lab.com/advanced-transitions/
[7] ViewBuilder Исследования (Часть 2) —— Учитесь на подражании: https://www.fatbobman.com/posts/viewBuilder2/
[8] оптимизациясуществовать SwiftUI List Отображение большого набора данных по эффективности ответа: https://www.fatbobman.com/posts/optimize_the_response_efficiency_of_List/
[9] ViewBuilder Исследования (Часть 2) —— Учитесь на подражании: https://www.fatbobman.com/posts/viewBuilder2/
[10] How to animate the size of text: https://www.hackingwithswift.com/quick-start/swiftui/how-to-animate-the-size-of-text
[11] Twitter: https://twitter.com/fatbobman
[12] Discord Канал: https://discord.gg/ApqXmy5pQJ