8.7 发布—WinterCG 兼容性 第 1 部分
了解更多

API 可用性

共享元素过渡 API 在 @nativescript/core 版本 8.5.0 或更高版本中可用。

当您寻找提升应用程序用户体验的方法时,共享元素过渡可以帮助在整个应用程序中创建关联的视觉移动。它们甚至可以用来在两个组件之间创建变形效果。

本质上,您可以在不同页面上的组件上声明 sharedTransitionTag 属性,并传入自定义 SharedTransition 来创建引人入胜的视觉效果。

共享元素过渡示例
共享元素过渡类型iOSAndroid
页面导航
模态导航不可用

使用共享元素过渡

使用共享元素过渡需要在过渡的两个“端点”上对视图进行标记,并使用 SharedTransition 类对其进行配置。

标记要共享的视图

您可以使用包含 **唯一值** 的 sharedTransitionTag 属性标记任何页面上的任何 View 组件。

页面 A

在您 **即将离开** 的页面上标记一个视图

xml
<!-- Page A content -->
<Image sharedTransitionTag="hero" />

页面 B

在您 **即将到达** 的页面上标记一个视图,使用相同的匹配 sharedTransitionTag。当过渡开始时,该视图将从在 **页面 A** 上呈现的起始状态过渡到在 **页面 B** 上呈现的目标状态。

xml
<!-- Page B content -->
<Image sharedTransitionTag="hero" />

使用默认的 SharedTransition

您可以使用 SharedTransition.custom() API 以多种方式自定义过渡。

例如,使用默认值设置共享元素页面过渡

ts
import { SharedTransition, PageTransition } from '@nativescript/core'

page.frame.navigate({
  moduleName: `views/detail`,
  transition: SharedTransition.custom(new PageTransition()),
})

或者在打开模态时(*仅限 iOS*)

ts
import { SharedTransition, ModalTransition } from '@nativescript/core'

page.showModal('views/modal', {
  transition: SharedTransition.custom(new ModalTransition()),
  closeCallback(args) {
    // ... modal closed
  },
})

独立共享元素

在某些情况下,您可能在两个“端点”上都没有元素,或者需要在过渡期间动画其他元素。这就是“独立”共享元素解决的问题。

考虑以下示例

ts
SharedTransition.custom(new PageTransition(), {
  pageEnd: {
    opacity: 1,
    sharedTransitionTags: {
      spaceman: {
        opacity: 0,
        y: 20,
        scale: {
          x: 6,
          y: 6,
        },
      },
      title: {
        opacity: 0,
        x: -200,
      },
    },
  },
  // ...
})

标记为 spaceman 的视图将在 x 和 y 轴上分别向下移动 20dips 并向上缩放 6 倍。

标记为 title 的视图将淡出并向左移动 200dips

注意

  • 独立共享元素目前仅在 iOS 上受支持。
  • 不建议将“独立”元素与交互式过渡一起使用(并且很可能会导致意外行为)。

共享元素过渡的工作原理

为了提供开箱即用的灵活页面和模态过渡处理,@nativescript/core 提供了 PageTransitionModalTransition,它们预先配置了适用于大多数流行用例,但 SharedTransition 包含许多高级选项,可以自定义默认行为。

共享元素过渡会进行以下步骤

  1. 查找起始页和结束页之间具有匹配 sharedTransitionTag 值的视图。
  2. 当过渡开始时,在两个页面上都找到的标记有 sharedTransitionTag 的视图将在其起始状态和结束状态之间进行动画。
  3. 同时,传入页面将从配置中提供的 pageStart 状态动画到 pageEnd 状态。
  4. 在返回导航中,传出页面将从其当前状态动画到通过 pageReturn 提供的状态。

sharedTransitionTag 值可以动态绑定,以实现更多独特的功能。

sharedTransitionIgnore 可用于选择性地将视图包含或排除在共享过渡中。

属性

sharedTransitionTag

xml
<View sharedTransitionTag="hero" />

用于标识有资格进行共享元素过渡的视图的字符串值。

重要!

所有标签必须在每个给定页面上都是唯一的。重复的值可能会导致意外行为。

注意:

  • 在声明带有 sharedTransitionTag 的图像时,请确保在图像上声明了有效的尺寸(宽度/高度)。
  • 虽然可以标记 Label 组件,但不建议这样做,因为文本大小和样式在两种状态之间可能会有所不同。

sharedTransitionIgnore

xml
<View sharedTransitionIgnore="{{ someCondition }}" />

一个布尔值,指示在共享元素过渡期间是否应该忽略一个视图。

注意

此属性最常与动态绑定一起使用,否则它与一开始不标记视图没有什么区别。

API

SharedTransition

一个类,它公开各种 static 方法,用于配置共享元素过渡。

custom

ts
SharedTransition.custom(
  transition: Transition,
  options?: SharedTransitionConfig
)

设置共享元素过渡的主要 API。返回的对象可以直接作为 transition 传递给核心页面和模态导航 API。

第一个参数通常是 PageTransitionModalTransition,具体取决于导航类型。第二个可选参数允许配置过渡的各个方面。该函数返回配置的过渡实例以用于视觉过渡,并为内部状态跟踪进行设置。

