首页 > 解决方案 > 如何在 react-native 中一次为一个映射元素设置动画?

问题描述

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

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>
    ));

这是动画的gif

标签: javascriptreactjsreact-nativereact-animated

解决方案


这是一个有趣的问题。解决此问题的一种干净方法是开发一个包装器组件,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>;
}

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

还有一些有用的资源:

演示

在此处输入图像描述


推荐阅读