返回博客首页
← 所有文章

NativeScript 共享元素过渡(第 1 部分) - 太空人

2023 年 3 月 28 日 — 作者:技术指导委员会 (TSC)

让我们一起去太空旅行,好吗?

思考应用程序交互中的视觉关系可以帮助提供令人愉悦的用户体验。这一切都始于屏幕的布局。

在 StackBlitz 上亲自尝试

我们将使用核心版本的“Vanilla TypeScript”来进行说明,但这适用于所有版本。

设置太空人页面 A

在布局第一个页面时,我们知道我们希望地球移动到它在第二个页面上的位置,而太空人的面罩则直奔我们而来,露出第二个页面的静止画面。

布局标记

我们可以用以下方式表示它 - 省略 css 细节,只关注布局标记结构 - 查看 上面的 StackBlitz 示例 与 css

<GridLayout rows="3*, 2*">
  <image
    src="~/assets/Earth.png"
    sharedTransitionTag="earth"
    width="75%"
    stretch="aspectFit"
  ></image>
  <image
    src="~/assets/SpaceMan.png"
    sharedTransitionTag="spaceman"
    stretch="aspectFill"
  ></image>
  <GridLayout rows="160,auto,auto,auto,*" sharedTransitionTag="title">
    <label row="1" text="SPACE" />
    <label row="2" text="TRAVEL" />
    <button row="3" text="TICKETS AVAILABLE" width="55%" tap="{{ open }}" />
  </GridLayout>
  <GridLayout
    row="1"
    rows="auto,*"
    columns="auto,*"
    sharedTransitionTag="infobox"
  >
    <ContentView width="150" height="100">
      <image
        src="~/assets/Astronaut.jpg"
        width="150"
        height="100"
        stretch="aspectFill"
      ></image>
    </ContentView>
    <StackLayout col="1">
      <label text="98% RATING" />
      <label text="FIND OUT MORE" />
    </StackLayout>
    <label row="1" colSpan="2" text="{{descriptionText}}" textWrap="true" />
  </GridLayout>
</GridLayout>

遵循 GridLayout 文档,我们使用一个 2 行网格来表示一般布局,其中 1 行占用 3 倍可用空间,3*,其余占用 2 倍可用空间,2*

在第一行中,我们把地球图像放在太空人图像的下面(在这种情况下,像 Photoshop 文件一样分层),以及我们的“太空旅行”文字,按钮层叠在上面。

在第二行中,我们有一个详细的描述区域。

声明 sharedTransitionTag

我们知道我们想要在地球和太空人图像上进行一些关系视觉运动,因此我们用 sharedTransitionTag 值声明它们。

<image
  src="~/assets/Earth.png"
  sharedTransitionTag="earth"
  width="75%"
  stretch="aspectFit"
></image>
<image
  src="~/assets/SpaceMan.png"
  sharedTransitionTag="spaceman"
  stretch="aspectFill"
></image>

我们还希望“太空旅行”标题和“票务信息”按钮一起向左移动,当显示下一页面时,因此我们将它们包含在一个布局容器中,我们可以为它声明 sharedTransitionTag

<GridLayout rows="160,auto,auto,auto,*" sharedTransitionTag="title">
  <label row="1" text="SPACE" />
  <label row="2" text="TRAVEL" />
  <button row="3" text="TICKETS AVAILABLE" width="55%" tap="{{ open }}" />
</GridLayout>

最后,我们希望详细的描述区域向下移动并让路,因此我们也为它放置 sharedTransitionTag

<GridLayout
  row="1"
  rows="auto,*"
  columns="auto,*"
  sharedTransitionTag="infobox"
></GridLayout>

在页面导航上设置 SharedTransition

使用 SharedTransition API,我们可以自定义我们希望共享元素过渡的方式。

import { SharedTransition } from '@nativescript/core';

// setup page navigation to move to a new "earth-view" we will layout in a moment
page.frame.navigate({
  moduleName: 'earth-view',

  // we define our transition here
  transition: SharedTransition.custom(new PageTransition(), {
    // where the page we're moving away from should end up
    pageEnd: {
      // on iOS, we can define "independent" tag movement
      sharedTransitionTags: {
        // fade spaceman out while moving and scaling up
        spaceman: {
          opacity: 0,
          y: 20,
          scale: {
            x: 6,
            y: 6,
          },
        },
        // fade title out while moving off to the left
        title: {
          opacity: 0,
          x: -200,
        },
        // fade infobox out while moving it down and out
        infobox: {
          opacity: 0,
          y: 800,
        },
      },
    },
    // how the page returns back
    pageReturn: {
      // we use duration to override default spring settings
      // in this example, looks better with linear duration animation
      duration: 600,
    },
  }),
});

设置太空人页面 B

在布局第二个页面时,我们只需要考虑设计中的关系元素,并按我们想要的方式进行布局。

<GridLayout rows="*, *, *, *">
  <GridLayout columns="auto,auto">
    <image
      src="~/assets/back.png"
      tap="{{close}}"
      stretch="aspectFill"
      width="20"
      height="20"
    />
    <button col="1" text="Back" tap="{{close}}" width="50" />
  </GridLayout>
  <GridLayout row="1" rowSpan="2" rows="auto,auto,auto">
    <label text="EARTH" />
    <label
      row="1"
      text="Earth is the third planet from the Sun and the only object in the universe known to harbor life."
      textWrap="true"
      width="70%"
    />
    <image
      row="2"
      src="~/assets/Moon.png"
      stretch="aspectFit"
      width="80"
      height="80"
    />
  </GridLayout>
  <GridLayout rows="*" row="2" rowSpan="2">
    <image
      src="~/assets/Earth.png"
      stretch="aspectFit"
      sharedTransitionTag="earth"
    />
  </GridLayout>
</GridLayout>

这个例子最有趣的地方或许在于,我们只用 sharedTransitionTag="earth" 声明一个视图,因为它同时存在于页面 A 和页面 B 上。这样做可以让地球从它在页面 A 上的位置流畅地过渡到它在页面 B 上的更大的新位置。

动画的其余部分实际上由提供的 SharedTransition.custom 选项覆盖,对于 iOS,它仅在页面 A 上定义了各种 sharedTransitionTag 元素,以根据这些选项进行动画处理。

那么如果 iOS 支持仅在一个页面上存在的“独立”标记元素,而不存在于另一个页面上,那么如何处理 Android 呢?

注意:Android 的“独立”标记元素将在未来的核心版本中得到支持。

解决方法实际上很简单。您可以在标记的 <android> 部分内包含您希望 Android 与之创建关系的视图布局。因此,我们可以简单地为包含我们想要动画的另一个 sharedTransitionTag 的部分执行此操作,例如

<android>
  <GridLayout rows="3*, 2*" rowSpan="4">
    <image
      src="~/assets/SpaceMan.png"
      stretch="aspectFill"
      sharedTransitionTag="spaceman"
      scaleX="8"
      scaleY="8"
      translateY="940"
    ></image></GridLayout
></android>

在 Android 上,当导航到页面时,它现在将找到这些关系 sharedTransitionTag,并根据其视图声明的位置进行动画处理。

总结

这个例子说明了 @nativescript/core 中共享元素过渡的 versatility,适用于 iOS 和 Android,让您实现视觉上令人惊叹的效果。

您有一些关于真正引人入胜的视觉效果的想法吗?与社区分享,并在 Twitter 上添加 #nstricks 标签,我们期待看到您想出的美丽作品!