ts
interface SharedTransitionConfig {
  /**
   * State applied to the incoming page transition on start
   */
  pageStart?: SharedTransitionPageProperties
  /**
   * State that the incoming page transitions to.
   */
  pageEnd?: SharedTransitionPageProperties
  /**
   * State that the outgoing page transitions to.
   * (from it's current state)
   */
  pageReturn?: SharedTransitionPageProperties & {
    /**
     * In some cases you may want the returning animation to start with the original opacity,
     * instead of beginning where it ended up on pageEnd.
     * Note: you can try enabling this property in cases where your return animation doesn't appear correct.
     */
    useStartOpacity?: boolean;
  };

  /** @ios - only supported on iOS */
  interactive?: {
    dismiss?: {
      /**
       * A threshold (percentage) that if exceeded by the pan gesture
       * will finish the transition once the touch is released.
       *
       * Default: 0.5
       */
      finishThreshold?: number
      /**
       * You can create your own percent formula used for determing
       * the interactive value. By default, we handle this with the
       * following formula:
       *
       *   eventData.deltaX / (eventData.ios.view.bounds.size.width / 2)
       *
       * @param eventData PanGestureEventData
       * @returns The percentage value to be used as the finish/cancel threshold
       */
      percentFormula?: (eventData: PanGestureEventData) => number
    }
  }
}

interface SharedProperties {
  x?: number
  y?: number
  width?: number
  height?: number
  opacity?: number
  scale?: {
    x?: number
    y?: number
  }
}

interface SharedTransitionTagProperties = SharedProperties & {
  /**
   * (iOS only) The visual stacking order where 0 is at the bottom.
   * Shared elements are stacked one on top of the other during each transition.
   * By default they are not ordered in any particular fashion.
   */
  zIndex?: number;
  /**
   * (iOS only) Collection of properties to match and animate on each shared element.
   *
   * Defaults to: {
   *   view: ['backgroundColor'],
   *   layer: ['cornerRadius', 'borderWidth', 'borderColor']
   * }
   *
   * Tip: Using an empty array, [], for view or layer will avoid copying any properties if desired.
   */
  propertiesToMatch?: {
    /**
     * View related properties
     */
    view?: Array<string>;
    /**
     * specific CALayer related properties
     */
    layer?: Array<string>;
  };
  /**
   * (iOS only) Ability to modify other visuals while handling the transition.
   * Callback will be fired before the shared element is positioned and added to the transition.
   * For example: scroll a CollectionView based on the element's position
   */
  callback?: (view: View, action: SharedTransitionEventAction) => Promise<void>;
};

type SharedTransitionPageProperties = SharedProperties & {
  /**
   * @ios - only supported on iOS
   * Allow "independent" elements found only on one of the
   * pages to take part in the animation.
   */
  sharedTransitionTags?: Record<string, SharedTransitionTagProperties>
  /**
   * Spring animation settings.
   * Defaults:
   *  tension: 140
   *  friction: 10
   */
  spring?: {
    tension?: number
    friction?: number
    mass?: number
    delay?: number
    velocity?: number
    /** @ios - UIViewAnimationOptions */
    animateOptions?: any
  }
}

事件

ts
SharedTransition.events(): Observable

公开一个 Ovservable,用于监听各种共享元素过渡事件。

SharedTransition.startedEvent

当过渡开始时发出。

SharedTransition.finishedEvent

当过渡结束时发出。

SharedTransition.interactiveCancelledEvent

当交互式过渡取消时发出。

SharedTransition.interactiveUpdateEvent

当交互式过渡使用百分比值更新时发出。

ts
interface SharedTransitionEventData {
  id: number // transition instance id
  type: 'page' | 'modal'
  action?: 'present' | 'dismiss' | 'interactiveStart' | 'interactiveFinish'
  percent?: number
}

例如

ts
SharedTransition.events().on(
  SharedTransition.finishedEvent,
  (data: SharedTransitionEventData) => {
    //
  }
)

getSharedElements

ts
SharedTransition.getSharedElements(fromPage: View, toPage: View): {
  sharedElements: Array<View>
  presented: Array<View>
  presenting: Array<View>
}

主要由 PageTransitionModalTransition 在内部使用。

也可以用作实用程序来获取两个视图之间的共享元素,以及获取所有声明的 sharedTransitionTag 元素列表(*即使在两个视图之间没有共享时也是如此*)。

getState

ts
SharedTransition.getState(id: number): SharedTransitionState

通过 id 获取过渡的当前状态。

ts
interface SharedTransitionState extends SharedTransitionConfig {
  /**
   * @internal the preconfigured transition instance
   */
  instance?: Transition
  /**
   * the Page which will start the transition.
   */
  page?: ViewBase
  activeType?: SharedTransitionAnimationType
  toPage?: ViewBase
  /**
   * Whether interactive transition has began.
   */
  interactiveBegan?: boolean
  /**
   * Whether interactive transition was cancelled.
   */
  interactiveCancelled?: boolean
}
ts
enum SharedTransitionAnimationType {
  present,
  dismiss,
}

updateState

ts
SharedTransition.updateState(id: number, state: SharedTransitionState): void

在内部使用,以在过渡进行时更新状态。如果出于任何原因您需要更新内部状态,请提供此方法。

finishState

ts
SharedTransition.finishState(id: number): void

在内部使用,以在过渡完成后完成状态。如果出于任何原因您需要提前完成状态,请提供此方法。

疑难解答

  • 在两个不同页面之间意外提供不匹配的 sharedTransitionTag 值很常见。在遇到共享元素过渡问题时,请始终检查匹配的标签。
  • 尝试避免在 Label 上使用 sharedTransitionTag,因为它们通常不会表现出预期的行为。

致谢

共享元素过渡的 API 灵感来自 React Native Reanimated

上一个
iOS