【问题标题】:How to animate mapped elements one at a time in react-native?如何在 react-native 中一次为一个映射元素设置动画?
【发布时间】:2021-08-18 17:43:25
【问题描述】:

我映射了一个对象数组来创建一个标签元素,其中的细节被映射到元素上。然后我在渲染时创建了一个动画,标签放大到全尺寸。但是,我想将其带到下一步,并希望单独为每个标签设置动画,以便每个标签一个接一个地进行动画。对我来说,这似乎是动画的常见用法,那么我该如何从我的示例中做到这一点?有没有我想念的常用方法?

import {LeftIconsRightText} from '@atoms/LeftIconsRightText';
import {LeftTextRightCircle} from '@atoms/LeftTextRightCircle';
import {Text, TextTypes} from '@atoms/Text';
import VectorIcon, {vectorIconTypes} from '@atoms/VectorIcon';
import styled from '@styled-components';
import * as React from 'react';
import {useEffect, useRef} from 'react';
import {Animated, ScrollView} from 'react-native';

export interface ICustomerFeedbackCard {
  title: string;
  titleIconName: string[];
  tagInfo?: {feedback: string; rating: number}[];
}

export const CustomerFeedbackCard: React.FC<ICustomerFeedbackCard> = ({
  title,
  titleIconName,
  tagInfo,
  ...props
}) => {
  const FAST_ZOOM = 800;
  const START_ZOOM_SCALE = 0.25;
  const FINAL_ZOOM_SCALE = 1;
  const zoomAnim = useRef(new Animated.Value(START_ZOOM_SCALE)).current;

  /**
   * Creates an animation with a
   * set duration and scales the
   * size by a set factor to create
   * a small zoom effect
   */
  useEffect(() => {
    const zoomIn = () => {
      Animated.timing(zoomAnim, {
        toValue: FINAL_ZOOM_SCALE,
        duration: FAST_ZOOM,
        useNativeDriver: true,
      }).start();
    };
    zoomIn();
  }, [zoomAnim]);

  /**
   * Sorts all tags from highest
   * to lowest rating numbers
   * @returns void
   */

  const sortTags = () => {
    tagInfo?.sort((a, b) => b.rating - a.rating);
  };

  /**
   * Displays the all the created tags with
   * the feedback text and rating number
   * @returns JSX.Element
   */
  const displayTags = () =>
    tagInfo?.map((tag) => (
      <TagContainer
        style={[
          {
            transform: [{scale: zoomAnim}],
          },
        ]}>
        <LeftTextRightCircle feedback={tag.feedback} rating={tag.rating} />
      </TagContainer>
    ));

  return (
    <CardContainer {...props}>
      <HeaderContainer>
        <LeftIconsRightText icons={titleIconName} textDescription={title} />
        <Icon name="chevron-right" type={vectorIconTypes.SMALL} />
      </HeaderContainer>
      <ScrollOutline>
        <ScrollContainer>
          {sortTags()}
          {displayTags()}
        </ScrollContainer>
      </ScrollOutline>
      <FooterContainer>
        <TextFooter>Most recent customer compliments</TextFooter>
      </FooterContainer>
    </CardContainer>
  );
};

这里是对象数组供参考:

export const FEEDBACKS = [
  {feedback: 'Good Service', rating: 5},
  {feedback: 'Friendly', rating: 2},
  {feedback: 'Very Polite', rating: 2},
  {feedback: 'Above & Beyond', rating: 1},
  {feedback: 'Followed Instructions', rating: 1},
  {feedback: 'Speedy Service', rating: 3},
  {feedback: 'Clean', rating: 4},
  {feedback: 'Accommodating', rating: 0},
  {feedback: 'Enjoyable Experience', rating: 10},
  {feedback: 'Great', rating: 8},
];

编辑:我通过替换 React-Native-Animated 并使用动画视图来解决它,而不是使用 Animatable 并使用内置延迟的 Animatable。最终解决方案:

const displayTags = () =>
    tagInfo?.map((tag, index) => (
      <TagContainer animation="zoomIn" duration={1000} delay={index * 1000}>
        <LeftTextRightCircle feedback={tag.feedback} rating={tag.rating} />
      </TagContainer>
    ));

Here is a gif of the animation

【问题讨论】:

  • 尝试递归函数之类的东西

标签: javascript reactjs react-native react-animated


【解决方案1】:

这是一个有趣的问题。解决此问题的一种干净方法是开发一个包装器组件DelayedZoom,它将延迟缩放呈现其子组件。该组件将采用 delay 属性,您可以控制该属性为组件开始动画的时间添加延迟。

function DelayedZoom({delay, speed, endScale, startScale, children}) {
  const zoomAnim = useRef(new Animated.Value(startScale)).current;
  useEffect(() => {
    const zoomIn = () => {
      Animated.timing(zoomAnim, {
        delay: delay,
        toValue: endScale,
        duration: speed,
        useNativeDriver: true,
      }).start();
    };
    zoomIn();
  }, [zoomAnim]);

  return (
    <Animated.View
      style={[
        {
          transform: [{scale: zoomAnim}],
        },
      ]}>
      {children}
    </Animated.View>
  );
}

在此之后,您可以按如下方式使用该组件:

