SwiftUI перемещает изображение между страницами, анимация

Я пытался несколько дней, но не смог найти решения. Моя цель — переместить изображение из одного места в другое. Но я не хочу, чтобы при перемещении изображение перемещалось только вверх и вниз. Я хочу, чтобы это выглядело так, будто изображение скользит, пока скользит страница. Извините, если я не объяснил это ясно. Когда вы запустите код на своем компьютере, вы поймете анимацию, которую я пытаюсь создать. прошу вашей помощи


struct SwiftUIView: View {
    @State private var showFirstView = true
    @Namespace var namespace
    var body: some View {
        GeometryReader { geometry in
            ZStack {
                if showFirstView {
                    FirstView()
                        .transition(.move(edge: .trailing))
                        .frame(width: geometry.size.width, height: geometry.size.height)
                } else {
                    SecondView()
                        .transition(.move(edge: .leading))
                        .frame(width: geometry.size.width, height: geometry.size.height)
                }
            }
            .animation(.easeInOut(duration: 0.5), value: showFirstView)
        }
        .overlay(alignment: .bottom) {
            Button(action: {
                withAnimation {
                    showFirstView.toggle()
                }
            }) {
                Text("Switch View")
                    .padding()
                    .background(Color.blue)
                    .foregroundColor(.white)
                    .cornerRadius(10)
            }
            .padding()
        }
    }
    
    func FirstView() -> some View {
        ZStack{
            Color.red
                .ignoresSafeArea()
            cusview()
        }
    }
    
    func SecondView() -> some View {
        ZStack {
            Color.green
                .ignoresSafeArea()
            cusview()
                .offset(y: -150)
        }
    }
    
    func cusview() -> some View {
        Image(.image10)
            .resizable()
            .aspectRatio(contentMode: .fill)
            .frame(width: 140, height: 100)
            .matchedGeometryEffect(id: "one", in: namespace)
    }
}
 
#Preview {
    SwiftUIView()
}

Не могли бы вы уточнить, как именно вы хотите, чтобы изображение двигалось, если оно не должно просто скользить вверх и вниз? Допустим, мы находимся на первом (красном) виде. Когда мы переходим ко второму (зеленому) виду, первый вид сдвигается вправо, а второй вид выдвигается слева. Как должно двигаться изображение? Должен ли он также соскользнуть вправо? И что потом?

Benzy Neez 23.08.2024 16:49
Animista - анимация на ходу!
Animista - анимация на ходу!
Если вы веб-дизайнер или разработчик, вы знаете, что добавление анимации на ваш сайт может помочь сделать его более привлекательным и динамичным....
Повысьте уровень своего сайта с помощью анимации CSS и JavaScript: Пошаговое руководство
Повысьте уровень своего сайта с помощью анимации CSS и JavaScript: Пошаговое руководство
Если вы хотите добавить визуальный интерес к своему сайту, то внедрение анимации с помощью CSS и JavaScript может стать отличным способом сделать это....
0
1
67
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Я понятия не имею, почему вы так структурировали свой код или зачем нужна программа чтения геометрии, но это само по себе может быть целым обсуждением.

Проблема, с которой вы столкнулись, связана с тем, что вы применяете переход перемещения ко всему представлению, содержащему изображение. Если вы закомментируете соответствующие переходы для FirstView и SecondView, вы должны увидеть анимацию изображения правильно (кстати, .image10 существует только на вашей стороне, вы можете обновить код, который вы предоставили выше, на что-то более общее, например, на символ/ икона).

if showFirstView {
                    FirstView()
                        // .transition(.move(edge: .trailing))
                        .frame(width: geometry.size.width, height: geometry.size.height)
                } else {
                    SecondView()
                        // .transition(.move(edge: .leading))
                        .frame(width: geometry.size.width, height: geometry.size.height)
                }

Если вам абсолютно необходима анимация скольжения/перемещения, используйте вместо этого асимметричный переход:

if showFirstView {
                    FirstView()
                        .transition(.asymmetric(insertion: .move(edge: .trailing), removal: .opacity))
                        .frame(width: geometry.size.width, height: geometry.size.height)
                } else {
                    SecondView()
                        .transition(.asymmetric(insertion: .move(edge: .leading), removal: .opacity))
                        .frame(width: geometry.size.width, height: geometry.size.height)
                }

Это придаст переходу белый цвет, но я не знаю, как этого избежать. В качестве обходного пути вы можете установить цвет фона для ZStack, который будет либо третьим цветом, либо одним из двух цветов (красным или зеленым):

ZStack {
    Color.blue // <-- Here, set to this to either red or green, or some other colour
        .ignoresSafeArea()
    if showFirstView {
        FirstView()
         ...

Кстати, ZStack по умолчанию расширяется до размера контейнера, поэтому GeometryReader не требует установки размеров кадра. Кроме того, зачем использовать наложение для размещения кнопки, если у вас уже есть ZStack? Ниже показано, как это может быть намного чище:

   var body: some View {
        ZStack {
            if showFirstView {
                FirstView()
            } else {
                SecondView()
            }

            Button("Switch View"){
                showFirstView.toggle()
            }
            .padding()
            .background(Color.blue)
            .foregroundColor(.white)
            .cornerRadius(10)
            .frame(maxHeight: .infinity, alignment: .bottom)
        }
        .animation(.easeInOut(duration: 0.5), value: showFirstView)
    }
Ответ принят как подходящий

Когда я попробовал ваш код в симуляторе (iPhone 15 под управлением iOS 17.5), анимация изображения отключилась:

  • изображение в появившемся виде перемещается по горизонтали и вертикали, поэтому оно появляется по диагонали
  • изображение в исчезающем режиме перемещается только вертикально.

Вы сказали в своем посте:

Я не хочу, чтобы изображение перемещалось только вверх и вниз.

поэтому я спросил в комментарии, как именно вы хотите, чтобы он двигался? Я бы предположил, что существует два возможных способа анимировать изображение, если движение не должно быть только вертикальным:

  1. Изображение в появляющемся виде может оставаться таким, какое оно есть (перемещение по диагонали), но изображение в исчезающем виде также может перемещаться по диагонали, а не только по вертикали.

    Это будет означать, что когда одно изображение перемещается вправо, новое изображение появляется слева на той же высоте. Это по-прежнему будет выглядеть бессвязным, но, по крайней мере, изображение будет иметь постоянное направление движения.

    Я пытался найти способ реализовать это таким образом, но, извините, безуспешно.

  2. Анимация была бы менее бессвязной, если бы изображение плавно перемещалось из одной позиции в другую. Если вы не хотите, чтобы анимация была только вертикальной, тогда изображение должно будет двигаться по какой-то кривой.

Этот второй стиль анимации можно реализовать, отобразив изображение как еще один слой в ZStack и заставив его перемещаться между позициями заполнителей в двух дочерних представлениях.

  • Заполнители используют matchedGeometryPostion с isSource: true (что на самом деле является значением по умолчанию), поэтому они определяют позиции, между которыми перемещаются.

  • Само изображение использует matchedGeometryPosition с isSource: false, чтобы оно соответствовало исходным позициям.

  • По умолчанию matchedGeometryEffect устанавливает размер изображения из активного заполнителя, поэтому нет необходимости устанавливать отдельный frame для изображения. Однако лучше всего, если изображение будет отображаться поверх ZStack, а не как обычный слой. Это гарантирует, что изображение в его естественном размере не приведет к раздуванию размера ZStack в случае, когда размер изображения больше размера экрана.

Изогнутую траекторию можно получить, используя разную длительность анимации для переходов и matchedGeometryEffect:

  • если matchedGeometryEffect быстрее перехода, изображение следует по изогнутой траектории со стороны исчезающего вида
  • если matchedGeometryEffect медленнее, чем переход, изображение следует по изогнутой траектории со стороны появляющегося вида.
  • если на самом деле продолжительность анимации одинакова, изображение просто перемещается вертикально (чего, как вы сказали, вам не нужно).

Обновленный пример ниже показывает, как это работает. Другие изменения и предложения:

  • Два дочерних представления уже занимают все доступное пространство, поскольку оба содержат жадный символ Color. Это означает, что нет необходимости устанавливать frame для этих представлений, поэтому GeometryReader не нужен.

  • Знак GeometryReader не понадобится, даже если дочерние представления не содержат Color. Если вы хотите увеличить изображение до максимального размера, установите frame с помощью maxWidth: .infinity, maxHeight: .infinity.

  • Если у вас есть модификатор .animation, флаг переключать withAnimation не нужно. Фактически, использование отдельных модификаторов .animation вместо withAnimation является ключом к возможности контролировать перемещение изображения.

  • Модификатор .scaledToFill — это более простой способ применения .aspectRatio(contentMode: .fill).

  • Модификатор .foregroundColor устарел, вместо него используйте .foregroundStyle.

  • Модификатор .cornerRadius также устарел, вместо него используйте .clipShape или просто покажите RoundedRectangle на заднем плане.

  • Стиль кнопки .borderedProminent на самом деле является более простым способом добиться того же стиля кнопки. Чтобы он выглядел точно так же, как раньше, вам просто нужно добавить к метке немного отступов.

  • И действие, и метка кнопки могут быть представлены как замыкающие замыкания , что позволяет избежать набора круглых скобок. Сделать это таким образом означает пометить метку для метки, а не метку для действия.

  • Кнопка отображается в виде наложения, чтобы расположить ее внизу ZStack. Это нормально, но вы также можете рассмотреть возможность создания еще одного слоя ZStack и позиционирования, установив maxHeight: .infinity с помощью alignment: .bottom. Результат тот же, так что все зависит только от личных предпочтений.

struct SwiftUIView: View {
    @State private var showFirstView = true
    @Namespace var namespace

    var body: some View {
        ZStack {
            if showFirstView {
                FirstView()
                    .transition(.move(edge: .trailing))
            } else {
                SecondView()
                    .transition(.move(edge: .leading))
            }
        }
        .animation(.easeInOut(duration: 0.8), value: showFirstView)
        .overlay {
            cusview()

                // Try adjusting the duration to get different movements
                .animation(.easeInOut(duration: 0.7), value: showFirstView)
        }
        .overlay(alignment: .bottom) {
            Button {
                showFirstView.toggle()
            } label: {
                Text("Switch View")
                    .padding(.horizontal, 4)
                    .padding(.vertical, 9)
            }
            .buttonStyle(.borderedProminent)
            .padding()
        }
    }

    func FirstView() -> some View {
        ZStack{
            Color.red
                .ignoresSafeArea()
            imagePlaceholder
        }
    }

    func SecondView() -> some View {
        ZStack {
            Color.green
                .ignoresSafeArea()
            imagePlaceholder
                .offset(y: -150)
        }
    }

    private var imagePlaceholder: some View {
        Color.clear
            .frame(width: 140, height: 100)
            .matchedGeometryEffect(id: "one", in: namespace, isSource: true)
    }

    func cusview() -> some View {
        Image(.image10)
            .resizable()
            .scaledToFill()
            .matchedGeometryEffect(id: "one", in: namespace, isSource: false)
    }
}

Animation

@burak Всегда пожалуйста. Если это не та анимация, которую вы искали, возможно, вы могли бы объяснить, что вам на самом деле нужно? В противном случае, если ответ помог, возможно, вы могли бы принять его, нажав галочку? ;)

Benzy Neez 26.08.2024 08:15

Другие вопросы по теме