by Narendra N Shetty

由纳伦德拉·N·谢蒂(Narendra N Shetty)

与React Native共享元素过渡 (Shared Element Transition with React Native)

In this post I will be talking about how to achieve Shared Element Transition with React Native for both iOS and Android.

在这篇文章中,我将讨论如何通过iOS和Android的React Native实现共享元素转换。

I have posted the code on GitHub and you can take a look if you want to jump right into it.

我已将代码发布在GitHub上 ,如果您想直接进入它,可以看看。

意图 (Intent)

Lets take a look at what we’re going to build. Below is a photo grid example where we’ll add a shared element transition. This way, we can transition smoothly between a grid and the details page for a photo we select.

让我们看看我们将要构建什么。 下面是一个图片网格示例,我们将在其中添加共享元素过渡。 这样,我们可以在网格和所选照片的​​详细信息页面之间平滑过渡。

This is a much smoother and a continuous experience.

这是一个更加流畅和持续的体验。

方法 (Approach)

Before we build this, let me tell you how the system works under the hood. Since React Native doesn’t support true shared elements, when we say we are doing a shared element transition between two screens, we aren’t technically sharing any elements. Instead, each screen has its own individual element.

在我们构建它之前,让我告诉您系统是如何在后台运行的。 由于React Native不支持真正的共享元素,因此当我们说要在两个屏幕之间进行共享元素转换时,从技术上讲,我们并没有共享任何元素。 而是,每个屏幕都有其自己的单独元素。

What I am doing is passing the information about the shared element — such as its position and size — between these two elements.

我正在做的是在这两个元素之间传递有关共享元素的信息,例如其位置和大小。

When the details screen launches, its background is set to transparent, and it hides all of its elements. It then alters the attributes of the shared element to the one passed, then makes it visible. It then animates itself to its natural position. As the transition progresses, the window background and the rest of non-shared elements slowly fade in until they’re totally opaque.

当详细信息屏幕启动时,其背景将设置为透明,并隐藏其所有元素。 然后,它将共享元素的属性更改为传递的元素,然后使其可见。 然后将其自身设置为自然状态。 随着过渡的进行,窗口背景和其他非共享元素会慢慢淡入,直到它们完全不透明为止。

So while the element is not technically shared, this clever trick of smoke and mirror makes it appear that they are.

因此,尽管该元素在技术上并未共享,但这种巧妙的烟雾和镜子技巧使它们看起来像是。

So now that we understand how this process works, lets go step-by-step to understand how the mock element has been shared, and how we can control animations.

现在,我们了解了此过程的工作原理,让我们逐步了解了模拟元素是如何共享的,以及如何控制动画。

步骤1:进入和退出动画 (Step 1: Entry and Exit Animation)

I have two screens here: Grid and Details. From the Grid screen, we can launch the Detail screen by clicking on one of the images in the grid. Then we can return to the grid screen by hitting the back button.

我在这里有两个屏幕:网格和详细信息。 在“网格”屏幕中,我们可以通过单击网格中的图像之一来启动“详细信息”屏幕。 然后,我们可以通过单击“后退”按钮返回到网格屏幕。

When we go from Grid screen to Detail screen we have an opportunity to run two sets of transition animations — the Exit transition for the Grid screen, and the and Entry transition for Detail screen.

当我们从“网格”屏幕转到“详细信息”屏幕时,我们有机会运行两套过渡动画-“网格”屏幕的“退出”过渡,以及“详细信息”屏幕的和“入口”过渡。

Lets see how we implement this.

让我们看看我们如何实现这一点。

Without any transition, this is how the app looks. Clicking on the individual image takes you to a detail screen.

无需任何转换,这就是应用程序的外观。 单击单个图像将带您到详细信息屏幕。

Let’s add an exit transition to the first grid screen. Here we use a simple fade out transition using the Animated api, which interpolates the opacity attribute of the grid screen container from 1 to 0.

让我们向第一个网格屏幕添加退出过渡。 在这里,我们使用一个使用Animated API的简单淡出过渡,该过渡将网格屏幕容器的不透明度属性从1插入到0。

Now that we have done that, here’s how it looks:

现在我们已经完成了,看起来是这样的:

Not too bad. We see that the grid is faded out as we move onto the details screen.

还不错 当我们进入详细信息屏幕时,我们看到网格逐渐淡出。

Let’s now add another transition to the content of the detail screen as it comes in. Let’s slide the text into the place from the bottom.

现在,让我们在细节屏幕的内容中添加另一个过渡。让我们将文本从底部滑入该位置。

This is done by assigning an interpolated Animated value to the translateY property of the text container.

这是通过将插值的Animated值分配给文本容器的translateY属性来完成的。

And here’s how it looks:

外观如下:

Well the title and the description slides in very nicely, but the image appears abruptly. This is because our transition doesn’t target image specifically. We’ll fix this shortly.

