如何在 react-native 中一次为一个映射元素设置动画?
How to animate mapped elements one at a time in 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>
));
实现这个需要一些工作,我没有你的组件来尝试,所以我创建了一个基本的实现,我希望这会有所帮助
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;
基本上,我是在上一个动画结束时的状态中添加一个元素,需要注意的是键很重要,不要使用索引作为键。您可能想要添加任何其他已排序的值而不是 ID,或者可能 link 通过传递上一个项目的 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 版本,因此不建议在生产中使用,您可以等待其稳定发布也是。
这是一个有趣的问题。解决此问题的一种简洁方法是开发一个包装器组件 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>;
}
希望这有助于为您指明正确的方向!
还有一些有用的资源:
演示
我映射了一个对象数组来创建一个标签元素,并将详细信息映射到该元素上。然后我在渲染时创建了一个动画,标签放大到全尺寸。但是,我想将它带入下一步,并想单独为每个标签设置动画,以便每个标签按一个接一个的顺序设置动画。对我来说,这似乎是动画的常见用法,那么我如何从我的示例中做到这一点呢?有没有我缺少的常用方法?
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>
));
实现这个需要一些工作,我没有你的组件来尝试,所以我创建了一个基本的实现,我希望这会有所帮助
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;
基本上,我是在上一个动画结束时的状态中添加一个元素,需要注意的是键很重要,不要使用索引作为键。您可能想要添加任何其他已排序的值而不是 ID,或者可能 link 通过传递上一个项目的 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 版本,因此不建议在生产中使用,您可以等待其稳定发布也是。
这是一个有趣的问题。解决此问题的一种简洁方法是开发一个包装器组件 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>;
}
希望这有助于为您指明正确的方向!
还有一些有用的资源: