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.
Related
I Tried to using useRef to automate scroll if I press button
The idea is scroll with my scrollview and then I can press button to scroll to another set of view in my scrollview
this is my complete slider code
import { height } from 'deprecated-react-native-prop-types/DeprecatedImagePropType'
import { onChange } from 'deprecated-react-native-prop-types/DeprecatedTextInputPropTypes'
import React , {useState,useRef} from 'react'
import { StyleSheet, View , Text , ScrollView , Pressable , Image, Dimensions , TouchableOpacity} from 'react-native'
const WIDTH = Dimensions.get('window').width
const HEIGHT = Dimensions.get('window').height
const ImageSlider = ({slides}) => {
const refScrollview = useRef()
const [currentIndex,setCurrentIndex] = useState(0)
// console.log(slides[currentIndex].banner)
const [imageActive,setImageActive] = useState(1)
const onChange = (nativeEvent) =>{
if(nativeEvent){
const slide = Math.ceil(nativeEvent.contentOffset.x / nativeEvent.layoutMeasurement.width)
if(slide != imageActive){
console.log(slide)
setImageActive(slide)
console.log(refScrollview.current)
}
}
}
return(
<View style={{flex:1}}>
<ScrollView
ref={refScrollview}
// onContentSizeChange={(contentWidth, contentHeight)=> {refScrollview.current.scrollToEnd({animated: true})}}
onScroll={({nativeEvent}) => onChange(nativeEvent)}
showsHorizontalScrollIndicator={false}
pagingEnabled
horizontal
style={styles.wrap}
>
{
slides.map((e,index)=>
// <Text>{e.banner}</Text>
<View style={{flex:1}} key={index}>
<Image source={e.banner}
style={[styles.wrap]}
>
</Image>
<Image source={e.bannerFace}
style={styles.bannerFace}
>
</Image>
</View>
)
}
</ScrollView>
<View style={styles.wrapDot}>
{
slides.map((e,index)=>
<TouchableOpacity key={index} style={imageActive == index ? styles.lineActive : styles.lineOnly} onPress={() => {
refScrollview.current.scrollTo()}}>
</TouchableOpacity>
)
}
</View>
</View>
)
}
let styles = StyleSheet.create({
backgroundImage: {
flex:1,
width: null,
height: null,
// width: 400,
// height: 400,
// resizeMode: 'cover',
},
bannerFace:{
position:'absolute',
bottom:0,
right:0,
flex: 1,
width: 150,
height: 150,
resizeMode: 'contain'
},
wrap:{
width:WIDTH,
height:'100%'
},
wrapDot:{
position:'absolute',
bottom:20,
left:50,
flexDirection:'row',
alignSelf:'center'
},
lineOnly:{
padding:5,
width:70,
borderRadius:15,
borderColor:'#CECECE',
backgroundColor:'#CECECE',
marginHorizontal:5
},
lineActive:{
padding:5,
width:70,
borderRadius:15,
borderColor:'#FFF',
backgroundColor:'#FFF',
marginHorizontal:5
}
})
export default ImageSlider
I get the error TypeError: undefined is not a function, js engine: hermes
whenever i try to push/use this
onPress={() => {
refScrollview.current.scrollTo()}}
It looks like there is no scrollTo() function , am I doing this wrong ? or is it my react node.modules ?
I am a new to react native and i wonder how to use scrollView with keyboardAvoid in the following scene.I have searched for some relevant information, but still have no clue.
Following picture may be more intuitive then explain.
enter image description here
import React,{useRef,useEffect,useState} from 'react';
import { Text, View, StyleSheet,TextInput,ScrollView,KeyboardAvoidingView,TouchableOpacity } from 'react-native';
export default function App() {
const [selectedItem,setSeLectedItem] = useState(null)
const [userTyping,setUserTyping] = useState(false);
const selected = (index) => {
setUserTyping(true)
setSeLectedItem(index)
}
const onBlur=()=>{
setUserTyping(false)
}
return (
<View style={styles.container}>
<KeyboardAvoidingView style={{flex:1}}>
<ScrollView style={styles.scroll} >
{
['1', '2', '3', '4','5','6','7'].map((item,index) => (
<TouchableOpacity onPress={()=>selected(index)} key={item}>
<View style={[styles.itemWrapper,selectedItem===index &&styles.selectedItem]}>
<Text style={styles.itemText}>TEST {item}</Text>
{
(selectedItem===index)&&userTyping&&
<InputFC
style={styles.itemInput}
placeholder='NO TEXT'
placeholderTextColor={'white'}
autoCapitalize={'none'}
onBlur={onBlur}
/>
}
</View>
</TouchableOpacity>
))
}
</ScrollView>
</KeyboardAvoidingView>
</View>
);
}
const InputFC = (props) => {
const {style,placeholder,placeholderTextColor,autoCapitalize,onBlur} = props
const inputRef = useRef(null);
useEffect(()=>{
if(inputRef.current) inputRef?.current?.focus()
})
return (
<TextInput
style={style}
ref={inputRef}
placeholder={placeholder}
placeholderTextColor={placeholderTextColor}
autoCapitalize={autoCapitalize}
onBlur={onBlur}
/>)
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#24292e',
},
scroll: {
flex: 1,
paddingHorizontal: 20,
},
itemWrapper:{
width: '100%',
paddingLeft:20,
paddingVertical:20,
marginBottom: 20,
backgroundColor: '#6b6965',
borderRadius:20,
},
itemText:{
fontSize:20,
fontWeight:'bold',
color:'white',
alignItems:'center',
justifyContent:'center',
marginBottom:5,
}
,
itemInput:{
fontSize:20,
color:'white',
},
selectedItem:{
borderWidth:3,
borderColor:'#2188ff'
}
});
Simplified Demo online:
https://snack.expo.dev/#bravo20203/demo-about-scrollview
Thanks.
You can make this work by changing the flex: 1 to different components and by adding
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
style={{flex: 1}}
to the KeyboardAvoidingView. I have also wrapped your content into a SafeAreaView so it does not go into the header on iOS.
Since you are dynamically expanding the TextInput on press, which changes its height, I have added a keyboardVerticalOffset to the KeyboardAvoidingView and made the overflow visible.
The final code is as follows.
import React, { useEffect, createRef, useState, useRef } from 'react';
import { useHeaderHeight } from "#react-navigation/elements";
import {
Text,
View,
KeyboardAvoidingView,
ScrollView,
TextInput,
Platform,
TouchableOpacity,
StyleSheet,
SafeAreaView
} from 'react-native';
export default function App() {
const [selectedItem,setSeLectedItem] = useState(null)
const [userTyping,setUserTyping] = useState(false);
const selected = (index) => {
setUserTyping(true)
setSeLectedItem(index)
}
const onBlur=()=>{
setUserTyping(false)
}
return (
<SafeAreaView style={styles.container}>
<KeyboardAvoidingView
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
style={Platform.OS === 'ios' && {flex: 1}}
keyboardVerticalOffset={30}
>
<ScrollView style={styles.scroll}>
{
['1', '2', '3', '4','5','6','7'].map((item,index) => (
<TouchableOpacity onPress={()=>selected(index)} key={item}>
<View style={[styles.itemWrapper,selectedItem===index &&styles.selectedItem]}>
<Text style={styles.itemText}>TEST {item}</Text>
{
(selectedItem===index)&&userTyping&&
<InputFC
style={styles.itemInput}
placeholder='NO TEXT'
placeholderTextColor={'white'}
autoCapitalize={'none'}
onBlur={onBlur}
/>
}
</View>
</TouchableOpacity>
))
}
</ScrollView>
</KeyboardAvoidingView>
</SafeAreaView>
);
}
const InputFC = (props) => {
const {style,placeholder,placeholderTextColor,autoCapitalize,onBlur} = props
const inputRef = useRef(null);
useEffect(()=>{
if(inputRef.current) inputRef?.current?.focus()
})
return (
<TextInput
style={style}
ref={inputRef}
placeholder={placeholder}
placeholderTextColor={placeholderTextColor}
autoCapitalize={autoCapitalize}
onBlur={onBlur}
/>)
}
const styles = StyleSheet.create({
container: {
backgroundColor: '#24292e',
flex: 1
},
scroll: {
paddingHorizontal: 20,
overflow: "visible",
},
itemWrapper:{
width: '100%',
paddingLeft:20,
paddingVertical:20,
marginBottom: 20,
backgroundColor: '#6b6965',
borderRadius:20,
},
itemText:{
fontSize:20,
fontWeight:'bold',
color:'white',
alignItems:'center',
justifyContent:'center',
marginBottom:5,
}
,
itemInput:{
fontSize:20,
color:'white',
},
selectedItem:{
borderWidth:3,
borderColor:'#2188ff',
}
});
I have updated your snack here. Notice that I have tested this on iOS only.
If the keyboard is opened on select, the selected Input is now fully visible.
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,
},
});
Is there any possibility to customise the event on X for react-native-video?
To summarise my problem i have an app when on press play it will trigger a modal with a fullscreen video and starts to play automatically, this is the behavior i was seeking.
Video example
But then my issue is when i close the video it looks like is still playing on background. I tried to pass a onClose prop to close the modal but still facing the same issue.
This is what i tried:
Parent component:
<Modal
style={{justifyContent: 'center', alignItems: 'center'}}
animationType="slide"
transparent={false}
visible={modalVisible}
supportedOrientations={['portrait', 'landscape']}
onRequestClose={() => {
setModalVisible(!modalVisible);
}}>
{modalVisible && <View
style={{
backgroundColor: colors.background,
justifyContent: 'center',
alignItems: 'center',
}}>
<VideoPlayer onClose={() => setModalVisible(!modalVisible)}/>
</View>}
</Modal>
Video player:
import React, {
Component
} from 'react';
import {
StyleSheet,
Dimensions,
} from 'react-native';
import Video from 'react-native-video';
// Dimensions
const {width, height} = Dimensions.get('screen');
class VideoPlayer extends Component {
constructor(props) {
super(props);
const isPortrait = () => {
const dim = Dimensions.get('screen');
return dim.height >= dim.width;
};
this.state = {
orientation: isPortrait() ? 'portrait' : 'landscape'
}
// Event Listener for orientation changes
Dimensions.addEventListener('change', () => {
this.setState({
orientation: isPortrait() ? 'portrait' : 'landscape'
});
});
}
render() {
return (
<Video
fullscreen={true}
onFullscreenPlayerWillDismiss={() => this.props.onClose()}
onEnd={() => this.props.onClose()}
playWhenInactive={false}
playInBackground={false}
ref={p => { this.videoPlayer = p; }}
source={{uri: 'https://rawgit.com/mediaelement/mediaelement-files/master/big_buck_bunny.mp4'}}
style={{width: this.state.orientation === 'landscape' ? height : width, height: this.state.orientation === 'landscape' ? width : height }}
controls={true}
fullscreenOrientation="all"
/>
)
}
}
const styles = StyleSheet.create({
backgroundVideo: {
position: 'absolute',
top: 0,
left: 0,
bottom: 0,
right: 0,
},
});
export default VideoPlayer;
If anyone faced similar issue please let me know.
Thanks in advance.
Solved by using the package react-native-video-controls and customise the onBack prop:
<VideoPlayer
onBack={() => this.props.onClose()}
fullscreen={true}
onExitFullscreen={() => this.props.onClose()}
onEnd={() => this.props.onClose()}
playWhenInactive={false}
playInBackground={false}
source={{uri: 'https://rawgit.com/mediaelement/mediaelement-files/master/big_buck_bunny.mp4'}}
fullscreenOrientation="all"
/>
I set the Expo camera to open on the middle tab with react navigation. However, the camera only opens when I click on that tab the first time. If I switch off of it and go back it's just a black screen. Also the take a picture button is not there. (I am new with react native and kinda coding as a whole)
'use strict';
import React, { Component } from 'react';
import { createBottomTabNavigator } from 'react-navigation';
import { Camera, Permissions } from 'expo';
import {
AppRegistry,
Dimensions,
StyleSheet,
Text,
TouchableOpacity,
View,
Button
} from 'react-native';
class HomeScreen extends React.Component {
render() {
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Text>Home!</Text>
</View>
);
}
}
class CameraView extends React.Component {
state = {
hasCameraPermission: null,
type: Camera.Constants.Type.back,
};
async componentWillMount() {
const { status } = await Permissions.askAsync(Permissions.CAMERA);
this.setState({ hasCameraPermission: status === 'granted' });
}
render() {
const { hasCameraPermission } = this.state;
if (hasCameraPermission === null) {
return <View />;
} else if (hasCameraPermission === false) {
return <Text>No access to camera</Text>;
} else {
return (
<View style={{ flex: 1 }}>
<Camera style={{ flex: 1 }} type={this.state.type}>
<View
style={{
flex: 1,
backgroundColor: 'transparent',
flexDirection: 'row',
}}>
<TouchableOpacity
style={{
flex: 0.1,
alignSelf: 'flex-end',
alignItems: 'center',
}}
onPress={() => {
this.setState({
type: this.state.type === Camera.Constants.Type.back
? Camera.Constants.Type.front
: Camera.Constants.Type.back,
});
}}>
<Text
style={{ fontSize: 18, marginBottom: 10, color: 'white' }}>
{' '}Flip{' '}
</Text>
</TouchableOpacity>
</View>
</Camera>
</View>
);
}
}
}
class SettingsScreen extends React.Component {
render() {
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Text>Settings!</Text>
</View>
);
}
}
export default createBottomTabNavigator({
Home: HomeScreen,
Camera:CameraView,
Settings: SettingsScreen
});
const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: 'column',
top: 250
},
capture: {
flex: 0,
backgroundColor: '#fff',
borderRadius: 5,
padding: 15,
paddingHorizontal: 20,
alignSelf: 'center',
margin: 20
}
});
With react navigation 5.x
import { useIsFocused } from '#react-navigation/native';
export const CameraView = (props) => {
const isFocused = useIsFocused();
return (
<View>
{ isFocused && <RNCamera /> }
</View>
)
}
useIsFocused Documentation
I had some issue.
This code solved it:
import { useIsFocused } from '#react-navigation/native';
function MyComponent() {
const isFocused = useIsFocused()
return (
<View>
{ isFocused && <RNCamera /> }
</View>
)
}
Old navigation
import { withNavigationFocus } from 'react-navigation'
render() {
const { isFocused } = this.props
return (
<View>
{ isFocused && <RNCamera ... /> }
</View>
)
}
export default withNavigationFocus(Camera)
To make this work you need to:
1.
import { NavigationEvents } from 'react-navigation';
state = { loaded: true }
render() {
const { loaded } = this.state;
return (
<View style={styles.container}>
<NavigationEvents
onWillFocus={payload => this.setState({loaded: true})}
onDidBlur={payload => this.setState({loaded: false})}/>
<View style={styles.cameraArea}>
{loaded && (
<Camera
type={Camera.Constants.Type.back}
ref={ref => {
this.camera = ref;
}}
/>
)}
</View>
The idea is to hide this camera view (onDidBlur-> loaded: false), then when you come back (onWillFocus is triggered and change loaded to true). When render() function is called it will show the <Camera /> again.
If you have any questions, feel free to ask.
This works for me. ( Navigation 5.x )
if you are using a different screen for CAMERA, you can easy unmount the screen when moving to another.
Reason of this behavior : Only one Camera preview can be active at any
given time. If you have multiple screens in your app, you should
unmount Camera components whenever a screen is unfocused.
<Stack.Screen name="camera" component={CameraScreen} options={{unmountOnBlur: true}}/>
</Stack.Navigator>
Documentation Link : https://docs.expo.io/versions/latest/sdk/camera/
Thanks.
I got it working by using NavigationEvents to determine if the tab is in focus and from there mount and unmount the camera. This also frees up the camera, if you need to use it in another screen. Here is what I would do in your example:
import { NavigationEvents } from 'react-navigation';
...
class CameraView extends React.Component {
constructor(props) {
super(props)
this.state = {
hasCameraPermission: null,
type: Camera.Constants.Type.back,
isFocused:true
};
}
...
render(){
//...your existing if's
} if(this.state.isFocused === false){
return (
<NavigationEvents
onWillFocus={payload => {
//console.log("will focus", payload);
this.setState({isFocused:true})
}}
onDidBlur={payload => {
//console.log('did leave',payload)
this.setState({isFocused:false})
}}
/>
)
}
} else {
return (
<View style={{ flex: 1 }}>
<Camera style={{ flex: 1 }} type={this.state.type}>
<NavigationEvents
onWillFocus={payload => {
//console.log("will focus", payload);
this.setState({isFocused:true})
}}
onDidBlur={payload => {
//console.log('did leave',payload)
this.setState({isFocused:false})
}}
/>
//...the rest of your camera code
}
I hope it works for you as well
I solved it using the hook useIsFocused from react-navigation/native. I tested it on Android, iOS and Web
import { useIsFocused } from '#react-navigation/native';
...
const isFocused = useIsFocused();
...
return (
isFocused && (
<Camera
ref={(ref) => {
camera = ref;
}}
onBarCodeScanned={scanned ? undefined : handleBarCodeScanned}
style={StyleSheet.absoluteFillObject}
/>
)
);
If you are using RN Expo with React Navigation - Tab Navigator.
Then just use unmountOnBlur - unmountOnBlur Documentation
This will force the camera to unmount on every navigation focus changes.
in Expo react native
import { useIsFocused } from '#react-navigation/native';
const isFocused = useIsFocused();
{isFocused &&
<Camera
type={type}
onBarCodeScanned={scanned ? undefined : handleBarCodeScanned}
style={{ flex: 1 }}
flashMode={flash}
zoom={zoom}
/>
}