标题和说明都很好地滑入了,但是图像突然出现了。 这是因为我们的过渡并不专门针对图像。 我们会尽快修复。

步骤2:共享元素的过渡层 (Step 2: Transition layer for the shared element)

We now add a transition layer which appears during the transition, and contains only the shared element.

现在,我们添加一个过渡层,该过渡层在过渡期间出现,并且仅包含共享元素。

This layer is triggered when the image in the grid is clicked. It receives information about the shared element, such as its position and size from both the Grid screen and Details screen.

单击网格中的图像时将触发此层。 它从“网格”屏幕和“详细信息”屏幕接收有关共享元素的信息,例如其位置和大小。

步骤3:过渡层中的动画 (Step 3: Animation in the transition layer)

We have the information in the transition layer about the source and destination position of the shared element. We just need to animate them.

我们在过渡层中有关于共享元素的源和目标位置的信息。 我们只需要为它们设置动画。

Let’s first set the element based on the source position and size, then animate it to the destination location. This can be done in two ways. Let’s take a look at both of them.

首先,根据源位置和大小设置元素,然后将其设置为目标位置的动画。 这可以通过两种方式完成。 让我们看看它们两者。

通过插值宽度,高度,顶部和左侧 (By interpolating on the width, height, top and left)

This is a straightforward approach. If we want an element to change from one size to another, and from one position to another, we modify the width, height, top, and left style properties of the element.

这是一个简单的方法。 如果希望元素从一种尺寸更改为另一种尺寸,并从一个位置更改为另一种位置,则可以修改元素的宽度,高度,顶部和左侧样式属性。

And here’s how it looks:

这是它的外观:

绩效分析 (Performance Analysis)

When using Animated, we declare a graph of nodes that represent the animations that we want to perform, and then use a driver to update an Animated value using a predefined curve.

使用Animated时,我们声明一个表示要执行的动画的节点图,然后使用驱动程序使用预定义的曲线更新Animated值。

Here’s a breakdown of the steps for an animation and where it happens:

以下是动画制作步骤及其发生的细分:

  • JavaScript: The animation driver uses requestAnimationFrame to execute on every frame and update the value it drives using the new value it calculates based on the animation curve.

    JavaScript:动画驱动程序使用requestAnimationFrame在每个帧上执行,并使用基于动画曲线计算的新值更新其驱动的值。

  • JavaScript: Intermediate values are calculated and passed to a props node that is attached to a View.

    JavaScript:计算中间值并将其传递到附加到View的props节点。

  • JavaScript: The View is updated using setNativeProps.

    JavaScript的:在View使用更新setNativeProps

  • JavaScript to Native bridge.

    JavaScript到本机桥。
  • Native: The UIView or android.View is updated.

    本机: UIViewandroid.View已更新。

As you can see, most of the work happens on the JavaScript thread. If it is blocked, the animation will skip frames. It also needs to go through the JavaScript to Native bridge on every frame to update native views.

如您所见,大多数工作都在JavaScript线程上进行。 如果被阻止,动画将跳过帧。 它还需要在每个框架上通过JavaScript到Native桥来更新本机视图。

This problem can be solved by using useNativeDriver. This moves all of these steps to native.

可以使用useNativeDriver解决此问题。 这会将所有这些步骤移至本地。

Since Animated produces a graph of animated nodes, it can be serialized and sent to native only once when the animation starts. This eliminates the need to callback into the JavaScript thread. The native code can take care of updating the views directly on the UI thread on every frame.

由于Animated会生成动画节点图,因此可以将其序列化,并在动画开始时仅将其发送到native一次。 这消除了回调到JavaScript线程的需要。 本机代码可以负责直接在每个框架上的UI线程上更新视图。

The main limitation is that we can only animate non-layout properties. Things like transform and opacity will work, but flexbox and position properties like the one used above won’t.

主要限制是我们只能为非布局属性设置动画。 诸如transformopacity之类的东西都可以使用,但是flexbox和position属性(如上面所使用的那种)不起作用。

插值转换并使用useNativeDriver (Interpolating on transform and using useNativeDriver)

Let us now animate using transform. This will require a some math to calculate the scale, x and y position.

现在让我们使用变换制作动画。 这将需要一些数学运算来计算比例尺,x和y位置。

With this implementation, if we’re scaling from a smaller image to a larger one, the image will pixelate. So we will render the larger image, then scale it down to its start size, then animate it up to the natural size.

通过此实现,如果我们将图像从较小的图像缩放到较大的图像,则图像将像素化。 因此,我们将渲染较大的图像,然后将其缩小到其初始大小,然后对其进行动画处理以达到自然大小。

We can get the start scale value with a line of JavaScript like this:

我们可以使用如下一行JavaScript来获取起始比例值:

openingScale = sourceDimension.width / destinationDimension.width;

