I am new to React Native Animation & I was following a tutorial & I did exactly what he was teaching but I failed to get the desired result. I am trying to develop a multipage onboarding screen with a svg animation. The problem is that it works fine but the moment I reach the last item in my flatlist, it doesn't update the index and hence the animation is not completed for the last array item.
Below is the relevant code:
RegisterationScreen:
const data = [
<EmailComponent />,
<PasswordComponent />,
<DPComponent />,
<AgeComponent />,
];
const RegisterScreen = () => {
const scrollX = useRef(new Animated.Value(0)).current;
const [currentIndex, setCurrentIndex] = useState(0);
const slidesRef = useRef(null);
const viewableItemsChanged = useCallback(({viewableItems}) => {
console.log(viewableItems[0]);
setCurrentIndex(viewableItems[0].index);
}, []);
const scrollTo = () => {
if (currentIndex < data.length - 1) {
slidesRef.current.scrollToIndex({index: currentIndex + 1});
} else {
console.log('Last Item!');
}
};
const goBack = () => {
if (currentIndex === 0) {
return;
}
slidesRef.current.scrollToIndex({index: currentIndex - 1});
};
// console.log(currentIndex);
return (
<RegisterationStateProvider>
<View style={styles.container}>
<StatusBar barStyle="dark-content" backgroundColor="#ffffff" />
<SafeAreaView style={styles.back}>
<TouchableOpacity onPress={goBack} activeOpacity={0.6}>
<AntDesign name="arrowleft" size={32} color="black" />
</TouchableOpacity>
</SafeAreaView>
<View style={{flex: 3}}>
<FlatList
ref={slidesRef}
scrollEnabled
data={data}
keyExtractor={(_, index) => 'key' + index}
renderItem={({item}) => item}
horizontal
showsHorizontalScrollIndicator={false}
pagingEnabled
bounces={false}
onScroll={
Animated.event(
[{nativeEvent: {contentOffset: {x: scrollX}}}],
{useNativeDriver: false},
)}
scrollEventThrottle={32}
onViewableItemsChanged={viewableItemsChanged}
/>
</View>
<Dot data={data} scrollX={scrollX} />
<NextButton
scrollTo={scrollTo}
percentage={(currentIndex + 1) * (100 / data.length)}
/>
</View>
</RegisterationStateProvider>
);
};
NextButton:
const NextButton = ({percentage, scrollTo}) => {
const size = 100;
const strokeWidth = 4;
const center = size / 2;
const radius = size / 2 - strokeWidth / 2;
const circumference = 2 * Math.PI * radius;
// console.log(percentage);
const progressAnimation = useRef(new Animated.Value(0)).current;
const progressRef = useRef(null);
const animation = toValue => {
return Animated.timing(progressAnimation, {
toValue,
duration: 250,
useNativeDriver: false,
}).start();
};
useEffect(() => {
animation(percentage);
}, [percentage]);
useEffect(() => {
progressAnimation.addListener(
value => {
const strokeDashoffset =
circumference - (circumference * value.value) / 100;
if (progressRef?.current) {
progressRef.current.setNativeProps({
strokeDashoffset,
});
}
},
[percentage],
);
return () => {
progressAnimation.removeAllListeners();
};
}, []);
return (
<View style={styles.container}>
<Svg width={size} height={size}>
<G rotation="-90" origin={center}>
<Circle
stroke="#E6E7E8"
cx={center}
cy={center}
r={radius}
strokeWidth={strokeWidth}
/>
<Circle
ref={progressRef}
stroke="#FF5864"
cx={center}
cy={center}
r={radius}
strokeWidth={strokeWidth}
strokeDasharray={circumference}
strokeDashoffset={circumference}
/>
</G>
</Svg>
<TouchableOpacity
disabled={false}
onPress={scrollTo}
style={styles.button}
>
<AntDesign name="arrowright" size={35} color="#fff" />
</TouchableOpacity>
</View>
);
};
I cannot find the error/bug in the code, everything looks fine because when I check if (currentIndex < data.length - 1) in scrollTo function, it should render for index=3 because 3<4 is true obviously but unfortunately it doesn't work and no error in console either.
Below is the output of console for console.log(currentIndex); & console.log(percentage);
LOG {"index": 0, "isViewable": true, "item": <EmailComponent />, "key": "key0"}
LOG {"index": 0, "isViewable": true, "item": <EmailComponent />, "key": "key0"}
LOG {"index": 1, "isViewable": true, "item": <PasswordComponent />, "key": "key1"}
LOG {"index": 1, "isViewable": true, "item": <PasswordComponent />, "key": "key1"}
LOG {"index": 2, "isViewable": true, "item": <DPComponent />, "key": "key2"}
LOG 0
LOG 25
LOG 1
LOG 50
LOG 2
LOG 75
As you can see that even though I am at the last item, the animation is still at (75%) 270° & not full complete i.e. (100%) 360° & {index:3} is not logging in console even though I'm at last item of data.
onViewableItemsChanged is not being triggered on the last item in the FlatList. Lower the viewAreaCoveragePercentThreshold in the viewabilityConfig property https://reactnative.dev/docs/flatlist#viewabilityconfig
Related
So, I was getting familiar with reanimated2, for that I'm building a simple todo app, so whenever I check a single todo (a simple toggle state, completed or !completed), I change the text style to line-through so that it appears to be completed, now in order to add some animations, I was trying to combine reanimated to it, but was unsuccessful, so can anyone guide me how to achieve what I want?
const TodoItem = ({ todo }) => {
const { toggleTodo } = useTodoStore((state) => state);
const progress = useSharedValue('none');
useEffect(() => {
progress.value = withTiming(todo.completed ? 'line-through' : 'none', {
duration: 1000,
easing: Easing.linear,
})
}, [todo.completed]);
const textReanimatedStyle = useAnimatedStyle(() => ({
textDecorationLine: progress.value, // ts error: Type 'string' is not assignable to type 'number | "none" | "underline" | "line-through" | "underline line-through" | undefined'.
}), []);
const toggleTodoHandler = () => {
toggleTodo(todo.id);
};
return (
<Animated.View
style={styles.container}
entering={FadeIn}
exiting={FadeOut}
layout={Layout.delay(300)}>
<View style={styles.innerContainer}>
<TouchableOpacity
style={styles.checkboxContainer}
activeOpacity={0.6}
onPress={toggleTodoHandler}>
{todo.completed ? (
<Ionicon name="checkmark" size={20} color={'gray'} />
) : null}
</TouchableOpacity>
<Animated.Text
numberOfLines={2}
style={[
styles.text,
textReanimatedStyle,
]}>
{todo.text}
</Animated.Text>
</View>
</Animated.View>
);
};
Now whenever I toggle complete or not, it throws error saying:
Invalid RCTTextDecorationLineType 'noneNaN'. should be one of: (
"line-through",
none,
underline,
"underline line-through"
)
Not sure this is what you wanted to achive. But what I did was can be handle with a state change also without reanimated.
There is an alternate way also. check this answer https://stackoverflow.com/a/65262789/8743951
const textState = useSharedValue('none');
const [todo, setTodo] = useState<{ text: string; completed: boolean }>({ text: 'complete this', completed: false });
const textReanimatedStyle = useAnimatedStyle(() => {
if (textState.value === 'checked') {
return {
textDecorationLine: 'line-through',
};
}
return {
textDecorationLine: 'none',
};
}, []);
const toggleTodoHandler = (): void => {
textState.value = todo.completed ? 'none' : 'checked';
setTodo({ ...todo, completed: !todo.completed });
};
return (
<Animated.View style={styles.container} entering={FadeIn} exiting={FadeOut} layout={Layout.delay(300)}>
<View style={styles.innerContainer}>
<TouchableOpacity style={styles.checkboxContainer} activeOpacity={0.6} onPress={toggleTodoHandler}>
{todo.completed ? <Ionicon name="checkmark" size={20} color="gray" /> : null}
</TouchableOpacity>
<Animated.Text numberOfLines={2} style={[styles.text, textReanimatedStyle]}>
{todo.text}
</Animated.Text>
</View>
</Animated.View>
);
I have flatlist horizontal like below
const DATA = [
{
id: 'bd7acbea-c1b1-46c2-aed5-3ad53abb28ba',
title: 'First Item',
},
{
id: '3ac68afc-c605-48d3-a4f8-fbd91aa97f63',
title: 'Second Item',
},
{
id: '58694a0f-3da1-471f-bd96-145571e29d72',
title: 'Third Item',
},
{
id: 'bd7acbea-c1b1-46c2-aed5-3ad353abb28ba',
title: 'Fourth Item',
},
{
id: '3ac68afc-c605-48d3-a4f8-fbd291aa97f63',
title: 'Fifth Item',
},
{
id: '58694a0f-3da1-471f-bd961-145571e29d72',
title: 'Sixth Item',
},
];
const Item = ({ title }) => (
<View style={styles.item}>
<Text style={styles.title}>{title}</Text>
</View>
);
const App = () => {
const renderItem = ({ item }) => (
<Item title={item.title} />
);
return (
<SafeAreaView style={styles.container}>
<FlatList
horizontal
data={DATA}
renderItem={renderItem}
keyExtractor={item => item.id}
/>
</SafeAreaView>
);
}
Whenever Item entered the viewport , I want to add animation to that element.I can get X and Y position of scroll with onScroll , now how do i get the positions of items to check if its in view port or if it went away from viewport...
Thank you.
Sorry for the late response. My pc has been super weird lately so when I encounter errors I have to second guess myself, and when nothing appears wrong, I second guess my pc (this time it was entirely me).
Here's my answer. I implemented the basic fade in/out [animation example][1] into the Item component. Whether it fades out or in is decided by the prop isViewable
// Item.js
const Item = (props) => {
const {
item:{title, isViewable}
} = props
/*
I copied and pasted the basic animation example from the react-native dev page
*/
const fadeAnim = useRef(new Animated.Value(1)).current;
const fadeIn = () => {
Animated.timing(fadeAnim, {
toValue: 1,
duration: 1000,
useNativeDriver:false
}).start();
};
const fadeOut = () => {
Animated.timing(fadeAnim, {
toValue: 0,
duration: 1500,
useNativeDriver:false
}).start();
};
/* end of animation example*/
// fade in/out base on if isViewable
if(isViewable || isViewable == 0)
fadeIn()
else
fadeOut()
const animation = {opacity:fadeAnim}
return (
//add animation to Animated.View
<Animated.View style={[style.itemContainer,animation]}>
<View style={style.item}>
<Text style={style.title}>{title}</Text>
</View>
</Animated.View>
);
}
Create a FlatListWrapper (to avoid the onViewableItemChange on fly error). By doing this, as long as you don't make changes to FlatListWrapper, you wont get the on the fly error
// FlatListWrapper.js
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
},
// main FlatList 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> {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,
}
})
By wrapping your FlatList in a separate file, you wont encounter the "onViewableItemsChange on the fly" error as long as you dont modify FlatListWrapper.js
[1]: https://reactnative.dev/docs/animated
Use onViewableItemsChanged this is called when the items in the flatlist changes.
const handleViewableItemsChanged = (viewableItems, changed) => {}
<Flatlist
...
onViewableItemsChanged={handleViewableItemsChanged}
I'm trying to migrate to react navigation 5, but I get an error where I use the DrawerItemList in place of the DrawerNavigatorItems.
My code used to look like this:
const MainNavigator = createDrawerNavigator(
// e.g.
Winners: {
screen: WinnersNavigator,
navigationOptions: {
drawerLabel: (
<BoldText style={NavigationStyles.arhiki}>Νικητές</BoldText>
),
drawerIcon: tabInfo => {
return (
<View style={NavigationStyles.winners}>
<FontAwesome
name="users"
size={iconMultiplier / 10}
color={tabInfo.tintColor}
/>
</View>
);
}
}
},
// ... more screens
contentComponent: props => {
const dispatch = useDispatch();
// This is for showing the Admin screen link, if user is an admin.
const userIdExists = useSelector(state => state.auth.userId);
return (
<View style={{ flex: 1 }}>
<SafeAreaView forceInset={{ top: "always", horizontal: "never" }}>
{/* These are the default drawer items */}
// THE PROBLEM IS HERE!!!
<DrawerNavigatorItems {...props} />
{/* Plus our custom buttons */}
{userIdExists && (
<View style={NavigationStyles.summary}>
<Ionicons.Button
name="ios-create"
backgroundColor={Colours.moccasin_light}
size={iconMultiplier / 10}
// style={{marginLeft: -20 }}
color="#888"
onPress={() => props.navigation.navigate("CreateWelcome")}
></Ionicons.Button>
<Text
onPress={() => props.navigation.navigate("CreateWelcome")}
style={[
NavigationStyles.exodos,
Platform.OS == "android" ? { marginLeft: -6 } : null
]}
>
Δημιουργία
</Text>
</View>
)}
...
Now it looks like this:
const CustomDrawerContent = props => {
const dispatch = useDispatch();
// This is for showing the Admin screen link, if user is an admin.
const userIdExists = useSelector(state => state.auth.userId);
return (
<View style={{ flex: 1 }}>
<SafeAreaView forceInset={{ top: "always", horizontal: "never" }}>
<DrawerContentScrollView {...props}>
{/* These are the default drawer items */}
<DrawerItemList {...props} />
{/* Plus our custom buttons */}
<Drawer.Section>
{userIdExists && (
<View style={NavigationStyles.summary}>
<DrawerItem
label={() => (
<Text
// onPress={() => props.navigation.navigate("CreateWelcome")}
style={[
NavigationStyles.exodos,
Platform.OS == "android" ? { marginLeft: -6 } : null
]}
>
Δημιουργία
</Text>
)}
icon={() => (
<Ionicons.Button
name="ios-create"
backgroundColor={Colours.moccasin_light}
size={iconMultiplier / 10}
// style={{marginLeft: -20 }}
color="#888"
// onPress={() => props.navigation.navigate("CreateWelcome")}
></Ionicons.Button>
)}
onPress={() => props.navigation.navigate("CreateWelcome")}
/>
</View>
)}
// ... more items
</Drawer.Section>
</DrawerContentScrollView>
</SafeAreaView>
</View>
);
};
const MainDrawerNavigator = createDrawerNavigator();
export const MainNavigator = () => {
return (
<MainDrawerNavigator.Navigator
drawerStyle={{
width: width < 900 ? 0.6 * width : 0.4 * width,
backgroundColor: Colours.moccasin_light,
overlayColor: Colours.maroonRGBA
}}
drawerContentOptions={{ activeTintColor: Colours.gr_brown }}
drawerContent={props => <CustomDrawerContent {...props} />}
>
<MainDrawerNavigator.Screen
name="GameNavigator"
component={GameNavigator}
options={{
drawerLabel: (
<BoldText style={NavigationStyles.arhiki}>Αρχική</BoldText>
),
drawerIcon: ({ color }) => (
<View style={NavigationStyles.shield}>
<MaterialCommunityIcons
name="shield-cross"
size={iconMultiplier / 8}
color={color}
/>
</View>
)
}}
/>
// ... more screens
</MainDrawerNavigator.Navigator>
);
};
The error I get is:
TypeError: label is not a function. (In 'label({
color: color,
focused: focused
})', 'label' is an instance of Object)
and it's generated at the <DrawerItemList {...props} />
In the docs I read:
import {
DrawerContentScrollView,
DrawerItemList,
} from '#react-navigation/drawer';
function CustomDrawerContent(props) {
return (
<DrawerContentScrollView {...props}>
<DrawerItemList {...props} />
</DrawerContentScrollView>
);
}
// To add additional items in the drawer, you can use the DrawerItem component:
function CustomDrawerContent(props) {
return (
<DrawerContentScrollView {...props}>
<DrawerItemList {...props} />
<DrawerItem
label="Help"
onPress={() => Linking.openURL('https://mywebsite.com/help')}
/>
</DrawerContentScrollView>
);
}
The first screen that I render is a Navigator, the GameNavigator.
Could that be a problem?
I read in a issue that:
"It's not possible to add navigators inside drawer content. You can achieve custom layouts using a custom router and custom navigator:" source,but I've learned from a course that it is possible! Or does this guy mean something else with drawer content?
The GameNavigator is:
const GameStackNavigator = createStackNavigator();
const GameNavigator = () => {
return (
<GameStackNavigator.Navigator
initialRouteName="Welcome"
screenOptions={defaultNavOptions}
>
<GameStackNavigator.Screen
name="Welcome"
component={WelcomeScreen}
options={WelcomeScreenOptions}
/>
...
</GameStackNavigator.Navigator>
);
};
And the WelcomeScreen is:
const WelcomeScreen = props => {
const dispatch = useDispatch();
const [isLoading, setIsLoading] = useState(false);
const [modalVisible, setModalVisible] = useState(false);
const [gridTileAnimValue] = useState(new Animated.Value(0));
const [isConnected, setIsConnected] = useState(false);
// For adding the points that are saved in memory, when connection is established.
useEffect(() => {
const unsub = NetInfo.addEventListener(state => {
setIsConnected(state.isConnected);
});
return () => unsub();
}, []);
const getPoints = async () => {
let points = await AsyncStorage.getItem("savedPoints");
if (!!points) {
const getEmail = async () => {
const userData = await AsyncStorage.getItem("userData");
if (userData) {
const transformedData = JSON.parse(userData);
const { userEmail } = transformedData;
return userEmail;
}
};
const email = await getEmail();
// Give it some time to get the token and userId,
// because saveData needs them.
setTimeout(
async () => await dispatch(dataActions.saveData(email, +points)),
3000
);
await AsyncStorage.removeItem("savedPoints");
}
};
if (isConnected) getPoints();
useEffect(() => {
getFilters = async () => {
await dispatch(filtersActions.fetchDifficultyLevelFilters());
await dispatch(filtersActions.fetchCategoriesFilters());
};
}, [dispatch]);
useEffect(() => {
props.navigation.setOptions({
headerRight: () => (
<HeaderButtons HeaderButtonComponent={CustomHeaderButton}>
<Item
title="game-info"
iconName={
Platform.OS === "android"
? "md-information-circle-outline"
: "ios-information-circle-outline"
}
// style={{width: width / 8, height: height / 10, paddingTop: height / 35}}
onPress={() => setModalVisible(!modalVisible)}
/>
</HeaderButtons>
)
});
}, [modalVisible, setModalVisible]);
useEffect(() => {
const checkIfInfoNeeded = async () => {
return await AsyncStorage.getItem("NO_infoNeeded");
};
checkIfInfoNeeded().then(NO_infoNeeded => {
if (NO_infoNeeded === "NO") {
return;
} else {
setModalVisible(true);
}
});
}, []);
const animateGridTile = () => {
Animated.timing(gridTileAnimValue, {
toValue: 1,
duration: 1200,
useNativeDriver: false
}).start();
};
useEffect(() => {
animateGridTile();
const unsubscribe = props.navigation.addListener("focus", animateGridTile);
return () => unsubscribe();
}, [animateGridTile]);
useEffect(() => {
const unsubscribe = props.navigation.addListener("willBlur", () =>
gridTileAnimValue.setValue(0)
);
return () => {
unsubscribe();
};
}, []);
const cardStyle = { opacity: gridTileAnimValue };
const renderGridItem = itemData => {
return (
<Animated.View style={cardStyle}>
<CategoryGridTile
color={itemData.item.color}
title={itemData.item.title}
id={itemData.item.id}
onSelect={() => {
if (itemData.item.id == 0) {
props.navigation.navigate({
routeName: "MixedChoicesScreen"
});
} else if (itemData.item.id == 1) {
props.navigation.navigate({
routeName: "MultiChoiceCategories",
params: {
gameType: itemData.item.title
}
});
} else if (itemData.item.id == 2) {
props.navigation.navigate({
routeName: "TrueFalseCategories",
params: {
gameType: itemData.item.title
}
});
}
}}
/>
</Animated.View>
);
};
if (isLoading) {
return (
<CustomLinearGradient>
<View style={styles.centered}>
<ActivityIndicator size="large" color={Colours.moccasin_light} />
</View>
</CustomLinearGradient>
);
}
return (
<CustomLinearGradient>
<View style={styles.flatListContainer}>
{modalVisible && (
<CustomModal
modalVisible={modalVisible}
setModalVisible={setModalVisible}
onRequestClose={() => {
Alert.alert(
"Επιλογές",
"Παρακαλώ επιλέξτε μία από τις δύο επιλογές της καρτούλας: Ναι ή Όχι.",
[{ text: "Εντάξει", style: "default" }]
);
// Alert.alert("Παρακαλώ επιλέξτε μία από τις δύο επιλογές της καρτούλας: Πληροφορίες ή Δεν χρειάζεται.");
}}
textOne="Θέλετε να διαβάσετε τις οδηγίες χρήσεως και τις πληροφορίες σχετικά με τις
δοκιμαστικές εκδόσεις της εφαρμογής."
buttonOneTitle="Ναι"
buttonTwoTitle="Όχι"
onPressOne={async () => {
AsyncStorage.setItem("NO_infoNeeded", "NO");
setModalVisible(false);
props.navigation.navigate("GameInfo");
}}
onPressTwo={async () => {
AsyncStorage.setItem("NO_infoNeeded", "NO");
setModalVisible(false);
}}
/>
)}
<FlatList
// numColumns={2}
keyExtractor={(item, index) => item.id}
data={GAME_TYPES}
renderItem={renderGridItem}
/>
</View>
</CustomLinearGradient>
);
};
export const WelcomeScreenOptions = ({ route, navigation }) => {
return {
title: "ΕΝ ΤΟΥΤΩ ΝΙΚΑ",
headerLeft: () => (
<HeaderButtons HeaderButtonComponent={CustomHeaderButton}>
<Item
onPress={() => navigation.toggleDrawer()}
title="Menu"
iconSize={73}
iconName={Platform.OS === "android" ? "md-menu" : "ios-menu"}
// style={{
// width: width / 8,
// height: height / 10,
// paddingTop: height / 35
// }}
/>
</HeaderButtons>
)
};
};
Any suggestion would be appreciated.
Thanks
For drawerLabel you have directly set an object which is wrong
drawerLabel: (
<BoldText style={NavigationStyles.arhiki}>Αρχική</BoldText>
),
This should be either a string or a function that returns a component, so you should change it like below
drawerLabel: {()=>(
<BoldText style={NavigationStyles.arhiki}>Αρχική</BoldText>
)},
You can refer the docs
https://reactnavigation.org/docs/drawer-navigator/#drawerlabel
I've been trying to develop a react native app and I'm stuck here . I want to decompose onboarding component. Here is what I've done . Is it oke or what else can I do more readeble code ? I've divided Slider and SubSlider component but I think it isn't enough .Can I use component instead of functions Any suggestion will help me . What are the best practices in this case ? Thanks
const renderScrollView = (scroll, width: number, x: Animated.Value) => {
return (
<Animated.ScrollView showsHorizontalScrollIndicator={false} ref={scroll} pagingEnabled
onScroll={Animated.event([{ nativeEvent: { contentOffset: { x } } }], {
useNativeDriver: true,
})}
horizontal
>
{slides.map((data, index) => {
return (
<Slider key={index}>
<SliderImage src={data.src} />
<Box paddingHorizontal="l">
<SliderTitle>{data.label}</SliderTitle>
<SliderText>{data.description}</SliderText>
</Box>
</Slider>
);
})}
</Animated.ScrollView>
);
};
const renderSubSlider = (width: number,x: Animated.Value) => {
return (
<SubSlider>
<SubSliderItem>
{slides.map((_, index) => {
return (
<Dot
key={index + 1}
currentIndex={Animated.divide(x, width)}
{...{ index }}
/>
);
})}
</SubSliderItem>
</SubSlider>
);
};
const Onboarding = React.memo(({ onDone }: OnboardingProps) => {
const { width } = useDimensions().window;
const [completed, setCompleted] = useState<boolean>(false);
const scroll = useRef(null);
const x = React.useRef<Animated.Value>(new Animated.Value(0)).current;
React.useEffect(() => {
x.addListener(({ value }) => {
if (Math.ceil(value / width) === slides.length - 1) {
setCompleted(true);
} else {
setCompleted(false);
}
});
return () => x.removeAllListeners();
}, []);
return (
<Box flex={1} backgroundColor="mainBackground">
<Box flex={3}>{renderScrollView(scroll, width, x)}</Box>
{renderSubSlider(width, x)}
</Box>
);
});
I am Tring to make a screen in react native in which height of upper view is resizable via dragging from the bottom.enter image description here
Size of (1) increases on dragging (2)
So far I tried two approaches
1. PanResponder
const Chat = (props) => {
const orders = [
//Some Array
]
const Height = Dimensions.get('window').height
const boxHeight = useRef(new Animated.Value(Height / 3)).current
const panResponder = useRef(PanResponder.create({
onMoveShouldSetPanResponder: (evt, gestureState) => true,
onPanResponderMove: (evt, gestureState) => {
const nextValue = boxHeight+gestureState.dy
let newVal = nextValue >= maxHeight?maxHeight:nextValue
newVal = newVal <= minHeight?minHeight:newVal
boxHeight.setValue(newVal)
},
}
)).current;
return (
<Container>
<Animated.View style={[{ borderBottomColor: '#ccc', borderBottomWidth: 3 }, { height:boxHeight }]}>
<FlatList
data={orders}
renderItem={({ item, index }) => <List item={item} index={index} />}
/>
<View {...panResponder.panHandlers} style={{backgroundColor:'#000',height:20}}></View >
</Animated.View>
<ChatScreen />
</Container>
)
}
Pan-gesture- handler
const Chat = (props) => {
const orders = [
//some array
]
const Height = Dimensions.get('window').height
const boxHeight = useRef(new Animated.Value(Height / 3)).current
const minHeight = 10
const maxHeight = Height - 100
const onPanGestureEvent = (event) => {
boxHeight.setValue(
Animated.diffClamp(
Animated.add(
orderBoxHeight, Animated.multiply(
event.nativeEvent.translationY, Animated.diffClamp(
event.nativeEvent.velocityY, 0, 1
)
)
)
), minHeight, maxHeight
)
}
return (
<Container>
<Animated.View style={[{ borderBottomColor: '#ccc', borderBottomWidth: 3 }, { height: boxHeight }]}>
<FlatList
data={orders}
renderItem={({ item, index }) => <OrderList item={item} index={index} />}
/>
<PanGestureHandler
onGestureEvent={(event) => onPanGestureEvent(event)}
>
<Animated.View style={{ backgroundColor: '#000', height: 20 }}></Animated.View >
</PanGestureHandler>
</Animated.View>
<ChatScreen />
</Container>
)
}
But none Worked... I have also tried few other variations of both but none of them worked as desired either it slides too fast or does not move at all