将可动画项目添加到 Flatlist 水平项目
Add animatable Items to Flatlist horizontal items
我有如下所示的水平平面列表,我试图在向右滑动时以及在视图中看到新项目时添加弹跳效果。
const Item = ({ title, image, index }) => (
<Animatable.View>
<View style={styles.item}>
<Text>{title}</Text>
</View>
</Animatable.View>
);
const renderItem = ({ item, index }) => (
<Item title={item.title} image={item.image} index={index} />
);
const [viewableItemsIndices, setViewableItemsIndices] = useState([]);
const handleVieweableItemsChanged = useCallback(
({ viewableItems, changed }) => {
setViewableItemsIndices(viewableItems.map((item) => item.index));
},
[]
);
const viewabilityConfig = useRef({
itemVisiblePercentThreshold: 80,
}).current;
return (
<SafeAreaView style={style.container}>
<View style={style.flatlistContainer}>
<FlatList
snapToInterval={120}
horizontal={true}
data={DATA.map((item, i) => {
item.isViewable = viewableItemsIndices.find((ix) => ix == i);
return item;
})}
renderItem={renderItem}
keyExtractor={(item) => item.id}
onViewableItemsChanged={handleVieweableItemsChanged}
viewabilityConfig={viewabilityConfig}
extraData={viewableItemsIndices}
/>
</View>
<Text>Items that should be visible:</Text>
{viewableItemsIndices.map((i) => (
<Text> {DATA[i].title}</Text>
))}
</SafeAreaView>
);
我在 viewableItemsIndices 中的 flatlist 中有所有可见项目,现在我如何根据其索引将可动画动画跟踪到单个项目..
const AnimationRef = useRef(null);
const _onPress = () => {
if(AnimationRef) {
AnimationRef.current?.bounce();
}
}
return (
<TouchableWithoutFeedback onPress={_onPress}>
<Animatable.View ref={AnimationRef}>
<Text>Bounce me!</Text>
</Animatable.View>
</TouchableWithoutFeedback>
);
编辑:下面是完整的代码
import React , { useState, useRef,useCallback } from 'react';
import { SafeAreaView, Image , View, FlatList, StyleSheet, Text, StatusBar } from 'react-native';
import { Ionicons, MaterialCommunityIcons , FontAwesome5 } from "@expo/vector-icons";
import * as Animatable from 'react-native-animatable';
import FlatListWrapper from "../anim/flatwrapper";
const FlatListAnimation = () => {
const DATA = [
{
id: 'bd7acbea-c1b1-46c2-aed5-3ad53abb28ba',
title: 'First Item Item Item',
image : 'https://res.cloudinary.com/Sony-liv/image/fetch/c_fill,e_brightness:10,f_auto,fl_lossy,h_494,q_auto:low,w_344/https://origin-staticv2.sonyliv.com/portrait_thumb/scam_Lang_Protrait_Thumb.jpg'
},
{
id: '3ac68afc-c605-48d3-a4f8-fbd91aa97f63',
title: 'Second Item',
image : 'https://res.cloudinary.com/Sony-liv/image/fetch/c_fill,e_brightness:10,f_auto,fl_lossy,h_494,q_auto:low,w_344/https://origin-staticv2.sonyliv.com/portrait_thumb/Ramsingh-CharlieA_07062021_Lang_Protrait_Thumb.jpg'
},
{
id: '58694a0f-3da1-471f-bd96-145571e29d72',
title: 'Third Item',
image : 'https://res.cloudinary.com/Sony-liv/image/fetch/c_fill,e_brightness:10,f_auto,fl_lossy,h_494,q_auto:low,w_344/https://origin-staticv2.sonyliv.com/portrait_thumb/6028647473001.jpg'
},
{
id: '58694a0sdsf-3da1-471f-bd96-145571e29d72',
title: 'Third Item',
image : 'https://res.cloudinary.com/Sony-liv/image/fetch/c_fill,e_brightness:10,f_auto,fl_lossy,h_494,q_auto:low,w_344/https://origin-staticv2.sonyliv.com/portrait_thumb/6033313370001.jpg'
},
{
id: '58694a0f-3ddsda1-471f-bd96-145571e29d72',
title: 'Third Item',
image : 'https://res.cloudinary.com/Sony-liv/image/fetch/c_fill,e_brightness:10,f_auto,fl_lossy,h_494,q_auto:low,w_344/https://origin-staticv2.sonyliv.com/portrait_thumb/6033325319001_v2.jpg'
},
{
id: 'bd7acbea-c1b1-46c2-aed5-3ad531abb28ba',
title: 'First Item Item Item',
image : 'https://res.cloudinary.com/Sony-liv/image/fetch/c_fill,e_brightness:10,f_auto,fl_lossy,h_494,q_auto:low,w_344/https://origin-staticv2.sonyliv.com/portrait_thumb/scam_Lang_Protrait_Thumb.jpg'
},
{
id: '3ac68afc-c605-48d3-a4f8-fbd912aa97f63',
title: 'Second Item',
image : 'https://res.cloudinary.com/Sony-liv/image/fetch/c_fill,e_brightness:10,f_auto,fl_lossy,h_494,q_auto:low,w_344/https://origin-staticv2.sonyliv.com/portrait_thumb/Ramsingh-CharlieA_07062021_Lang_Protrait_Thumb.jpg'
},
{
id: '58694a0f-3da1-471f-bd96-1455371e29d72',
title: 'Third Item',
image : 'https://res.cloudinary.com/Sony-liv/image/fetch/c_fill,e_brightness:10,f_auto,fl_lossy,h_494,q_auto:low,w_344/https://origin-staticv2.sonyliv.com/portrait_thumb/6028647473001.jpg'
},
{
id: '58694a0sdsf-3da1-471f-bd96-1445571e29d72',
title: 'Third Item',
image : 'https://res.cloudinary.com/Sony-liv/image/fetch/c_fill,e_brightness:10,f_auto,fl_lossy,h_494,q_auto:low,w_344/https://origin-staticv2.sonyliv.com/portrait_thumb/6033313370001.jpg'
},
];
const maxlimit = 20;
// Item.js
const Item = (props) => {
const {
item:{image , title, isViewable},
index
} = props
let animation = null
// set your animation type base on isViewable
if(isViewable || isViewable == 0){
animation = ""
}
else{
animation = ""
}
return (
//add animation to Animated.View
<Animatable.View style={style.itemContainer} >
<Image
resizeMode="contain"
style={styles.tinyLogo}
source={{
uri: image,
}}
/>
</Animatable.View>
);
}
const renderItem = ({ item , index }) => (
<Item title={item.title} image = {item.image} index= {index} />
);
// store the indices of the viewableItmes
const [ viewableItemsIndices, setViewableItemsIndices ] = useState([]);
const handleVieweableItemsChanged = useCallback(({viewableItems, changed }) => {
setViewableItemsIndices(viewableItems.map(item=>item.index))
}, []);
// config that decides when an item is viewable
const viewabilityConfig = useRef({
// useRef to try to counter the view rerender thing
itemVisiblePercentThreshold:80
}).current;
// wrapped handleViewChange in useCallback to try to handle the re-render error
return (
<SafeAreaView style={style.container}>
<FlatListWrapper
horizontal={true}
//{/*give each data item an isViewable prop*/}
data={DATA.map((item,i)=>{
item.isViewable=viewableItemsIndices.find(ix=>ix == i)
return item
})}
renderItem={item=><Item {...item}/>}
keyExtractor={item => item.id}
onViewableItemsChanged={({viewableItems, changed})=>{
// set viewableItemIndices to the indices when view change
setViewableItemsIndices(viewableItems.map(item=>item.index))
}}
//{/*config that decides when an item is viewable*/}
viewabilityConfig={{itemVisiblePercentThreshold:80}}
extraData={viewableItemsIndices}
/>
{/* Extra stuff that just tells you what items should be visible*/}
<Text>Items that should be visible:</Text>
{viewableItemsIndices.map(i=><Text key={'text-'+i}> {DATA[i].title}</Text>)}
</SafeAreaView>
);
}
const style = StyleSheet.create({
container:{
padding:10,
alignItems:'center'
},
item:{
borderWidth:1,
padding:5,
borderColor:'green',
},
itemContainer:{
flex:1,
backgroundColor: 'transparent',
marginVertical: 8,
width:120,
alignItems:'center',
marginHorizontal: 3,
}
})
const styles = StyleSheet.create({
container: {
flex: 1,
marginTop: StatusBar.currentHeight || 0,
},
item: {
flex:1,
backgroundColor: 'transparent',
marginVertical: 8,
width:120,
alignItems:'center',
marginHorizontal: 3,
},
title: {
fontSize: 32,
},
tinyLogo: {
borderRadius : 4,
width: 150,
height:150
},
});
export default FlatListAnimation;
FlatWrapper.js
//FlatListWrapper
import React, {useRef, useState, useCallback } from 'react';
import {
View, StyleSheet, FlatList ,
} from 'react-native';
const FlatListWrapper = (props) => {
// useRef to avoid onViewableItemsChange on fly error
const viewabilityConfig = useRef({
// useRef to try to counter the view rerender thing
itemVisiblePercentThreshold:80
}).current;
// wrapped handleViewChange in useCallback to try to handle the onViewableItemsChange on fly error
const onViewChange = useCallback(props.onViewableItemsChanged,[])
return (
<View style={style.flatlistContainer}>
<FlatList
{...props}
horizontal={true}
onViewableItemsChanged={onViewChange}
/>
</View>
);
}
const style = StyleSheet.create({
flatlistContainer:{
borderWidth:1,
borderColor:'red',
width:'100%',
},
})
export default FlatListWrapper
您需要做的就是编辑 Item
中的动画。我用 slideIn/slideOut 替换了 fadeIn 和 fadeOut(它很糟糕,但它有动作。让它反弹需要使用 y 值):
const Item = (props) => {
const {
item:{title, isViewable}
} = props
// to get bounce effect, we will animate a translation
const translateXY = useRef(new Animated.ValueXY()).current;
const slideValue = 40
const slideIn = () => {
Animated.spring(translateXY, {
toValue: {x:slideValue,y:5},
duration: 250,
useNativeDriver:false
}).start();
};
const slideOut = () => {
Animated.timing(translateXY, {
toValue: {x:-slideValue,y:5},
duration: 250,
useNativeDriver:false
}).start();
};
// fade in/out base on if isViewable
if(isViewable || isViewable == 0)
slideIn()
else
slideOut()
const initialState={transform:[{translateX:-slideValue}]}
const animation = {transform:translateXY.getTranslateTransform()}
return (
//add animation to Animated.View
<Animated.View style={[style.itemContainer,initialState,animation]}>
<View style={style.item}>
<Text style={style.title}>{title}</Text>
</View>
</Animated.View>
);
}
那是一个很棒的图书馆!不用为 Animated.Value 操心真是太好了。
// Item.js
const Item = (props) => {
const {
item:{title, isViewable},
index
} = props
let animation = null
// set your animation type base on isViewable
if(isViewable || isViewable == 0){
animation = "bounceInLeft"
}
else{
animation = "bounceOutLeft"
}
return (
//add animation to Animated.View
<Animatable.View style={style.itemContainer} animation = {animation}>
<View style={style.item}>
<Text style={style.title}>{title}</Text>
</View>
</Animatable.View>
);
}
// main component
const FlatListAnimation = () => {
// store the indices of the viewableItmes
const [ viewableItemsIndices, setViewableItemsIndices ] = useState([]);
return (
<SafeAreaView style={style.container}>
<FlatListWrapper
horizontal={true}
//{/*give each data item an isViewable prop*/}
data={DATA.map((item,i)=>{
item.isViewable=viewableItemsIndices.find(ix=>ix == i)
return item
})}
renderItem={item=><Item {...item}/>}
keyExtractor={item => item.id}
onViewableItemsChanged={({viewableItems, changed})=>{
// set viewableItemIndices to the indices when view change
setViewableItemsIndices(viewableItems.map(item=>item.index))
}}
//{/*config that decides when an item is viewable*/}
viewabilityConfig={{itemVisiblePercentThreshold:80}}
extraData={viewableItemsIndices}
/>
{/* Extra stuff that just tells you what items should be visible*/}
<Text>Items that should be visible:</Text>
{viewableItemsIndices.map(i=><Text key={'text-'+i}> {DATA[i].title}</Text>)}
</SafeAreaView>
);
}
const style = StyleSheet.create({
container:{
padding:10,
alignItems:'center'
},
flatlistContainer:{
borderWidth:1,
borderColor:'red',
width:'50%',
height:40
},
item:{
borderWidth:1,
padding:5,
},
itemContainer:{
padding:5,
}
})
//FlatListWrapper
import React, {useRef, useState, useCallback } from 'react';
import {
View, StyleSheet, FlatList ,
} from 'react-native';
const FlatListWrapper = (props) => {
// useRef to avoid onViewableItemsChange on fly error
const viewabilityConfig = useRef({
// useRef to try to counter the view rerender thing
itemVisiblePercentThreshold:80
}).current;
// wrapped handleViewChange in useCallback to try to handle the onViewableItemsChange on fly error
const onViewChange = useCallback(props.onViewableItemsChanged,[])
return (
<View style={style.flatlistContainer}>
<FlatList
{...props}
horizontal={true}
onViewableItemsChanged={onViewChange}
/>
</View>
);
}
const style = StyleSheet.create({
flatlistContainer:{
borderWidth:1,
borderColor:'red',
width:'50%',
height:40
},
})
export default FlatListWrapper
好的,所以我认为闪烁是由 extraData
道具引起的,导致 Flatlist 重新加载。重新阅读 Animatable 文档后,我发现有一种方法可以访问 Animatable View refs 并从该 ref 调用动画。知道这一点后,不再需要在屏幕上的项目发生变化时强制平面列表重新渲染,并且消除了我发现的所有闪烁
import React , { useState, useRef,useCallback } from 'react';
import { SafeAreaView, Image , View, FlatList, StyleSheet, Text, StatusBar } from 'react-native';
import { Ionicons, MaterialCommunityIcons , FontAwesome5 } from "@expo/vector-icons";
import * as Animatable from 'react-native-animatable';
import FlatListWrapper from "../components/FlatListWrapper";
const FlatListAnimation = () => {
const DATA = [
{
id: 'bd7acbea-c1b1-46c2-aed5-3ad53abb28ba',
title: 'First Item Item Item',
image : 'https://res.cloudinary.com/Sony-liv/image/fetch/c_fill,e_brightness:10,f_auto,fl_lossy,h_494,q_auto:low,w_344/https://origin-staticv2.sonyliv.com/portrait_thumb/scam_Lang_Protrait_Thumb.jpg'
},
{
id: '3ac68afc-c605-48d3-a4f8-fbd91aa97f63',
title: 'Second Item',
image : 'https://res.cloudinary.com/Sony-liv/image/fetch/c_fill,e_brightness:10,f_auto,fl_lossy,h_494,q_auto:low,w_344/https://origin-staticv2.sonyliv.com/portrait_thumb/Ramsingh-CharlieA_07062021_Lang_Protrait_Thumb.jpg'
},
{
id: '58694a0f-3da1-471f-bd96-145571e29d72-3-3',
title: 'Third Item',
image : 'https://res.cloudinary.com/Sony-liv/image/fetch/c_fill,e_brightness:10,f_auto,fl_lossy,h_494,q_auto:low,w_344/https://origin-staticv2.sonyliv.com/portrait_thumb/6028647473001.jpg'
},
{
id: '58694a0sdsf-3da1-471f-bd96-145571e29d72-4-4',
title: 'Fourth Item',
image : 'https://res.cloudinary.com/Sony-liv/image/fetch/c_fill,e_brightness:10,f_auto,fl_lossy,h_494,q_auto:low,w_344/https://origin-staticv2.sonyliv.com/portrait_thumb/6033313370001.jpg'
},
{
id: '58694a0f-3ddsda1-471f-bd96-145571e29d72-5-5',
title: 'Fifth Item',
image : 'https://res.cloudinary.com/Sony-liv/image/fetch/c_fill,e_brightness:10,f_auto,fl_lossy,h_494,q_auto:low,w_344/https://origin-staticv2.sonyliv.com/portrait_thumb/6033325319001_v2.jpg'
},
{
id: 'bd7acbea-c1b1-46c2-aed5-3ad531abb28ba-6-6',
title: 'Sixth Item Item Item',
image : 'https://res.cloudinary.com/Sony-liv/image/fetch/c_fill,e_brightness:10,f_auto,fl_lossy,h_494,q_auto:low,w_344/https://origin-staticv2.sonyliv.com/portrait_thumb/scam_Lang_Protrait_Thumb.jpg'
},
{
id: '3ac68afc-c605-48d3-a4f8-fbd912aa97f63-7-7',
title: 'Seventh Item',
image : 'https://res.cloudinary.com/Sony-liv/image/fetch/c_fill,e_brightness:10,f_auto,fl_lossy,h_494,q_auto:low,w_344/https://origin-staticv2.sonyliv.com/portrait_thumb/Ramsingh-CharlieA_07062021_Lang_Protrait_Thumb.jpg'
},
{
id: '58694a0f-3da1-471f-bd96-1455371e29d72-8-8',
title: 'Eighth Item',
image : 'https://res.cloudinary.com/Sony-liv/image/fetch/c_fill,e_brightness:10,f_auto,fl_lossy,h_494,q_auto:low,w_344/https://origin-staticv2.sonyliv.com/portrait_thumb/6028647473001.jpg'
},
{
id: '58694a0sdsf-3da1-471f-bd96-1445571e29d72-9-9',
title: 'Ninth Item',
image : 'https://res.cloudinary.com/Sony-liv/image/fetch/c_fill,e_brightness:10,f_auto,fl_lossy,h_494,q_auto:low,w_344/https://origin-staticv2.sonyliv.com/portrait_thumb/6033313370001.jpg'
},
];
const maxlimit = 20;
// store the indices of the viewableItmes
const [ viewableItemsIndices, setViewableItemsIndices ] = useState([]);
const itemRefs = useRef(
DATA.map( item=> {})
);
const isScrollingForward = useRef(true);
// use last and current scroll position to determine scroll direction
const lastScrollPosition = useRef(0);
const handleScroll = ({nativeEvent})=>{
let currentPosition = nativeEvent.contentOffset.x
isScrollingForward.current = currentPosition > lastScrollPosition.current
lastScrollPosition.current = currentPosition
}
// Item.js
const Item = (props) => {
let {
item:{ image , title, isViewable },
index
} = props
const itemContainer = isViewable ? styles.itemContainer : [styles.itemContainer]
return (
//add animation to Animated.View
<Animatable.View style={itemContainer} ref={ref=>itemRefs[index]=ref} >
<Image
resizeMode="contain"
style={styles.tinyLogo}
source={{
uri: image,
}}
/>
</Animatable.View>
);
}
return (
<SafeAreaView style={styles.container}>
<FlatListWrapper
horizontal={true}
//{/*give each data item an isViewable prop*/}
data={DATA.map((item,i)=>{
item.isViewable = viewableItemsIndices.findIndex(ix=>ix == i) >=0;
return item
})}
renderItem={(item,i)=><Item {...item} />}
keyExtractor={item => item.id}
onViewableItemsChanged={({viewableItems, changed})=>{
// setViewableItemsIndices(viewableItems.map(item=>item.index))
viewableItems.forEach(item=>{
let itemRef = itemRefs[item.index];
itemRef?.transitionTo({opacity:1})
})
changed.forEach(item=>{
let itemRef = itemRefs[item.index];
if(!item.isViewable)
itemRef?.transitionTo({opacity:0})
})
}}
//{/*config that decides when an item is viewable*/}
viewabilityConfig={{itemVisiblePercentThreshold:100}}
onScroll={handleScroll}
disableScrollMomentum={true}
/>
{/* Extra stuff that just tells you what items should be visible*/}
<Text>Items that should be visible:</Text>
{viewableItemsIndices.map(i=><Text key={'text-'+i}> {DATA[i].title}</Text>)}
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
// flex: 1,
marginTop: StatusBar.currentHeight || 0,
},
itemContainer: {
opacity:0
},
title: {
fontSize: 32,
},
tinyLogo: {
borderRadius : 4,
width: 150,
height:150
},
});
export default FlatListAnimation;
我有如下所示的水平平面列表,我试图在向右滑动时以及在视图中看到新项目时添加弹跳效果。
const Item = ({ title, image, index }) => (
<Animatable.View>
<View style={styles.item}>
<Text>{title}</Text>
</View>
</Animatable.View>
);
const renderItem = ({ item, index }) => (
<Item title={item.title} image={item.image} index={index} />
);
const [viewableItemsIndices, setViewableItemsIndices] = useState([]);
const handleVieweableItemsChanged = useCallback(
({ viewableItems, changed }) => {
setViewableItemsIndices(viewableItems.map((item) => item.index));
},
[]
);
const viewabilityConfig = useRef({
itemVisiblePercentThreshold: 80,
}).current;
return (
<SafeAreaView style={style.container}>
<View style={style.flatlistContainer}>
<FlatList
snapToInterval={120}
horizontal={true}
data={DATA.map((item, i) => {
item.isViewable = viewableItemsIndices.find((ix) => ix == i);
return item;
})}
renderItem={renderItem}
keyExtractor={(item) => item.id}
onViewableItemsChanged={handleVieweableItemsChanged}
viewabilityConfig={viewabilityConfig}
extraData={viewableItemsIndices}
/>
</View>
<Text>Items that should be visible:</Text>
{viewableItemsIndices.map((i) => (
<Text> {DATA[i].title}</Text>
))}
</SafeAreaView>
);
我在 viewableItemsIndices 中的 flatlist 中有所有可见项目,现在我如何根据其索引将可动画动画跟踪到单个项目..
const AnimationRef = useRef(null);
const _onPress = () => {
if(AnimationRef) {
AnimationRef.current?.bounce();
}
}
return (
<TouchableWithoutFeedback onPress={_onPress}>
<Animatable.View ref={AnimationRef}>
<Text>Bounce me!</Text>
</Animatable.View>
</TouchableWithoutFeedback>
);
编辑:下面是完整的代码
import React , { useState, useRef,useCallback } from 'react';
import { SafeAreaView, Image , View, FlatList, StyleSheet, Text, StatusBar } from 'react-native';
import { Ionicons, MaterialCommunityIcons , FontAwesome5 } from "@expo/vector-icons";
import * as Animatable from 'react-native-animatable';
import FlatListWrapper from "../anim/flatwrapper";
const FlatListAnimation = () => {
const DATA = [
{
id: 'bd7acbea-c1b1-46c2-aed5-3ad53abb28ba',
title: 'First Item Item Item',
image : 'https://res.cloudinary.com/Sony-liv/image/fetch/c_fill,e_brightness:10,f_auto,fl_lossy,h_494,q_auto:low,w_344/https://origin-staticv2.sonyliv.com/portrait_thumb/scam_Lang_Protrait_Thumb.jpg'
},
{
id: '3ac68afc-c605-48d3-a4f8-fbd91aa97f63',
title: 'Second Item',
image : 'https://res.cloudinary.com/Sony-liv/image/fetch/c_fill,e_brightness:10,f_auto,fl_lossy,h_494,q_auto:low,w_344/https://origin-staticv2.sonyliv.com/portrait_thumb/Ramsingh-CharlieA_07062021_Lang_Protrait_Thumb.jpg'
},
{
id: '58694a0f-3da1-471f-bd96-145571e29d72',
title: 'Third Item',
image : 'https://res.cloudinary.com/Sony-liv/image/fetch/c_fill,e_brightness:10,f_auto,fl_lossy,h_494,q_auto:low,w_344/https://origin-staticv2.sonyliv.com/portrait_thumb/6028647473001.jpg'
},
{
id: '58694a0sdsf-3da1-471f-bd96-145571e29d72',
title: 'Third Item',
image : 'https://res.cloudinary.com/Sony-liv/image/fetch/c_fill,e_brightness:10,f_auto,fl_lossy,h_494,q_auto:low,w_344/https://origin-staticv2.sonyliv.com/portrait_thumb/6033313370001.jpg'
},
{
id: '58694a0f-3ddsda1-471f-bd96-145571e29d72',
title: 'Third Item',
image : 'https://res.cloudinary.com/Sony-liv/image/fetch/c_fill,e_brightness:10,f_auto,fl_lossy,h_494,q_auto:low,w_344/https://origin-staticv2.sonyliv.com/portrait_thumb/6033325319001_v2.jpg'
},
{
id: 'bd7acbea-c1b1-46c2-aed5-3ad531abb28ba',
title: 'First Item Item Item',
image : 'https://res.cloudinary.com/Sony-liv/image/fetch/c_fill,e_brightness:10,f_auto,fl_lossy,h_494,q_auto:low,w_344/https://origin-staticv2.sonyliv.com/portrait_thumb/scam_Lang_Protrait_Thumb.jpg'
},
{
id: '3ac68afc-c605-48d3-a4f8-fbd912aa97f63',
title: 'Second Item',
image : 'https://res.cloudinary.com/Sony-liv/image/fetch/c_fill,e_brightness:10,f_auto,fl_lossy,h_494,q_auto:low,w_344/https://origin-staticv2.sonyliv.com/portrait_thumb/Ramsingh-CharlieA_07062021_Lang_Protrait_Thumb.jpg'
},
{
id: '58694a0f-3da1-471f-bd96-1455371e29d72',
title: 'Third Item',
image : 'https://res.cloudinary.com/Sony-liv/image/fetch/c_fill,e_brightness:10,f_auto,fl_lossy,h_494,q_auto:low,w_344/https://origin-staticv2.sonyliv.com/portrait_thumb/6028647473001.jpg'
},
{
id: '58694a0sdsf-3da1-471f-bd96-1445571e29d72',
title: 'Third Item',
image : 'https://res.cloudinary.com/Sony-liv/image/fetch/c_fill,e_brightness:10,f_auto,fl_lossy,h_494,q_auto:low,w_344/https://origin-staticv2.sonyliv.com/portrait_thumb/6033313370001.jpg'
},
];
const maxlimit = 20;
// Item.js
const Item = (props) => {
const {
item:{image , title, isViewable},
index
} = props
let animation = null
// set your animation type base on isViewable
if(isViewable || isViewable == 0){
animation = ""
}
else{
animation = ""
}
return (
//add animation to Animated.View
<Animatable.View style={style.itemContainer} >
<Image
resizeMode="contain"
style={styles.tinyLogo}
source={{
uri: image,
}}
/>
</Animatable.View>
);
}
const renderItem = ({ item , index }) => (
<Item title={item.title} image = {item.image} index= {index} />
);
// store the indices of the viewableItmes
const [ viewableItemsIndices, setViewableItemsIndices ] = useState([]);
const handleVieweableItemsChanged = useCallback(({viewableItems, changed }) => {
setViewableItemsIndices(viewableItems.map(item=>item.index))
}, []);
// config that decides when an item is viewable
const viewabilityConfig = useRef({
// useRef to try to counter the view rerender thing
itemVisiblePercentThreshold:80
}).current;
// wrapped handleViewChange in useCallback to try to handle the re-render error
return (
<SafeAreaView style={style.container}>
<FlatListWrapper
horizontal={true}
//{/*give each data item an isViewable prop*/}
data={DATA.map((item,i)=>{
item.isViewable=viewableItemsIndices.find(ix=>ix == i)
return item
})}
renderItem={item=><Item {...item}/>}
keyExtractor={item => item.id}
onViewableItemsChanged={({viewableItems, changed})=>{
// set viewableItemIndices to the indices when view change
setViewableItemsIndices(viewableItems.map(item=>item.index))
}}
//{/*config that decides when an item is viewable*/}
viewabilityConfig={{itemVisiblePercentThreshold:80}}
extraData={viewableItemsIndices}
/>
{/* Extra stuff that just tells you what items should be visible*/}
<Text>Items that should be visible:</Text>
{viewableItemsIndices.map(i=><Text key={'text-'+i}> {DATA[i].title}</Text>)}
</SafeAreaView>
);
}
const style = StyleSheet.create({
container:{
padding:10,
alignItems:'center'
},
item:{
borderWidth:1,
padding:5,
borderColor:'green',
},
itemContainer:{
flex:1,
backgroundColor: 'transparent',
marginVertical: 8,
width:120,
alignItems:'center',
marginHorizontal: 3,
}
})
const styles = StyleSheet.create({
container: {
flex: 1,
marginTop: StatusBar.currentHeight || 0,
},
item: {
flex:1,
backgroundColor: 'transparent',
marginVertical: 8,
width:120,
alignItems:'center',
marginHorizontal: 3,
},
title: {
fontSize: 32,
},
tinyLogo: {
borderRadius : 4,
width: 150,
height:150
},
});
export default FlatListAnimation;
FlatWrapper.js
//FlatListWrapper
import React, {useRef, useState, useCallback } from 'react';
import {
View, StyleSheet, FlatList ,
} from 'react-native';
const FlatListWrapper = (props) => {
// useRef to avoid onViewableItemsChange on fly error
const viewabilityConfig = useRef({
// useRef to try to counter the view rerender thing
itemVisiblePercentThreshold:80
}).current;
// wrapped handleViewChange in useCallback to try to handle the onViewableItemsChange on fly error
const onViewChange = useCallback(props.onViewableItemsChanged,[])
return (
<View style={style.flatlistContainer}>
<FlatList
{...props}
horizontal={true}
onViewableItemsChanged={onViewChange}
/>
</View>
);
}
const style = StyleSheet.create({
flatlistContainer:{
borderWidth:1,
borderColor:'red',
width:'100%',
},
})
export default FlatListWrapper
您需要做的就是编辑 Item
中的动画。我用 slideIn/slideOut 替换了 fadeIn 和 fadeOut(它很糟糕,但它有动作。让它反弹需要使用 y 值):
const Item = (props) => {
const {
item:{title, isViewable}
} = props
// to get bounce effect, we will animate a translation
const translateXY = useRef(new Animated.ValueXY()).current;
const slideValue = 40
const slideIn = () => {
Animated.spring(translateXY, {
toValue: {x:slideValue,y:5},
duration: 250,
useNativeDriver:false
}).start();
};
const slideOut = () => {
Animated.timing(translateXY, {
toValue: {x:-slideValue,y:5},
duration: 250,
useNativeDriver:false
}).start();
};
// fade in/out base on if isViewable
if(isViewable || isViewable == 0)
slideIn()
else
slideOut()
const initialState={transform:[{translateX:-slideValue}]}
const animation = {transform:translateXY.getTranslateTransform()}
return (
//add animation to Animated.View
<Animated.View style={[style.itemContainer,initialState,animation]}>
<View style={style.item}>
<Text style={style.title}>{title}</Text>
</View>
</Animated.View>
);
}
那是一个很棒的图书馆!不用为 Animated.Value 操心真是太好了。
// Item.js
const Item = (props) => {
const {
item:{title, isViewable},
index
} = props
let animation = null
// set your animation type base on isViewable
if(isViewable || isViewable == 0){
animation = "bounceInLeft"
}
else{
animation = "bounceOutLeft"
}
return (
//add animation to Animated.View
<Animatable.View style={style.itemContainer} animation = {animation}>
<View style={style.item}>
<Text style={style.title}>{title}</Text>
</View>
</Animatable.View>
);
}
// main component
const FlatListAnimation = () => {
// store the indices of the viewableItmes
const [ viewableItemsIndices, setViewableItemsIndices ] = useState([]);
return (
<SafeAreaView style={style.container}>
<FlatListWrapper
horizontal={true}
//{/*give each data item an isViewable prop*/}
data={DATA.map((item,i)=>{
item.isViewable=viewableItemsIndices.find(ix=>ix == i)
return item
})}
renderItem={item=><Item {...item}/>}
keyExtractor={item => item.id}
onViewableItemsChanged={({viewableItems, changed})=>{
// set viewableItemIndices to the indices when view change
setViewableItemsIndices(viewableItems.map(item=>item.index))
}}
//{/*config that decides when an item is viewable*/}
viewabilityConfig={{itemVisiblePercentThreshold:80}}
extraData={viewableItemsIndices}
/>
{/* Extra stuff that just tells you what items should be visible*/}
<Text>Items that should be visible:</Text>
{viewableItemsIndices.map(i=><Text key={'text-'+i}> {DATA[i].title}</Text>)}
</SafeAreaView>
);
}
const style = StyleSheet.create({
container:{
padding:10,
alignItems:'center'
},
flatlistContainer:{
borderWidth:1,
borderColor:'red',
width:'50%',
height:40
},
item:{
borderWidth:1,
padding:5,
},
itemContainer:{
padding:5,
}
})
//FlatListWrapper
import React, {useRef, useState, useCallback } from 'react';
import {
View, StyleSheet, FlatList ,
} from 'react-native';
const FlatListWrapper = (props) => {
// useRef to avoid onViewableItemsChange on fly error
const viewabilityConfig = useRef({
// useRef to try to counter the view rerender thing
itemVisiblePercentThreshold:80
}).current;
// wrapped handleViewChange in useCallback to try to handle the onViewableItemsChange on fly error
const onViewChange = useCallback(props.onViewableItemsChanged,[])
return (
<View style={style.flatlistContainer}>
<FlatList
{...props}
horizontal={true}
onViewableItemsChanged={onViewChange}
/>
</View>
);
}
const style = StyleSheet.create({
flatlistContainer:{
borderWidth:1,
borderColor:'red',
width:'50%',
height:40
},
})
export default FlatListWrapper
好的,所以我认为闪烁是由 extraData
道具引起的,导致 Flatlist 重新加载。重新阅读 Animatable 文档后,我发现有一种方法可以访问 Animatable View refs 并从该 ref 调用动画。知道这一点后,不再需要在屏幕上的项目发生变化时强制平面列表重新渲染,并且消除了我发现的所有闪烁
import React , { useState, useRef,useCallback } from 'react';
import { SafeAreaView, Image , View, FlatList, StyleSheet, Text, StatusBar } from 'react-native';
import { Ionicons, MaterialCommunityIcons , FontAwesome5 } from "@expo/vector-icons";
import * as Animatable from 'react-native-animatable';
import FlatListWrapper from "../components/FlatListWrapper";
const FlatListAnimation = () => {
const DATA = [
{
id: 'bd7acbea-c1b1-46c2-aed5-3ad53abb28ba',
title: 'First Item Item Item',
image : 'https://res.cloudinary.com/Sony-liv/image/fetch/c_fill,e_brightness:10,f_auto,fl_lossy,h_494,q_auto:low,w_344/https://origin-staticv2.sonyliv.com/portrait_thumb/scam_Lang_Protrait_Thumb.jpg'
},
{
id: '3ac68afc-c605-48d3-a4f8-fbd91aa97f63',
title: 'Second Item',
image : 'https://res.cloudinary.com/Sony-liv/image/fetch/c_fill,e_brightness:10,f_auto,fl_lossy,h_494,q_auto:low,w_344/https://origin-staticv2.sonyliv.com/portrait_thumb/Ramsingh-CharlieA_07062021_Lang_Protrait_Thumb.jpg'
},
{
id: '58694a0f-3da1-471f-bd96-145571e29d72-3-3',
title: 'Third Item',
image : 'https://res.cloudinary.com/Sony-liv/image/fetch/c_fill,e_brightness:10,f_auto,fl_lossy,h_494,q_auto:low,w_344/https://origin-staticv2.sonyliv.com/portrait_thumb/6028647473001.jpg'
},
{
id: '58694a0sdsf-3da1-471f-bd96-145571e29d72-4-4',
title: 'Fourth Item',
image : 'https://res.cloudinary.com/Sony-liv/image/fetch/c_fill,e_brightness:10,f_auto,fl_lossy,h_494,q_auto:low,w_344/https://origin-staticv2.sonyliv.com/portrait_thumb/6033313370001.jpg'
},
{
id: '58694a0f-3ddsda1-471f-bd96-145571e29d72-5-5',
title: 'Fifth Item',
image : 'https://res.cloudinary.com/Sony-liv/image/fetch/c_fill,e_brightness:10,f_auto,fl_lossy,h_494,q_auto:low,w_344/https://origin-staticv2.sonyliv.com/portrait_thumb/6033325319001_v2.jpg'
},
{
id: 'bd7acbea-c1b1-46c2-aed5-3ad531abb28ba-6-6',
title: 'Sixth Item Item Item',
image : 'https://res.cloudinary.com/Sony-liv/image/fetch/c_fill,e_brightness:10,f_auto,fl_lossy,h_494,q_auto:low,w_344/https://origin-staticv2.sonyliv.com/portrait_thumb/scam_Lang_Protrait_Thumb.jpg'
},
{
id: '3ac68afc-c605-48d3-a4f8-fbd912aa97f63-7-7',
title: 'Seventh Item',
image : 'https://res.cloudinary.com/Sony-liv/image/fetch/c_fill,e_brightness:10,f_auto,fl_lossy,h_494,q_auto:low,w_344/https://origin-staticv2.sonyliv.com/portrait_thumb/Ramsingh-CharlieA_07062021_Lang_Protrait_Thumb.jpg'
},
{
id: '58694a0f-3da1-471f-bd96-1455371e29d72-8-8',
title: 'Eighth Item',
image : 'https://res.cloudinary.com/Sony-liv/image/fetch/c_fill,e_brightness:10,f_auto,fl_lossy,h_494,q_auto:low,w_344/https://origin-staticv2.sonyliv.com/portrait_thumb/6028647473001.jpg'
},
{
id: '58694a0sdsf-3da1-471f-bd96-1445571e29d72-9-9',
title: 'Ninth Item',
image : 'https://res.cloudinary.com/Sony-liv/image/fetch/c_fill,e_brightness:10,f_auto,fl_lossy,h_494,q_auto:low,w_344/https://origin-staticv2.sonyliv.com/portrait_thumb/6033313370001.jpg'
},
];
const maxlimit = 20;
// store the indices of the viewableItmes
const [ viewableItemsIndices, setViewableItemsIndices ] = useState([]);
const itemRefs = useRef(
DATA.map( item=> {})
);
const isScrollingForward = useRef(true);
// use last and current scroll position to determine scroll direction
const lastScrollPosition = useRef(0);
const handleScroll = ({nativeEvent})=>{
let currentPosition = nativeEvent.contentOffset.x
isScrollingForward.current = currentPosition > lastScrollPosition.current
lastScrollPosition.current = currentPosition
}
// Item.js
const Item = (props) => {
let {
item:{ image , title, isViewable },
index
} = props
const itemContainer = isViewable ? styles.itemContainer : [styles.itemContainer]
return (
//add animation to Animated.View
<Animatable.View style={itemContainer} ref={ref=>itemRefs[index]=ref} >
<Image
resizeMode="contain"
style={styles.tinyLogo}
source={{
uri: image,
}}
/>
</Animatable.View>
);
}
return (
<SafeAreaView style={styles.container}>
<FlatListWrapper
horizontal={true}
//{/*give each data item an isViewable prop*/}
data={DATA.map((item,i)=>{
item.isViewable = viewableItemsIndices.findIndex(ix=>ix == i) >=0;
return item
})}
renderItem={(item,i)=><Item {...item} />}
keyExtractor={item => item.id}
onViewableItemsChanged={({viewableItems, changed})=>{
// setViewableItemsIndices(viewableItems.map(item=>item.index))
viewableItems.forEach(item=>{
let itemRef = itemRefs[item.index];
itemRef?.transitionTo({opacity:1})
})
changed.forEach(item=>{
let itemRef = itemRefs[item.index];
if(!item.isViewable)
itemRef?.transitionTo({opacity:0})
})
}}
//{/*config that decides when an item is viewable*/}
viewabilityConfig={{itemVisiblePercentThreshold:100}}
onScroll={handleScroll}
disableScrollMomentum={true}
/>
{/* Extra stuff that just tells you what items should be visible*/}
<Text>Items that should be visible:</Text>
{viewableItemsIndices.map(i=><Text key={'text-'+i}> {DATA[i].title}</Text>)}
</SafeAreaView>
);
}
const styles = StyleSheet.create({
container: {
// flex: 1,
marginTop: StatusBar.currentHeight || 0,
},
itemContainer: {
opacity:0
},
title: {
fontSize: 32,
},
tinyLogo: {
borderRadius : 4,
width: 150,
height:150
},
});
export default FlatListAnimation;