You see that the scaled image and original image don’t look the same that is because the aspect ratio of source image and destination image are different, so to solve it we will render the image with source aspect ratio based on destination dimension.

您会看到缩放后的图像和原始图像看起来不一样,这是因为源图像和目标图像的纵横比不同,因此要解决此问题,我们将基于目标尺寸渲染具有源纵横比的图像。

const sourceAspectRatio = source.width / source.height;const destAspectRatio = destination.width / destination.height;
if (aspectRatio - destAspectRatio > 0) {  // Landscape image  const newWidth = aspectRatio * destination.height;  openingScale = source.width / newWidth;} else {  // Portrait image  const newHeight = destination.width / aspectRatio;  openingScale = source.height / newHeight;}

Now that the scale is correct, we need to get the new position based on the destination image. This can be calculated by the destination position minus half of the difference between the old dimension and new dimension. Which would equate to:

现在比例正确,我们需要根据目标图像获取新位置。 这可以通过目标位置减去旧尺寸和新尺寸之间的差值的一半来计算。 等于:

if (aspectRatio - destAspectRatio > 0) {  // Landscape image  destination.pageX -= (newWidth - destinationWidth) / 2;} else {  // Portrait image  destination.pageY -= (newHeight - destinationHeight) / 2;}

That’s perfect! We now have the right dimension, and position for the transitioning image.

那很完美! 现在,我们拥有正确的尺寸和过渡图像的位置。

Now we need to calculate the translation position from which to animate the image. We’ll scale the image from the center, so we need to apply our translate considering that we are just moving the center of the photo. So we’ll just do some relatively easy math, by taking the source position plus half of the source dimension. This would equate to this:

现在,我们需要计算要从中进行动画处理的平移位置。 我们将从中心缩放图像,因此考虑到我们只是移动照片的中心,我们需要应用转换。 因此,通过获取源位置加上源尺寸的一半,我们将做一些相对简单的数学运算。 这等于:

const translateInitX = source.pageX + source.width / 2;const translateInitY = source.pageY + source.height / 2;
const translateDestX = destination.pageX + destination.width / 2;const translateDestY = destination.pageY + destination.height / 2;

We can now calculate the translate position by the difference between the center of source image and destination image

现在,我们可以通过源图像中心和目标图像中心之间的差异来计算平移位置

const openingInitTranslateX = translateInitX - translateDestX;const openingInitTranslateY = translateInitY - translateDestY;

With this found start scale and translate values we can animate using the Animated api.

有了这个找到的开始比例和转换值,我们可以使用Animated api进行Animated

That’s it. We now have transition working. We can now use useNativeDriver since we are now animating only non-layout properties.

而已。 我们现在有过渡工作。 现在我们可以使用useNativeDriver因为我们现在仅对非布局属性进行动画处理。

步骤4:在过渡期间隐藏源图像和目标图像 (Step 4: Hiding the source and destination image during transition)

In the previous gif, we saw that during transition, the clicked image was still in the same position, and the destination image appeared before the transition was complete.

在上一个gif中,我们看到在过渡期间,单击的图像仍处于相同位置,并且目标图像出现在过渡完成之前。

Lets hide the source and destination image during the transition, so that it looks like the clicked image is the one animating to the detail screen.

让我们在过渡期间隐藏源图像和目标图像,以使所单击的图像看起来像是向详细信息屏幕添加动画的图像。

Let now see the output.

现在让我们看一下输出。

步骤5:处理后退按钮 (Step 5: Handle the back button)

During transitioning into the detail screen using Animated.timing() we change the AnimatedValue from 0 to 1. So when back button is clicked, we just have to change the AnimatedValue from 1 to o.

在使用Animated.timing()过渡到详细信息屏幕的过程中,我们将AnimatedValue从0更改为1。因此,当单击后退按钮时,我们只需要将AnimatedValue从1更改为o。

That’s it. You can check the code on Github and try out the demo on Expo.

而已。 您可以在Github上检查代码,并在Expo上试用该演示。

narendrashetty/photo-gallery-RNContribute to photo-gallery-RN development by creating an account on GitHub.github.comphoto-gallery on ExpoAn empty new projectexpo.io

narendrashetty / photo-gallery-RN 通过在GitHub上创建一个帐户来为photo-gallery-RN开发做出贡献。 世博会上 github.com photo-gallery 一个空的新项目 expo.io

Also checkout Eric Vicenti’s broadcast on shared element transition.

还可以检查Eric Vicenti 关于共享元素转换广播

Thank you for taking time and reading this post. If you found this useful please clap and share it. You can connect with me on Twitter @narendra_shetty.

感谢您抽出宝贵时间阅读本帖子。 如果您觉得此功能有用,请拍一下并分享。 您可以通过Twitter @narendra_shetty与我联系

翻译自: https://www.freecodecamp.org/news/shared-element-transition-with-react-native-159f8bc37f50/

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