I have a pan responder overlaying the whole screen and a scrollview underneath it.
I would like to call the scrollTo() method and using Pan responders X/Y travel positions to scroll.
I am using this Pan Responder example code to create a Y value that can increment or decrement as you swipe up or down on the screen.
https://reactnative.dev/docs/panresponder
I don't know how to get myScroll view to listen to this Y value change.
Any help is appreciated. thanks
// You should be able to run this by copying & pasting
import { createRef, useRef } from "react";
import { Animated, PanResponder, StyleSheet, Text, View } from "react-native";
// some refs...
const scrollRef = createRef();
const buttonRef = createRef();
// panResponder Hook
const useScreenPanResponder = () => {
const pan = useRef(new Animated.ValueXY()).current;
const panResponder = useRef(
PanResponder.create({
onMoveShouldSetPanResponder: () => true,
onPanResponderGrant: () => {
pan.setOffset({
y: pan.y._value,
});
},
onPanResponderMove: Animated.event([null, { dy: pan.y }]),
onPanResponderRelease: () => {
pan.flattenOffset();
},
})
).current;
return { pan, panResponder };
};
// custom button
const ButtonWrapper = ({ children }) => {
return (
<View
onTouchStart={() => {
buttonRef.current = Date.now();
}}
onTouchEnd={() => {
if (Date.now() - buttonRef.current < 500) {
console.log("Actual Press");
} else {
console.log("Not a Press");
}
}}
>
{children}
</View>
);
};
// long list of text
const Content = () => {
const data = Array.from(Array(100));
return (
<View style={{ backgroundColor: "orange", flexDirection: "column" }}>
{data.map((i) => (
<Text style={{ fontSize: 17, color: "black" }}>Some Content</Text>
))}
</View>
);
};
export default function App() {
const { pan, panResponder } = useScreenPanResponder();
console.log("pan!", pan);
console.log("scrollRef", scrollRef);
const scrollToPosition = () => {
// scrollRef.current.scrollTo({ y: pan.y });
};
return (
<View style={styles.container}>
{/* Container taking up the whole screen, lets us swipe to change pan responder y pos */}
<Animated.View
style={{
position: "absolute",
width: "100%",
height: "100%",
backgroundColor: "rgba(0,255,0,.5)",
}}
{...panResponder.panHandlers}
/>
{/* A animated container that reacts to pan Responder Y pos */}
<Animated.View
style={{
transform: [{ translateY: pan.y }],
}}
>
<ButtonWrapper>
<View style={styles.box} {...panResponder.panHandlers} />
</ButtonWrapper>
</Animated.View>
{/* Here we need the scrollView to be controlled by the Pan Y pos */}
<Animated.ScrollView ref={scrollRef}>
<Content />
</Animated.ScrollView>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: "center",
justifyContent: "center",
},
titleText: {
fontSize: 14,
lineHeight: 24,
fontWeight: "bold",
},
box: {
height: 150,
width: 150,
backgroundColor: "blue",
borderRadius: 5,
},
});
Related
after recording a 7-second video, I save it and then store it on my server.
however, when playing the video back it has no video, only audio.
video link: https://storage.googleapis.com/clipdrop-prod/tfZJe6V5ImfAYD_k9GMnKK6JD.mp4?avoidCache=1
here is the media info of said video
below is my code
once the component loads, it calls startRecording function. which starts the recording and waits for the recording to be "stopped" (aka saved), which it then loads the uri for the local file it has saved to on the device from videoData.
Code
import react, { useRef, useState, useEffect } from 'react';
import { Text, View, Image } from 'react-native';
import PrimaryButton from '../../../components/buttons/PrimaryButton';
import { windowWidth } from '../../../helpers/screenHelper';
import { Camera, CameraRecordingOptions, CameraType, requestCameraPermissionsAsync, requestMicrophonePermissionsAsync, VideoCodec } from 'expo-camera';
import CountDown from './CountDown';
import { CountDownStyles } from './CountDown.styles';
export default function RecordAnswerView(props:any){
const { question, onComplete, onRestart } = props;
const [showCountDown, setShowCountDown] = useState(true);
const [countDownText, setCountDownText] = useState(3);
const cameraRef = useRef<any>(null);
const [showRedFlash, setShowRedFlash] = useState(false);
const [permission, requestPermission] = Camera.useCameraPermissions();
const [videoData, setVideoData] = useState(true);
let answerCounterState:any = null;
const saveRecording = () => { cameraRef.current.stopRecording();}
const restartRecording = () => {onRestart();}
const startRecording = async () =>{
let videoData = await cameraRef.current.recordAsync({
codec: VideoCodec.H264,
quality: "720p",
videoBitrate: 5*1000*1000
});
console.log(videoData);
onComplete((videoData as any).uri);
};
const startCountDown = () => {
setCountDownText(3);
setShowCountDown(true);
setTimeout(()=>{
setCountDownText(3);
setTimeout(()=>{
setCountDownText(2);
setTimeout(()=>{
setCountDownText(1);
setTimeout(async ()=>{
setShowCountDown(false);
setCountDownText(3);
await startRecording();
}, 1000);
}, 1000);
}, 1000);
}, 1000);
}
useEffect(()=>{
async function init(){
let audio = await requestMicrophonePermissionsAsync();
let camera = await requestCameraPermissionsAsync();
setShowRedFlash(false);
startCountDown();
}
init();
}, []);
return <>
<View style={{marginHorizontal:12}}>
<Text style={{textAlign:'center', fontSize:22, marginVertical:12}}>Answer Recorder</Text>
<Camera ref={cameraRef} type={CameraType.front} style={{width: windowWidth - 20, height: (windowWidth - 20) * 1.02}}>
<View style={{width:"100%", height:"100%", alignItems:'center', justifyContent:'center'}}>
<Text style={{display: showCountDown ? 'flex' : 'none', fontSize:32, backgroundColor:'#4A86E8', color:"#FFFFFF" ,paddingVertical: 2, paddingHorizontal: 14, borderRadius: 16}}>{ countDownText }</Text>
</View>
</Camera>
<View style={{alignItems:'center', justifyContent:'center', flexDirection:'row', alignContent:'center', marginTop: 5}}>
<Text style={{borderTopLeftRadius: 8, borderBottomLeftRadius: 8, textAlign:'center', color:'#E9E9E9',backgroundColor:'#333333', width: windowWidth / 2, padding: 8, fontSize:13, marginLeft: 4, paddingVertical:9}}>Recommended Answer Time</Text>
{ (!showCountDown) ? <CountDown totalTime={question.recommend_answer_duration} onZero={()=>{
saveRecording();
setShowRedFlash(false);
setShowCountDown(false);
}}
onFlip={()=>{
setShowRedFlash(!showRedFlash);
}}/> : <Text style={ CountDownStyles.textStyle }>{question.recommend_answer_duration} Seconds Remaining</Text> }
</View>
{ !showCountDown && <><PrimaryButton onPress={()=>{saveRecording();}} text={"Save"} containerStyle={{ marginTop: 20, marginBottom: 8, width: windowWidth - 20}} fontSize={20}/>
<PrimaryButton onPress={()=>{restartRecording();}} text={"Restart Recording"} containerStyle={{marginTop: 5, marginBottom: 10, width: windowWidth - 20}} fontSize={10} height={32}/>
</>}
</View>
<View style={{display: showRedFlash ? "flex" : "none", position:'absolute', top:0, bottom: 0, left: 0, right: 0, backgroundColor: '#ff000036'}} pointerEvents={"none"}>
</View>
</>
}
Testing
Test on a physical android device with the last Android OS
I'm having a problem with react-native-gesture handler animation in a FlatList.
when i try to scroll on the flatList the behavior of PanGesture not trigger scroll event.
Reanimated Version - ~2.5
Flatlist
<FlatList
data={test}
ref={flatListRef}
ItemSeparatorComponent={Divider}
style={{
marginTop: 18,
}}
scrollEnabled
renderItem={({ item }) => {
return (
<ActionItem
simultaneousHandlers={flatListRef}
actions={[
{
type: 'UPDATE',
handlePress: () => {
console.log(item.id);
},
},
{
type: 'UPDATE',
handlePress: () => {
console.log(item.id);
},
},
]}
>
<View style={styles.container}>
<Text style={{}}>{item.name}</Text>
</View>
</ActionItem>
);
}}
/>
ActionItem
import { useTheme } from '#hooks';
import React from 'react';
import {
StyleSheet,
View,
Text,
Dimensions,
TouchableOpacity,
} from 'react-native';
import {
PanGestureHandler,
PanGestureHandlerGestureEvent,
} from 'react-native-gesture-handler';
import Animated, {
useAnimatedGestureHandler,
useAnimatedStyle,
useSharedValue,
withTiming,
withSpring,
} from 'react-native-reanimated';
import { IActionItem } from './types';
import Edit from '#assets/svgs/icons/edit.svg';
const threshold = (-Dimensions.get('window').width - 18) * 0.05;
function ActionItem<T>({
children,
actions,
simultaneousHandlers,
}: IActionItem<T>) {
const [actionsWidth, setActionsWidth] = React.useState<number>(0);
const translateX = useSharedValue(0);
const theme = useTheme();
const gestures = useAnimatedGestureHandler<PanGestureHandlerGestureEvent>({
onStart: () => {
translateX.value = withTiming(0);
},
onActive: (e) => {
if (e.translationX < 0) {
translateX.value = withSpring(e.translationX);
}
},
onEnd: (e) => {
const shouldHideActions = e.translationX > threshold;
if (shouldHideActions) {
translateX.value = withTiming(0);
} else {
translateX.value = withSpring(-actionsWidth);
}
},
});
const animatedStyles = useAnimatedStyle(() => {
return {
transform: [
{
translateX: translateX.value,
},
],
};
});
return (
<Animated.View style={styles.container}>
<Animated.View
style={styles.actionsContainer}
onLayout={(e) => {
setActionsWidth(e.nativeEvent.layout.width);
}}
>
{actions?.map((action, index) => {
return (
<TouchableOpacity
key={index}
onPress={action.handlePress}
style={[
{
marginRight: index !== actions.length - 1 ? 20 : 0,
marginLeft: index === 0 ? 20 : 0,
},
]}
>
{action.type === 'UPDATE' && <Edit />}
</TouchableOpacity>
);
})}
</Animated.View>
<PanGestureHandler
simultaneousHandlers={simultaneousHandlers}
onGestureEvent={gestures}
>
<Animated.View style={[animatedStyles, styles.action]}>
{children}
</Animated.View>
</PanGestureHandler>
</Animated.View>
);
}
const styles = StyleSheet.create({
container: {
width: '100%',
elevation: 10,
},
actionsContainer: {
position: 'absolute',
flexDirection: 'row',
alignItems: 'center',
height: '100%',
flex: 1,
right: 0,
},
action: {
backgroundColor: 'red',
},
});
export default ActionItem;
i'm using the flat list from React-Native-Gesture-handler and i'm passing the reference of flatlist to scroll work but doesn't work, anyone have some ideia why ?
I am trying to make a Covid19 React Native Expo app. It contains a search filter from which user will select a country then the selected country results will be shown to the user. I keep getting this error on my Android device "Unexpected Identifier You" while on web pack the countries load but they don't filter correctly.
Working Snack Link: https://snack.expo.io/#moeez71/ac5758
Here is my code:
import React, { useState, useEffect } from "react";
import {
ActivityIndicator,
Alert,
FlatList,
Text,
StyleSheet,
View,
TextInput,
} from "react-native";
export default function ABCDEE() {
const [arrayholder, setArrayholder] = useState([]);
const [text, setText] = useState("");
const [data, setData] = useState([]);
const [loading, setLoading] = useState(true);
const fetchAPI = () => {
return fetch("https://api.covid19api.com/countries")
.then((response) => response.json())
.then((responseJson) => {
setData(responseJson);
setLoading(false);
setArrayholder(responseJson);
})
.catch((error) => {
console.error(error);
});
};
useEffect(() => {
fetchAPI();
});
const searchData = (text) => {
const newData = arrayholder.filter((item) => {
const itemData = item.Country.toUpperCase();
const textData = text.toUpperCase();
return itemData.indexOf(textData) > -1;
});
setData(newData);
setText(text);
};
const itemSeparator = () => {
return (
<View
style={{
height: 0.5,
width: "100%",
backgroundColor: "#000",
}}
/>
);
};
return (
<View>
{loading === false ? (
<View style={styles.MainContainer}>
<TextInput
style={styles.textInput}
onChangeText={(text) => searchData(text)}
value={text}
underlineColorAndroid="transparent"
placeholder="Search Here"
/>
<FlatList
data={data}
keyExtractor={(item, index) => index.toString()}
ItemSeparatorComponent={itemSeparator}
renderItem={({ item }) => (
<Text style={styles.row}>{item.Country}</Text>
)}
style={{ marginTop: 10 }}
/>
</View>
) : (
<Text>loading</Text>
)}
</View>
);
}
const styles = StyleSheet.create({
MainContainer: {
paddingTop: 50,
justifyContent: "center",
flex: 1,
margin: 5,
},
row: {
fontSize: 18,
padding: 12,
},
textInput: {
textAlign: "center",
height: 42,
borderWidth: 1,
borderColor: "#009688",
borderRadius: 8,
backgroundColor: "#FFFF",
},
});
You had made two mistakes in the above code
useEffect second parameter should be a empty array for it act as componentDidMount()
useEffect(() => {
fetchAPI();
},[])
in FlatList renderItem need to destructure the item.
renderItem={( {item} ) => <Text style={styles.row}
>{item.Country}</Text>}
Working code
import React, {useState, useEffect} from "react"
import { ActivityIndicator, Alert, FlatList, Text, StyleSheet, View, TextInput } from 'react-native';
export default function ABCDEE(){
const [arrayholder,setArrayholder] =useState([])
const[text, setText] = useState('')
const[data, setData] = useState([])
const [loading , setLoading] = useState(true)
const fetchAPI = ()=> {
return fetch('https://api.covid19api.com/countries')
.then((response) => response.json())
.then((responseJson) => {
setData(responseJson)
setLoading(false)
setArrayholder(responseJson)
}
)
.catch((error) => {
console.error(error);
});
}
useEffect(() => {
fetchAPI();
},[])
const searchData= (text)=> {
const newData = arrayholder.filter(item => {
const itemData = item.Country.toUpperCase();
const textData = text.toUpperCase();
return itemData.indexOf(textData) > -1
});
setData(newData)
setText(text)
}
const itemSeparator = () => {
return (
<View
style={{
height: .5,
width: "100%",
backgroundColor: "#000",
}}
/>
);
}
return (
<View style={{flex:1}} >
{loading === false ?
<View style={styles.MainContainer}>
<TextInput
style={styles.textInput}
onChangeText={(text) => searchData(text)}
value={text}
underlineColorAndroid='transparent'
placeholder="Search Here" />
<FlatList
data={data}
keyExtractor={ (item, index) => index.toString() }
ItemSeparatorComponent={itemSeparator}
renderItem={( {item} ) => <Text style={styles.row}
>{item.Country}</Text>}
style={{ marginTop: 10 }} />
</View>
: <Text>loading</Text>}
</View>
);
}
const styles = StyleSheet.create({
MainContainer: {
paddingTop: 50,
justifyContent: 'center',
flex: 1,
margin: 5,
},
row: {
fontSize: 18,
padding: 12
},
textInput: {
textAlign: 'center',
height: 42,
borderWidth: 1,
borderColor: '#009688',
borderRadius: 8,
backgroundColor: "#FFFF"
}
});
There are multiple issues with your implementation. I will point out some mistake/ignorance. You can clean up you code accordingly.
Do not create 2 state to keep same data. ie. arrayholder and data.
Change text value on search, don't the data. based on that text filter
Hooks always define some variable to be watched.
Update: Seems there is an issue with flex in android view, i use fixed height it is visible.
Just a hack for android issue. minHeight
MainContainer: {
paddingTop: 50,
justifyContent: 'center',
flex: 1,
margin: 5,
minHeight: 800,
},
Working link: https://snack.expo.io/kTuT3uql_
Updated code:
import React, { useState, useEffect } from 'react';
import {
ActivityIndicator,
Alert,
FlatList,
Text,
StyleSheet,
View,
TextInput,
} from 'react-native';
export default function ABCDEE() {
const [text, setText] = useState('');
const [state, setState] = useState({ data: [], loading: false }); // only one data source
const { data, loading } = state;
const fetchAPI = () => {
//setState({data:[], loading: true});
return fetch('https://api.covid19api.com/countries')
.then(response => response.json())
.then(data => {
console.log(data);
setState({ data, loading: false }); // set only data
})
.catch(error => {
console.error(error);
});
};
useEffect(() => {
fetchAPI();
}, []); // use `[]` to avoid multiple side effect
const filterdData = text // based on text, filter data and use filtered data
? data.filter(item => {
const itemData = item.Country.toUpperCase();
const textData = text.toUpperCase();
return itemData.indexOf(textData) > -1;
})
: data; // on on text, u can return all data
console.log(data);
const itemSeparator = () => {
return (
<View
style={{
height: 0.5,
width: '100%',
backgroundColor: '#000',
}}
/>
);
};
return (
<View>
{loading === false ? (
<View style={styles.MainContainer}>
<TextInput
style={styles.textInput}
onChangeText={text => setText(text)}
value={text}
underlineColorAndroid="transparent"
placeholder="Search Here"
/>
<FlatList
data={filterdData}
keyExtractor={(item, index) => index.toString()}
ItemSeparatorComponent={itemSeparator}
renderItem={({ item }) => (
<Text style={styles.row}>{item.Country}</Text>
)}
style={{ marginTop: 10 }}
/>
</View>
) : (
<Text>loading</Text>
)}
</View>
);
}
const styles = StyleSheet.create({
MainContainer: {
paddingTop: 50,
justifyContent: 'center',
//flex: 1,
margin: 5,
height: 800,
},
row: {
fontSize: 18,
padding: 12,
},
textInput: {
textAlign: 'center',
height: 42,
borderWidth: 1,
borderColor: '#009688',
borderRadius: 8,
backgroundColor: '#333',
},
});
It seems like the error comes from the catch block when you fetch your data. The response comes with a 200 status, so it's not an issue with the endpoint itself. I console.logged the response and it seems fine. The problem is when you parse the response and try to use it in the second then() block, so the catch block fires up. I could not debug it right in the editor you use, but I would check what type of object I'm receiving from the API call.
This is not a direct answer to your question but I didn't want this to get lost in the comments as it is directly related with your efforts on this app.
App Store and Google Play Store no longer accept apps that have references to Covid-19 https://developer.apple.com/news/?id=03142020a
You can only publish apps about Covid-19 if you are reputable source like a government's health department.
Therefore, I urge you to reevaluate your efforts on this app.
I am using functional component with Hooks and I am getting user current location and assign it to MapView initialRegion but component doesn't rerender.
I tried to use reference ref with MapView component to call animateToRegion() but it's not working.
Is there is any thing that I miss?
here is my code sample:
import React, { useState, useEffect, useRef } from 'react';
import { View, Text, Image, Platform, ScrollView, ActivityIndicator } from 'react-native';
import { DateMenu, Dropdown, DropdownTextInput, InputField} from '../../../GlobalReusableComponents/TextFields';
import { Space } from '../../../GlobalReusableComponents/Separators';
import { Button } from 'react-native-elements';
import axios from 'axios';
import MapView, { Marker } from 'react-native-maps';
import useReverseGeocoding from '../../../context/useReverseGeocoding';
import useCurrentLocation from '../../../context/useCurrentLocation';
import { getLocationAsync } from '../../../util/getCurrentLocation';
import { textStyles, buttonStyles } from '../../../globalStyles/styles';
import EStyleSheet from 'react-native-extended-stylesheet';
const latitudeDelta = 0.025
const longitudeDelta = 0.025
const JobLocationScreen = (props) => {
const [region, setRegion] = useState({
latitude: 38.907192,
longitude: -30.036871,
latitudeDelta,
longitudeDelta
});
const [latitude, setLatitude] = useState(null);
const [longitude, setLongitude] = useState(null);
const [reverseGeocodingApi, reverseGeocodingdata, isReverseGeacodingDone, reverseGeocodingErrorMessage] = useReverseGeocoding();
let mapRef = useRef(null);
useEffect(() => {
getYourCurrentLocation();
},
[])
useEffect(() => {
animateToRegion();
},
[region])
const onMapReady = () => {
if(!isMapReady) {
setIsMapReady(true);
}
};
const getYourCurrentLocation = async () => {
const { location } = await getLocationAsync();
console.log(location);
setRegion(region);
}
const onRegionChangeComplete = (selectedRegion) => {
setRegion(selectedRegion);
reverseGeocodingApi(selectedRegion);
}
const animateToRegion = () => {
mapRef.animateToRegion(region, 1000);
}
const onNextButtonPress = () => {
props.navigation.state.params.setSelectedValue(jobTags);
props.navigation.pop();
}
const _renderMapWithFixedMarker = () => {
return (
<View style={{flex: 1}}>
<MapView
ref={ref => {
mapRef = ref
}}
onMapReady={onMapReady}
style={styles.map}
initialRegion={region}
onRegionChangeComplete={(selectedRegion) => onRegionChangeComplete(selectedRegion)}
/>
<View style={styles.pinBadge}>
<Text
style={{color: EStyleSheet.value('$primaryDarkGray')}}
>
Move to choose Location
</Text>
</View>
<View style={styles.markerFixed}>
<Image style={styles.marker} source={require('../../../assets/pin.png')} />
</View>
</View>
);
}
return (
<View style={styles.container}>
<View
pointerEvents='none'
style={styles.inputFieldContainer}
>
<InputField
maxLength={35}
placeholder='Selected Address'
value={isReverseGeacodingDone? reverseGeocodingdata.results[0].formatted_address : 'Loading ...'}
/>
</View>
{_renderMapWithFixedMarker()}
<View style={styles.bodyContainer}>
<View style={styles.buttonContainer}>
<Button
title="Confirm Your Location"
buttonStyle={buttonStyles.button}
onPress={() => onNextButtonPress()}
/>
</View>
</View>
</View>
);
}
JobLocationScreen.navigationOptions = ({navigation}) => {
return {
title: 'Select your Location'
};
};
export default JobLocationScreen;
const styles = EStyleSheet.create({
container: {
flex: 1,
backgroundColor: '$primaryBackgroundColor'
},
inputFieldContainer: {
backgroundColor: '#f8f9f9',
paddingVertical: 20,
paddingHorizontal: 20
},
map: {
flex: 1
},
pinBadge: {
position: 'absolute',
paddingVertical: 10,
paddingHorizontal: 15,
top: '38%',
alignSelf: 'center',
borderRadius: 25,
backgroundColor: '#ffffff',
shadowColor: '#acaeb4',
shadowOffset: {
width: 0,
height: 3,
},
shadowOpacity: 0.5,
shadowRadius: 5,
elevation: 5
},
markerFixed: {
left: '50%',
marginLeft: -10,
marginTop: -6,
position: 'absolute',
top: '50%'
},
marker: {
width: 20,
height: 41
},
bodyContainer: {
marginHorizontal: 20
},
buttonContainer: {
position: 'absolute',
bottom: 20,
width: '100%'
}
})
You need to access the ref using refname.current to get access to the value.
<MapView ref={mapRef}
and then when want to access it, use .current:
const animateToRegion = () => {
mapRef.current.animateToRegion(region, 1000);
}
See the docs
Conceptual Demo
for react-native-maps-super-cluster
<MapView ref={mapRef}
const animateToRegion = () => {
mapRef.current.getMapRef().animateToRegion(region, 1000);
}
How can I make a transition with maxHeight in React Native?
The equivalent code in React would be
function App() {
const [isOpen, setIsOpen] = useState(false)
return (
<div className="App">
<div className={`collapsible ${isOpen ? 'opened' : ''}`}>
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
</div>
<button onClick={() => setIsOpen(!isOpen)}>
{isOpen ? 'Close' : 'Open'}
</button>
</div>
);
}
And the css
.collapsible {
max-height: 0;
transition: max-height 0.35s ease-out;
overflow: hidden;
}
.opened {
max-height: 200px;
}
Here is a working codesandbox
How can I make the same but in React Native?
My guess is that you want to persist the animation when "opening" the view.
This is not supported out of the box using a StyleSheet object in React Native. You will have to implement this yourself using the Animated API.
https://facebook.github.io/react-native/docs/animations
Example:
import React from "react"
import { Animated } from "react-native"
import { Text, StyleSheet, TextProps } from "react-native"
import { TouchableOpacity } from "react-native-gesture-handler"
class AnimatedComponent {
constructor(props){
super(props)
this.state = { open: false }
this.animatedHeightValue = new Animated.Value(0)
}
_triggerAnimation = () => {
Animated.timing(this.animatedHeightValue, {
toValue: this.state.open ? 0 : 200,
duration: 200
}).start(() => {
this.setState({open: !this.state.open})
})
}
render() {
return(
<View>
<Animated.View style={{height: this.animatedHeightValue, backgroundColor: "blue"}}>
<Text>
{"Hello world"}
</Text>
</Animated.View>
<TouchableOpacity onPress={this._triggerAnimation}>
<Text>
{"Press for magic"}
</Text>
</TouchableOpacity>
</View>
)
}
Indentation is awful sorry, but should give an idea.
Hmmm.. I'm not completely sure did you actually meant this. But in case we're on the same page, check my Snack at: https://snack.expo.dev/#zvona/maxheight-animation
Here is the actual code (in case link of the snack gets deprecated):
import React, { useState, useRef } from 'react';
import {
Animated,
Text,
TouchableOpacity,
View,
StyleSheet,
} from 'react-native';
const App = () => {
const [isOpen, setIsOpen] = useState(false);
const [contentStyle, setContentStyle] = useState({ visibility: 'hidden' });
const currentHeight = useRef(new Animated.Value(0)).current;
const [maxHeight, setMaxHeight] = useState(0);
const toggleContent = () => {
setIsOpen(!isOpen);
Animated.timing(currentHeight, {
toValue: isOpen ? 0 : maxHeight,
duration: 350,
}).start();
};
const getContentHeight = ({ nativeEvent }) => {
if (maxHeight !== 0) {
return;
}
const { height } = nativeEvent.layout;
setMaxHeight(height);
setContentStyle({visibility: 'visible', height: currentHeight });
};
return (
<View style={styles.container}>
<TouchableOpacity onPress={toggleContent} style={styles.button}>
<Text>{'Open'}</Text>
</TouchableOpacity>
<Animated.View
style={[styles.content, contentStyle]}
onLayout={getContentHeight}>
<Text style={styles.paragraph}>
Change code in the editor and watch it change on your phone! Save to
get a shareable url.
</Text>
</Animated.View>
</View>
);
};
const styles = StyleSheet.create({
container: {
justifyContent: 'center',
backgroundColor: '#ffffff',
padding: 8,
},
button: {
justifyContent: 'center',
alignItems: 'center',
width: 120,
borderWidth: 1,
borderColor: 'black',
backgroundColor: '#c0c0c8',
padding: 10,
},
content: {
borderWidth: 1,
borderColor: 'blue',
overflow: 'hidden',
},
paragraph: {
fontSize: 18,
fontWeight: 'bold',
textAlign: 'center',
},
});
export default App;
The key ingredient of detecting the maxHeight is in onLayout event of the Animated.View. Then some magic on displaying content through state handling.
And if you want to define maxHeight manually, then just rip the getContentHeight off.