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)
原来的同步动画问题在于所有标签共用了同一个动画值,要实现逐个动画只需要给每个标签分配独立的动画值,按索引设置启动延迟即可,修改后的核心代码如下:
// 替换原来的单个动画值定义 const zoomAnims = useRef<Animated.Value[]>([]).current; useEffect(() => { // 先按标签数量初始化动画值数组 zoomAnims.length = 0; tagInfo?.forEach(() => { zoomAnims.push(new Animated.Value(START_ZOOM_SCALE)); }); // 批量创建带延迟的动画,按索引依次延迟200ms启动,可自行调整间隔时长 const animations = zoomAnims.map((anim, index) => Animated.timing(anim, { toValue: FINAL_ZOOM_SCALE, duration: FAST_ZOOM, delay: index * 200, useNativeDriver: true, }) ); // 并行启动所有动画,因为已经加了延迟,自然会按顺序执行 Animated.parallel(animations).start(); }, [tagInfo]); const displayTags = () => tagInfo?.map((tag, index) => ( <TagContainer key={tag.feedback} // 记得加唯一key style={[ { transform: [{scale: zoomAnims[index]}], }, ]}> <LeftTextRightCircle feedback={tag.feedback} rating={tag.rating} /> </TagContainer> ));
该方案不需要引入额外依赖,是React Native原生动画的标准实现逻辑。
最终采用的简化方案
通过react-native-animatable库实现的方案更简洁,库内置了动画延迟属性,直接通过索引计算延迟时间即可实现效果,核心代码如下:
const displayTags = () => tagInfo?.map((tag, index) => ( <TagContainer animation="zoomIn" duration={1000} delay={index * 1000}> <LeftTextRightCircle feedback={tag.feedback} rating={tag.rating} /> </TagContainer> ));
内容的提问来源于stack exchange,提问作者Ibrahim Noor Mohammed
相关产品推荐
相关产品推荐