function OtherScreen() {
  const tags = FEEDBACKS;
  const FAST_ZOOM = 800;
  const START_ZOOM_SCALE = 0.25;
  const FINAL_ZOOM_SCALE = 1;

  function renderTags() {
    return tags.map((tag, idx) => {
      const delay = idx * 10; // play around with this. Main thing is that you get a sense for when something should start to animate based on its index, idx.

      return (
        <DelayedZoom
          delay={delay}
          endScale={FINAL_ZOOM_SCALE}
          startScale={START_ZOOM_SCALE}
          speed={FAST_ZOOM}>
          {/** whatever you want to render with a delayed zoom would go here. In your case it may be TagContainer */}
          <TagContainer>
            <LeftTextRightCircle feedback={tag.feedback} rating={tag.rating} />
          </TagContainer>
        </DelayedZoom>
      );
    });
  }

  return <View>{renderTags()}</View>;
}

我希望这有助于为您指明正确的方向!

还有一些有用的资源:

演示

【讨论】:

  • 我真的很喜欢您的解决方案,但是有没有办法通过为每个标签合并 stagger() 和动画值数组来更轻松地做到这一点?这是我想尝试解决问题的一种方式,因为我觉得它会更符合实际,而不是创建整个组件。
  • 谢谢!我也喜欢这个解决方案,因为它更可重用且相对不那么突兀。您绝对可以按照您的要求进行操作,但这似乎是对问题的一种反应较少的方法,并且需要一些簿记。查看此资源以获取有关Animated.stagger 的一些指导:animationbook.codedaily.io/animated-stagger。如果您需要进一步的帮助,很高兴更新我的答案!
  • 有没有办法使用使用索引的逻辑将效果单独应用于每个元素?
  • 这很奇怪。我将在本地进行设置以进行测试和调试,然后在我发现新内容时回复您。
  • 我会建议在 TagContainer 的底层使用 DelayedZoom 组件来获得类似于您所得到的解决方案。很高兴您能找到最适合您的方法。
【解决方案2】:

实现这个有点工作,我没有你的组件来尝试,所以我创建了一个基本的实现,我希望这会有所帮助

import React, { useEffect, useRef, useState } from "react";
import { StyleSheet, Text, View, Animated } from "react-native";

const OBJ = [{ id: 1 }, { id: 2 }, { id: 3 }];

const Item = ({ data, addValue }) => {
  const zoomAnim = useRef(new Animated.Value(0)).current;
  useEffect(() => {
    const zoomIn = () => {
      Animated.timing(zoomAnim, {
        toValue: 1,
        duration: 500,
        useNativeDriver: true
      }).start(() => {
        addValue();
      });
    };
    zoomIn();
  }, [zoomAnim]);

  return (
    <View>
      <Animated.View
        ref={zoomAnim}
        style={[
          {
            transform: [{ scale: zoomAnim }]
          }
        ]}
      >
        <Text style={styles.text}>{data}</Text>
      </Animated.View>
    </View>
  );
};

function App() {
  const [state, setState] = useState([OBJ[0]]);
  const addValue = () => {
    const currentId = state[state.length - 1].id;
    if (OBJ[currentId]) {
      const temp = [...state];
      temp.push(OBJ[currentId]);
      setState(temp);
    }
  };
  return (
    <View style={styles.app}>
      {state.map((item) => {
        return <Item data={item.id} key={item.id} addValue={addValue} />;
      })}
    </View>
  );
}

const styles = StyleSheet.create({
  text: {
    fontSize: 20
  }
});

export default App;

基本上,我是在上一个动画结束的状态中添加一个元素,需要注意的一点是key很重要,不要用index作为key。而不是 Ids,您可能希望添加任何其他已排序的值,或者通过传递前一个项目的 id 来链接项目。

使用 REANIMATED 和 MOTI 添加解决方案

有一个你可以使用 moti(https://moti.fyi/) 的库,它可以与 reanimated 一起使用,所以你也需要添加 reanimated。在使用 Reanimated 之前,您必须考虑到该特定应用程序的普通 chrome 开发工具将停止使用 reanimated 2.0 及更高版本,但您可以使用 Flipper。

找到解决方案。

import { View as MotiView } from 'moti';

...
 const displayTags = () =>
    tagInfo?.map((tag, index) => (
      <MotiView
           key = {tag.id}
           from={{ translateY: 20, opacity: 0 }}
           animate={{ translateY: 0, opacity: 1 }}
           transition={{ type: 'timing' }}
           duration={500}
           delay={index * 150}>
      <TagContainer
        style={[
          {
            transform: [{scale: zoomAnim}],
          },
       ]}>
        <LeftTextRightCircle feedback={tag.feedback} rating={tag.rating} />
     </TagContainer>
     </MotiView>
  ));
...

就是这样,确保使用正确的键,不要使用索引作为键。 旁注:如果您怀疑自己是否使用复活的灵魂,请浏览https://docs.swmansion.com/react-native-reanimated/docs/此页面。使用 Moti,如果您重新制作了 2.3.0-alpha.1 版本,您也可以轻松制作非常酷的动画,那么您不需要使用 Moti,但由于它是 alpha 版本,因此不建议在生产中使用,您可以等待其稳定发布也是。

【讨论】:

  • 如果我只是将 id 添加到对象数组中,这是否也可以作为键使用?感谢您的帮助,我已经尝试讨论这个问题三天了,但我会尽快尝试。
  • 是的,如果你能做到的话。只是不要动态更改密钥。如果您更改密钥,那么它将重新渲染整个组件,并且它将重新激活这将不是您想要的行为。如果您愿意添加库并使用 Reanimated 库,请告诉我,我有更可靠的解决方案来解决这个问题。
  • 是的,我愿意使用 reanimated,更可靠的解决方案是什么?
  • 添加了您可以查看的当前答案
  • 不幸的是我不能使用 moti only reanimated
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-03-27
  • 2019-09-20
  • 1970-01-01
  • 1970-01-01
  • 2017-10-08
  • 2018-05-07
  • 2020-03-13
相关资源
最近更新 更多