61. Примеры для MatchedGeometryEffect

61. Примеры для MatchedGeometryEffect

Oleg991

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

Дано

  • Есть 2 фигуры: зеленый круг и черный прямоугольник
  • При нажатии на кнопку "Сменить фигуру" нужно показать другую вьюху
  • Под зеленым кругом и над черным прямоугольником есть спейсер фиксированной высоты (для наглядности разницы в анимациях)

Стартовый код:

struct MatchedGeometryEffectExample: View {
  @State private var showCircle = false
   
  var body: some View {
    ZStack {
      VStack(spacing: 4) {
        noEffectOption // без matchedGeometryEffect
      }
      Button("Сменить фигуру") {
        withAnimation(.linear(duration: 0.5)) {
          showCircle.toggle()
        }
      }
      .buttonStyle(.borderedProminent)
    }
  }
   
  /// Не использует `matchedGeometryEffect`
  @ViewBuilder
  private var noEffectOption: some View {
    if showCircle {
      greenCircle
    } else {
      blackRectangle
    }
  }
   
  private var greenCircle: some View {
    VStack(spacing: 0) {
      Circle()
        .fill(.green)
        .frame(width: 100, height: 100)
      spacerWithFrame
    }
  }
   
  private var blackRectangle: some View {
    VStack(spacing: 0) {
      spacerWithFrame
      Rectangle()
        .frame(width: 300, height: 50)
    }
  }
   
  private var spacerWithFrame: some View {
    Spacer().frame(height: 100)
  }


Анимация без matchedGeometryEffect

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

Дефолтный matchedGeometryEffect

Добавим matchedGeometryEffect с дефолтными настройками. Для этого нам понадобится задать идентификатор и namespace для анимации (код для фигур прежний):

struct MatchedGeometryEffectExample: View {
  @State private var showCircle = false
   
  @Namespace private var defaultAnimation // <- добавили
  private let defaultAnimationId = "defaultAnimation" // <- добавили
   
  var body: some View {
    ZStack {
      VStack(spacing: 4) {
        defaultOption // matchedGeometryEffect без настроек
      }
      Button("Сменить фигуру") {
        withAnimation(.linear(duration: 0.5)) {
          showCircle.toggle()
        }
      }
      .buttonStyle(.borderedProminent)
    }
  }
   
  /// Использует `matchedGeometryEffect`, параметры по умолчанию
  @ViewBuilder
  private var defaultOption: some View {
    if showCircle {
      greenCircle
        .matchedGeometryEffect(
          id: defaultAnimationId,
          in: defaultAnimation
        )
    } else {
      blackRectangle
        .matchedGeometryEffect(
          id: defaultAnimationId,
          in: defaultAnimation
        )
    }
  }
}
Анимация с дефолтными настройками matchedGeometryEffect

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

Настраиваем все кроме источника

В этом примере настроим два дополнительных параметра в модификаторе: properties и anchor:

  @ViewBuilder
  private var customAnchorAndPropertiesOption: some View {
    if showCircle {
      greenCircle
        .matchedGeometryEffect(
          id: animationTwoId,
          in: animationTwo,
          properties: .position, // <- добавили
          anchor: .trailing // <- добавили
        )
    } else {
      blackRectangle
        .matchedGeometryEffect(
          id: animationTwoId,
          in: animationTwo,
          anchor: .leading // <- добавили
        )
    }
  }
Анимация с двумя дополнительными параметрами в matchedGeometryEffect (properties + anchor)

В очередной раз видим новое поведение, осталось 2 сценария.

Настраиваем источником вычислений круг

  @ViewBuilder
  private var customOptionCircleIsSource: some View {
    if showCircle {
      greenCircle
        .matchedGeometryEffect(
          id: animationFourId,
          in: animationFour
        )
    } else {
      blackRectangle
        .matchedGeometryEffect(
          id: animationFourId,
          in: animationFour,
          isSource: false // <- добавили, по умолчанию тут true
        )
    }
  }
Источник для вычислений - круг

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

Настраиваем источником вычислений прямоугольник

  @ViewBuilder
  private var customOptionRectangleIsSource: some View {
    if showCircle {
      greenCircle
        .matchedGeometryEffect(
          id: animationThreeId,
          in: animationThree,
          isSource: false // <- добавили, по умолчанию тут true
        )
    } else {
      blackRectangle
        .matchedGeometryEffect(
          id: animationThreeId,
          in: animationThree
        )
    }
  }
Источник для вычислений - прямоугольник

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

Заключение

С использованием matchedGeometryEffect можно сделать много интересных анимаций - экспериментируйте и предлагайте дизайнерам 🙂

Код для этой статьи можно посмотреть тут, а другие статьи - тут.



Report Page