I'm trying to make animation in flatlist, something like this, it will have spring animation when moving item
Something like apple music you can see: https://streamable.com/yg1j2j
With the help of #David Scholz, I was able to make exactly the same, here is expo for that:
https://snack.expo.dev/#quockhanh210199/animated-list-selection
But problem is, when i enable scroll it will have weird animation, so my question is, how to make the animation behavior correct if i enable scroll, you can see the weird animation if you enable scroll and and more item, or use below code:
import React, {useState, useCallback} from 'react';
import { StyleSheet, Text,View, SafeAreaView, ScrollView, StatusBar, Animated,FlatList,TouchableOpacity } from 'react-native';
const App = () => {
const data = [
{
id: "1",
text: "Results",
},
{
id: "2",
text: "Products",
},
{
id: "3",
text: "Stores",
},
{
id: "4",
text: "Stores",
},
{
id: "5",
text: "Stores",
},
{
id: "6",
text: "Stores",
},
]
const [translateValue] = useState(new Animated.Value(0))
const [selected, setSelected] = useState(0)
const onPress = React.useCallback(
(index) => {
setSelected(index)
Animated.spring(translateValue, {
toValue: index * 100,
velocity: 5,
useNativeDriver: true,
}).start()
},
[translateValue, setSelected]
)
return (
<View style={{ flexDirection: "row", marginTop: 100 }}>
<Animated.View
style={[
{
position: "absolute",
backgroundColor: "black",
top: -5,
right: 0,
bottom: 0,
left: 15,
width: 70,
height: 30,
borderRadius: 12,
transform: [{ translateX: translateValue }],
},
]}
/>
<FlatList
data={data}
horizontal={true}
scrollEnabled={true}
renderItem={({ item, index }) => {
return (
<TouchableOpacity onPress={() => onPress(index)}>
<View style={{ width: 100, borderRadius: 10 }}>
<Text style={[{ textAlign: "center" }, index === selected ? { color: "white" } : { color: "black" }]}>
{item.text}
</Text>
</View>
</TouchableOpacity>
)
}}
keyExtractor={(item) => item.id}
/>
</View>
)
}
export default App
Please help, thank you so much
Related
I'm trying to use a useRef hook so a scrollview and my pan gesture handler can share a common ref. but once I initialize the useRef() hook and pass it to both components, it breaks with this error
TypeError: Attempted to assign to readonly property.
I've tried typecasting and adding types to the useRef call but it returns the same error. Can someone help please?
My component:
import { StyleSheet, Text, View, Image, Dimensions } from "react-native";
import React from "react";
import {
PanGestureHandler,
PanGestureHandlerGestureEvent,
PanGestureHandlerProps,
} from "react-native-gesture-handler";
import Animated, {
runOnJS,
useAnimatedGestureHandler,
useAnimatedStyle,
useSharedValue,
withTiming,
} from "react-native-reanimated";
import { FontAwesome } from "#expo/vector-icons";
export interface InfluencerItemProps
extends Pick<PanGestureHandlerProps, "simultaneousHandlers"> {
id?: string;
name: string;
userName: string;
profileImg: string;
rating?: Number;
onDismiss?: (Item: InfluencerItemProps) => void;
}
const ITEM_HEIGHT = 65;
const { width: SCREEN_WIDTH } = Dimensions.get("window");
const TRANSLATE_X_THRESHOLD = -SCREEN_WIDTH * 0.3;
const InfluencerItem = (props: InfluencerItemProps) => {
const { name, userName, profileImg, onDismiss, simultaneousHandlers } = props;
const translateX = useSharedValue(0);
const marginVertical = useSharedValue("2%");
const R_Height = useSharedValue(ITEM_HEIGHT);
const opacity = useSharedValue(1);
const panGesture = useAnimatedGestureHandler<PanGestureHandlerGestureEvent>({
onActive: (event) => {
translateX.value = event.translationX;
},
onEnd: () => {
const shouldbeDismissed = translateX.value < TRANSLATE_X_THRESHOLD;
if (shouldbeDismissed) {
translateX.value = withTiming(-SCREEN_WIDTH);
R_Height.value = withTiming(0);
marginVertical.value = withTiming("0%");
opacity.value = withTiming(0, undefined, (isFinished) => {
if (isFinished && onDismiss) {
runOnJS(onDismiss)(props);
}
});
} else {
translateX.value = withTiming(0);
}
},
});
const rStyle = useAnimatedStyle(() => ({
transform: [
{
translateX: translateX.value,
},
],
}));
const rIconContainerStyle = useAnimatedStyle(() => {
const opacity = withTiming(
translateX.value < TRANSLATE_X_THRESHOLD ? 1 : 0
);
return { opacity };
});
const RContainerStyle = useAnimatedStyle(() => {
return {
height: R_Height.value,
opacity: opacity.value,
marginVertical: marginVertical.value,
};
});
return (
<Animated.View style={[styles.wrapper, RContainerStyle]}>
<Animated.View style={[styles.iconContainer, rIconContainerStyle]}>
<FontAwesome name="trash" size={ITEM_HEIGHT * 0.5} color="white" />
</Animated.View>
<PanGestureHandler
simultaneousHandlers={simultaneousHandlers}
onGestureEvent={panGesture}
>
<Animated.View style={[styles.container, rStyle]}>
<Image
source={{
uri: profileImg,
}}
style={styles.image}
/>
<View style={styles.text}>
<Text style={styles.name}>{name}</Text>
<Text style={styles.userName}>{userName}</Text>
</View>
</Animated.View>
</PanGestureHandler>
</Animated.View>
);
};
export default InfluencerItem;
const styles = StyleSheet.create({
wrapper: {
width: "100%",
alignItems: "center",
},
container: {
flexDirection: "row",
borderWidth: 1,
borderRadius: 12,
height: ITEM_HEIGHT,
width: "100%",
backgroundColor: "#FFFFFF",
},
image: {
marginVertical: 10,
marginHorizontal: "4%",
height: 48,
width: 48,
borderRadius: 50,
},
text: {
justifyContent: "center",
alignItems: "flex-start",
marginHorizontal: 6,
},
name: {
fontSize: 14,
fontWeight: "500",
color: "#121212",
},
userName: {
fontSize: 12,
fontWeight: "400",
color: "#121212",
},
iconContainer: {
height: ITEM_HEIGHT,
width: ITEM_HEIGHT,
backgroundColor: "red",
position: "absolute",
right: "2.5%",
justifyContent: "center",
alignItems: "center",
},
});
InfluencerItem.defaultProps = {
name: "UserName",
userName: "userName",
profileImg:
"https://d2qp0siotla746.cloudfront.net/img/use-cases/profile-picture/template_0.jpg",
rating: "4",
};
This is my Screen:
import {
StyleSheet,
Text,
View,
SafeAreaView,
TextInput,
ScrollView,
} from "react-native";
import { StatusBar } from "expo-status-bar";
import React, { useCallback, useRef, useState } from "react";
import InfluencerItem from "../../components/InfluencerItem";
import { InfluencerItemProps } from "../../components/InfluencerItem";
// import { ScrollView } from "react-native-gesture-handler";
type Props = {};
const Saved = (props: Props) => {
const [search, setSearch] = useState<string>("");
const [influencerData, setInfluencerData] = useState(influencerz);
const handleSearch = () => {
console.log(search);
};
const onDismiss = useCallback((Item: InfluencerItemProps) => {
setInfluencerData((influencers) =>
influencers.filter((item) => item.id !== Item.id)
);
}, []);
const ref = useRef(null); //useRef initialization
return (
<SafeAreaView style={{ flex: 1 }}>
<View style={styles.container}>
<View style={styles.saved}>
{/* Saved Kikos component goes in here */}
<ScrollView ref={ref}> //passed ref here
{influencerData.map((influencer) => (
<InfluencerItem
key={influencer.id}
name={influencer.name}
userName={influencer.handle}
profileImg={influencer.image}
onDismiss={onDismiss}
simultaneousHandlers={ref} //also passed ref here
/>
))}
</ScrollView>
</View>
<Text style={styles.bottomText}>No Saved Kikos again</Text>
</View>
</SafeAreaView>
);
};
export default Saved;
const styles = StyleSheet.create({
container: {
paddingHorizontal: "4%",
},
headerText: {
color: "#121212",
fontWeight: "700",
lineHeight: 30,
fontSize: 20,
marginTop: 40,
},
search: {
borderRadius: 12,
backgroundColor: "#D9D9D9",
fontSize: 14,
lineHeight: 21,
color: "#7A7B7C",
paddingLeft: 10,
paddingRight: 5,
height: 45,
marginTop: 15,
position: "relative",
},
innerSearch: {
position: "absolute",
top: 30,
right: 10,
},
saved: {
backgroundColor: "rgba(217, 217, 217, 0.15)",
marginTop: 22,
paddingVertical: "7%",
marginBottom: 34,
},
bottomText: {
fontSize: 14,
fontWeight: "500",
textAlign: "center",
},
});
Use createRef() instead of useRef() as mentioned in the documentation.
const imagePinch = React.createRef();
return (
<RotationGestureHandler
simultaneousHandlers={imagePinch}
....
There is a complete example here in TypeScript.
Also make sure to use Animated version of components wherever applicable (<Animated.View> instead of <View>, <Animated.Image> instead of <Image> etc)
when I run my application it's okay and work If I create an array and put it in the data in FlatList like this array
const photos = [
{ id: 1, title: "Photo 1" },
{ id: 2, title: "Photo 2" },
{ id: 3, title: "Photo 3" },
{ id: 4, title: "Photo 4" },
{ id: 5, title: "Photo 5" },
{ id: 6, title: "Photo 6" },
];
But when I replace the photos array with an API, The app doesn't work. I tried more than API, I think the error is in my code not in the API,
This error appears to me " scrollToIndex out of range: request index1 but maximum is -1 "
What's wrong with my code?
import React, { useState, useRef, useEffect } from "react";
import {
StyleSheet,
View,
FlatList,
Dimensions,
Text,
TouchableOpacity,
} from "react-native";
import { AntDesign } from "#expo/vector-icons";
import axios from "axios";
const phoneWidth = Dimensions.get("screen").width;
const phoneHeight = Dimensions.get("screen").height;
function ScrollScreen() {
const [index, setIndex] = useState(0);
const [border, setBorder] = useState(0);
const refContainer = useRef();
const refBox = useRef();
const [data, setData] = useState([]);
useEffect(() => {
photos();
}, []);
function photos() {
axios
.get("https://jsonplaceholder.typicode.com/photos")
.then(async function (response) {
setData(response.data);
})
.catch((err) => console.error(err));
}
useEffect(() => {
refContainer.current.scrollToIndex({ animated: true, index });
}, [index]);
useEffect(() => {
refBox.current.scrollToIndex({ animated: true, index });
}, [index]);
const theNext = () => {
if (index < photos.length - 1) {
setIndex(index + 1);
setBorder(index + 1);
}
};
const thePrevious = () => {
if (index > 0) {
setIndex(index - 1);
setBorder(index - 1);
}
};
return (
<View style={styles.con}>
<AntDesign
style={[styles.iconConPosition, { left: phoneWidth * 0.05 }]}
onPress={thePrevious}
size={55}
color="#0dddcb"
name="caretleft"
/>
<AntDesign
style={[styles.iconConPosition, { right: phoneWidth * 0.05 }]}
onPress={theNext}
size={55}
color="#0dddcb"
name="caretright"
/>
<FlatList
scrollEnabled={false}
ref={refContainer}
data={data}
// data={photos}
keyExtractor={(item, index) => item.id.toString()}
style={styles.flatList}
renderItem={({ item, index }) => (
<View
style={{
height: 150,
width: phoneWidth * 0.7,
margin: 50,
backgroundColor: "red",
alignSelf: "center",
justifyContent: "center",
alignItems: "center",
}}
>
<Text>{item.id}</Text>
<Text>{item.title}</Text>
</View>
)}
horizontal
pagingEnabled //تفعيل خاصية التمرير
showsHorizontalScrollIndicator={false}
/>
<FlatList
ref={refBox}
data={data}
// data={photos}
keyExtractor={(item, index) => item.id.toString()}
style={styles.flatList}
renderItem={({ item, index }) => (
<TouchableOpacity
onPress={() => {
setIndex(index);
setBorder(index);
}}
style={
border === index
? {
height: 100,
width: phoneWidth * 0.4,
margin: 7,
backgroundColor: "gray",
alignSelf: "center",
justifyContent: "center",
alignItems: "center",
borderWidth: 2,
borderColor: "blue",
}
: {
height: 100,
width: phoneWidth * 0.4,
margin: 7,
backgroundColor: "gray",
alignSelf: "center",
justifyContent: "center",
alignItems: "center",
}
}
>
<Text>{item.id}</Text>
<Text>{item.title}</Text>
</TouchableOpacity>
)}
horizontal
/>
<Text>{index}</Text>
</View>
);
}
export default ScrollScreen;
Initially data is an empty array, but index is set to 0. On the first invocation of the useEffect that tries to scroll, there is an error because scrollToIndex(0) is an error when data is empty, since there is no item for index 0.
Try initializing the border and index state to -1 instead of 0 like:
const [index, setIndex] = useState(-1);
const [border, setBorder] = useState(-1);
On a separate but related note the theNext function has an error, it should be checking data.length instead of photos.length.
Ran into the same issue just now. Using ScrollToOffSet({offset: number, animated: boolean}) instead of scrollToIndex solved the issue for me
I'm trying to Animated "Scale" item when pressed In/Out,
but have an issue when pressing any item from the list there's a random item scaled!
I'm set the active index to Ref for every item when pressed and make a condition to scale the item based on it but not works well :\
here's My Code Snippet
(Live) One Here.
import React from 'react';
import {
FlatList,
SafeAreaView,
Text,
Pressable,
Animated,
} from 'react-native';
const examples = [
{
key: 1,
name: 'Animated Progress Bar Indicator',
},
{
key: 2,
name: 'FlatList Carousel Animation 1',
},
{
key: 3,
name: 'FlatList Carousel Animation 2',
},
{
key: 4,
name: 'FlatList Carousel Animation 3',
},
];
const HomeScreen = ({navigation}) => {
const scaleItem = React.useRef(new Animated.Value(1)).current;
const activeIndexRef = React.useRef(null);
const onPressInItem = (inds) => {
activeIndexRef.current = inds;
console.log('activeIndexRef', activeIndexRef.current);
Animated.spring(scaleItem, {
toValue: 0.95,
useNativeDriver: true,
}).start();
console.log(inds === activeIndexRef.current ? 'hey' : 'no');
};
const onPressOutItem = (inds) => {
activeIndexRef.current = inds;
console.log('activeIndexRef-onPressOutItem', activeIndexRef.current);
Animated.spring(scaleItem, {
toValue: 1.0,
useNativeDriver: true,
}).start();
console.log(inds === activeIndexRef.current ? 'hey' : 'no');
};
return (
<SafeAreaView style={styles.container}>
<FlatList
data={examples}
keyExtractor={(item) => item.key.toString()}
renderItem={({item, index}) => {
return (
<Pressable
onPressIn={() => onPressInItem(index)}
onPressOut={() => onPressOutItem(index)}>
<Animated.View
style={{
flexDirection: 'row',
backgroundColor: '#56f',
alignItems: 'center',
paddingVertical: 10,
marginVertical: 10,
transform: [
{
scale: activeIndexRef.current === index ? scaleItem : 1,
},
],
}}>
<Text style={styles.itemTitle}>🔥 </Text>
<Text style={styles.itemTitle}>{item.name}</Text>
</Animated.View>
</Pressable>
);
}}
/>
</SafeAreaView>
);
};
export default HomeScreen;
I'm a 13-year-old beginner to react native, and I need help with the problem in the title.
I need to do that all on a touchable opacity click. I have tried searching for other solutions on google but found nothing.
It's actually a tutorial on Youtube.
Here is the link: Tutorial
Please help me.
Thank you.
import React, { useState } from 'react';
import { StyleSheet, Text, View, FlatList, TextInput, TouchableOpacity } from 'react-native';
import Header from './components/header';
import TodoItem from './components/todoItem';
export default function App() {
const [todos, setTodos] = useState([
{ text: 'turn on laptop', key: '1'},
{ text: 'create an app', key: '2'},
{ text: 'play on the switch', key: '3'}
]);
const addToDo = () => {
}
const PressHandler = (key) => {
setTodos((prevTodos) => {
return prevTodos.filter(todo => todo.key != key);
});
}
return (
<View style={styles.container}>
<Header />
<View style={styles.content}>
{/* todo form */}
<View style={styles.list}>
<FlatList
data={todos}
renderItem={({ item }) => (
<TodoItem item={item} PressHandler={PressHandler}/>
)}
/>
</View>
</View>
<TextInput style={styles.todoInput} maxLength={20} onChange={() => this.setState} />
<TouchableOpacity style={styles.submitTodo} onPress={addToDo}>
<Text style={styles.submitTodoText}>Done</Text>
</TouchableOpacity>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#fff",
},
content: {
padding: 40,
},
todoInput: {
position: "absolute",
bottom: 1,
},
todoInput: {
height: 31,
borderColor: "coral",
borderStyle: "dashed",
borderWidth: 1,
borderRadius: 10,
position: "absolute",
bottom: 26,
width: "73%",
textAlign: "center",
marginLeft: 25,
},
submitTodo: {
padding: 10,
width: "15%",
position: "absolute",
right: 15,
bottom: 26,
backgroundColor: "coral",
borderRadius: 10,
alignItems: "center",
},
submitTodoText: {
color: "#fff",
fontSize: 10,
}
});
Again Thank you, for spending time on this.
I try to build a news app that shows on the MainPage an overview of news items.
The first 3 items need to be rendered different as the rest, using a FlatList.
First item is a 100% background image with some text on it (did this with: if index === 0))
The second and third item needs to be background images with titles in a row (so next to each other)
The rests is a list with image, title, and date (underneath each other)
I tried everything but item 2 and 3 is not working.
Tried with this little basic test:
import React, { Component } from "react";
import { View, StyleSheet, Text, FlatList } from "react-native";
export default class Screen1 extends Component {
state = {
data: [
{
text: "one"
},
{
item1: {
text: "two"
},
item2: {
text: "three"
}
},
{
item1: {
text: "four"
},
item2: {
text: "five"
}
},
{
item1: {
text: "six"
}
}
]
};
renderItem = ({ item, index }) => {
if (index === 0) {
return (
<View style={styles.bigSquare}>
<Text> {item.text} </Text>{" "}
</View>
);
} else if (index > 0 || index <= 3) {
return (
<View
style={{
flexDirection: "row"
}}
>
{" "}
{item.item2 && (
<View
style={[
styles.smallSquare,
{
backgroundColor: "red"
}
]}
>
<Text> {item.item2.text} </Text> <Text> {item.item2.text} </Text>{" "}
</View>
)}{" "}
</View>
);
}
};
keyExtractor = (item, index) => `${index}`;
render() {
return (
<View style={styles.container}>
<FlatList
data={this.state.data}
renderItem={this.renderItem}
keyExtractor={this.keyExtractor}
/>{" "}
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1
},
bigSquare: {
flexDirection: "column",
height: 220,
width: "100%",
margin: 10,
backgroundColor: "yellow",
justifyContent: "center",
alignItems: "center"
},
smallSquare: {
height: 100,
width: 100,
margin: 10,
backgroundColor: "green",
justifyContent: "center",
alignItems: "center"
}
});
Can someone point me in the right direction?
Example:
This approach is a little bit different. Separate your list into 3 parts which include,
First item
Second & third items
Rest of items (use FlatList to render this part)
Finally, you can display those 3 parts in different ways. But make sure to display part 1 & 2 as a ListHeaderComponent of FlatList.
import React, { Component } from "react";
import { SafeAreaView, View, FlatList, StyleSheet, Text, Dimensions } from "react-native";
const ScreenWidth = Dimensions.get('window').width;
const DATA = [
{
id: "1",
title: "First Item"
},
{
id: "2",
title: "Second Item"
},
{
id: "3",
title: "Third Item"
},
{
id: "4",
title: "Forth Item"
},
{
id: "5",
title: "Fifth Item"
},
{
id: "6",
title: "Sixth Item"
},
{
id: "7",
title: "Seventh Item"
}
];
export default class Example extends Component {
renderHeader = () => (
<View>
{/* Display index === 0 item */}
<View style={styles.bigSquare}>
<Text>{DATA[0].title}</Text>
</View>
{/* Display index > 0 && index < 3 items */}
<View style={{ flexDirection: 'row' }}>
<View style={styles.middleSqure}>
<Text>{DATA[1].title}</Text>
</View>
<View style={styles.middleSqure}>
<Text>{DATA[2].title}</Text>
</View>
</View>
</View>
)
renderItems = ({ item }) => (
<View style={styles.smallSquare}>
<Text>{item.title}</Text>
</View>
);
render() {
return (
<SafeAreaView style={{flex: 1, marginTop: 20}}>
{/* Display rest of item in a FlatList */}
<FlatList
data={DATA.slice(2)}
renderItem={this.renderItems}
ListHeaderComponent={this.renderHeader}
keyExtractor={item => item.id}
/>
</SafeAreaView>
);
}
}
const styles = StyleSheet.create({
bigSquare: {
height: 220,
width: ScreenWidth - 20,
margin: 10,
backgroundColor: "yellow",
justifyContent: "center",
alignItems: "center"
},
middleSqure: {
height: (ScreenWidth - 40) / 2,
width: (ScreenWidth - 40) / 2,
margin: 10,
backgroundColor: "red",
justifyContent: "center",
alignItems: "center"
},
smallSquare: {
height: 100,
width: 100,
margin: 10,
backgroundColor: "green",
justifyContent: "center",
alignItems: "center"
},
});
Hope this helps you. Feel free for doubts.