Adjust the scrollview height dynamically at runtime - react-native

I am developing a react-native project.
I have a ScrollView in MyComponent, the content of ScrollView consists of :
a MySubComponent,
a Text
a Image component
All the content/data of above components have dynamic length or height. So, I would like adjust the content height of my ScrollView on the fly at runtime.
To achieve that my plan is to disable automaticallyAdjustContentInsets of the ScrollView as you can see from my below code snippet. Then, have a state variable to hold the latest value of contentInsetBottom. But I am not sure how can I calculate the height of child components so that I can call setContentInsetBottom(totalHeight) to update the content height of my ScrollView .
(I am pretty sure my plan will work if I know how to calculate the height of each child component of my ScrollView.)
Anyone can guide me a bit?
const MyComponent = ({myText, image}) => {
// I have a state of the contentInsetBottom for the ScrollView
const [contentInsetBottom, setContentInsetBottom] = useState(0);
// how can I get the height of each children component, sum them up & call setContentInsetBottom(totalHeight) here ?
return (
<ScrollView
automaticallyAdjustContentInsets={false}
contentInset={{top: 0, bottom: contentInsetBottom}}
style={styles.scrollView}
contentContainerStyle={styles.contentContainer}>
<MySubComponent />
<Text>{myText}</Text>
<Image source={{uri: image?.uri}}>
</ScrollView>)
}

Wrap all content inside the <ScrollView> in a <View>. Then use onLayout to get the height of that parent View.
const handleScrollContentLayout = (e) => {
const { height } = e.nativeEvent.layout
setScrollLayoutHeight(height)
}
...
<View onLayout={handleScrollContentLayout}>
{ /* scrollView content... */ }
</View>
Then you can use the scrollLayoutHeight as per your needs to set the height at runtime.

Related

How to make a React Native TextInput change Opacity just like TouchableOpacity?

My code looks something like this currently:
<View>
<TextInput placeholder='PlaceholderText'>
</TextInput>
</View>
I want to make a TextInput component that has an opacity animation on click (exactly like TouchableOpacity changes opacity on click).
I tried wrapping the TextInput inside TouchableOpacity, but it doesn't work since the touchable component surrounds text input. Is there a standard React Native or StyleSheet way of doing this or would I have to manually create an animation to mimic that effect?
Simply wrap your TextInput in a View element and animate the View's opacity color from the onFocs event of the TextInput. TextInput doesn't have the opacity attribute, therefore the additional view is required for your goal.
Following code may give you an idea how to solve it. Have to admit, that I haven't tested the code, it may contain some error, so use it carefully.
// First create an animated value to use for the view's opacity.
const textInputAnimOpacity = useRef(new Animated.Value(1.0)).current;
// create a method to set the opacity back to 1.0
const showTxtInput = () => {
Animated.timing(textInputAnimOpacity, {
toValue: 1.0, // value to reach
duration: 250 // time in ms
}).start();
};
// this animated method differs from the first one by (obviously the value 0.7)
//and the callback part that goes into the start()-method
const blurTxtInput = () => {
Animated.timing(textInputAnimOpacity, {
toValue: 0.7, // value to reach
duration: 250 // time in ms
}).start(({ finished }) => {
showTxtInput(); // Callback after finish, setting opacity back to 1.0
});
};
/*Set the opacity to the value, we want to animate*/
<View style={{opacity:textInputAnimOpacity}}>
/* call blurTxtInput to set the value to 0.7 and again to 1.0 once it reaches 0.7*/
<TextInput onPressIn={blurTxtInput} placeholder='PlaceholderText'>
</TextInput>
</View>
If you just want to set opacity, make your styles change using the onPressIn and onPressOut props:
const [pressed, setPressed] = useState(false);
// in render
<TextInput
onPressIn={() => setPressed(true)}
onPressOut={() => setPressed(false)}
style={pressed ? styles.textInputPressed : styles.textInput}
// ...
/>
If you need the changes to animate, you can do that with the built-in RN Animated component or react-native-reanimated, using the same props to trigger the animations.

Set Header Height with Safe Area Insets with React Navigation

I can pass screenOptions to the Navigator with
a headerStyle object with a height property, but I'd like the height to take into account the SafeAreaInsets and not be a fixed value
There's getDefaultHeaderHeight function that takes into account the statusBarHeight here https://github.com/react-navigation/react-navigation/blob/dbe961ba5bb243e8da4d889c3c7dd6ed1de287c4/packages/drawer/src/views/Header.tsx#L8 - is there a way I can call this function and simply add n pixels across all devices?
I'm not sure about in React Navigation 5, but height is not a supported property of headerStyle in React Navigation 6. The rest of this response is based on references to and my personal experience with React Navigation 6.
You may need to write a fully custom header component to achieve what you want. React Navigation even has a Guide on supporting safe areas. Summarizing the Guide from React Navigation, plus a little extra real-world usage of it, you can then use the useSafeAreaInsets hook from react-native-safe-area-context and then in the style for the View surrounding your custom header, you can use the top value from the hook response as paddingTop to ensure that your header avoids the status bar area. If the rest of the height of your header needs to be explicitly defined, you'll need to add that top value to your height number as the height property of this View. If the height of your header is dynamic and based on its content, you probably don't actually need to set an explicit height on your header.
Example:
import React, { useEffect } from 'react';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
const MyComponent = () => {
const insets = useSafeAreaInsets();
const navigation = useNavigation();
useEffect(() => {
navigation.setOptions({
// only us height if you REALLY need it, height should be dynamic based on content
header: () => <View style={{ paddingTop: insets.Top, height: value + insets.Top }}>
{content of your custom header}
</View>
});
}, [insets, navigation]);
};

How to handle responsive layout in React Native

I'm using the react-native-dimension library for making my UI responsive as follows:
const{width,height} = Dimensions.get('window');
and in my style.js file :
imageBackgroundLandscape:{
width:height,
height:width
},
imageBackgroundPortrait:{
width:width,
height:height
}
The problem is that when I rotate the screen, the width and height variables have got previous values!
For example in the portrait mode my variables are:
width : 800
height: 1280
and when I rotate the screen my variables are:
width : 800 // previous value
height: 1280 // previous value
In addition, I use the react-native-orientation to determine the mode of the screen.
I want to know how can I change the values of them (width, height) automatically when I rotate the device, or are there any other libraries for this?
Thanks in advance.
I usually handle the height, width confusion with the following code:
//Dimensions.js
import {Dimensions} from 'react-native';
const {height, width} = Dimensions.get('window');
const actualDimensions = {
height: (height<width) ? width : height,
width: (width>height) ? height : width
};
export default actualDimensions;
Instead of requiring the height and width from Dimensions, use the actualDimensions and for managing the orientation gracefully you should give a try to this library as well.
The Dimensions are loaded before the JS bundle gets loaded into the app so it is recommended to fetch the height, width dynamically for every render
You can read this here
I usually used Flexbox to arrange the layout for my components. It helps them to be responsive. Maybe you could give a try too.
Layout with Flexbox
You can use these steps to make your UI responsive.
1: use percentage whenever it's possible
2: use the power of flexbox to make your UI grow and shrink
3: use Dimension API
Actually, you do right but half of the task. you got the width and height from Dimensions and it is right, but how react-native understand your orientation changes?
First, your code should understand the change of orientation, then you set a call-back function to change the state of your application for implementing new width and height.
Awfully, I don't know the react-native can understand a change of orientation with its built-in functions or not. So I'm using this library to understand orientation changes and then I use setState to re-render the codes.
Absolutely, I put the width and height inside state of the component.
If you wanna lock the orientation change, use this library.
Firstly:
You are facing that issue is because you forgot to call const{width,height}
= Dimensions.get('window'); again when the orientation has changed.
In order to get the latest value of width and height after the orientation change you would have to call the Dimensions.get('window') function again and get width and height from it's output.
Secondly:
Instead of using multiple libraries, you can just use one library(react-native-styleman), that lets you handle this type of stuff very easily:
Here is how the code would look like using react-native-styleman.
import { withStyles } from 'react-native-styleman';
const styles = () => ({
container: {
// your common styles here for container node.
flex: 1,
// lets write a media query to change background color automatically based on the device's orientation
'#media': [
{
orientation: 'landscape', // for landscape
styles: { // apply following styles
// these styles would be applied when the device is in landscape
// mode.
backgroundColor: 'green'
//.... more landscape related styles here...
}
},
{
orientation: 'portrait', // for portrait
styles: { // apply folllowing styles
// these styles would be applied when the device is in portrait
// mode.
backgroundColor: 'red'
//.... more protrait related styles here...
}
}
]
}
});
let MainComponent = ({ styles })=>(
<View style={styles.container}>
<Text> Hello World </Text>
</View>
);
// now, lets wire up things together.
MainComponent = withStyles(styles)(MainComponent);
export {
MainComponent
};
I am using react-native-responsive-screen. it is working also with orientation change
USAGE
import {
widthPercentageToDP as wp,
heightPercentageToDP as hp,
listenOrientationChange as lor,
removeOrientationListener as rol
} from 'react-native-responsive-screen';
class Login extends Component {
componentDidMount() {
lor(this);
}
componentWillUnmount() {
rol();
}
render() {
const styles = StyleSheet.create({
container: { flex: 1 },
textWrapper: {
height: hp('70%'),
width: wp('80%')
},
myText: { fontSize: hp('5%') }
});
return (
<View style={styles.container}>
<View style={styles.textWrapper}>
<Text style={styles.myText}>Login</Text>
</View>
</View>
);
}
}
export default Login;

React Native Animated API - combine translate and rotation

Encountered this issue recently when I work with react native animated API.
As the image shows, a card component is positioned at top left corner, its flip animation state is controlled by the rotateY value, moving animation is controlled by translateX and translateY values.
It seems the rotation pivot point always been set to the card's original position. After the card has been moved (changing the translateX and translateY value), the card flip rotation animates reference its original position.
It there a way to adjust the rotation pivot point? Alternatively, is there a way to animate component's position instead of translation? Thanks.
Got it working finally. Turns out you can animate the component position change without using the translate property, by adding a listener to the animated value and updating the component state accordingly:
in the constructor, setup card component initial position and cardPos animated value.
in the componentDidMount function, attach listeners to the animated values. when animated values change, update the component state.
in the render function set the component root value style to position:"absolute" and actual position sync to the values in component's state.
constructor(props){
super(props);
// set card initial position as component state
this.state = {
cardPosX: this.props.position.x,
cardPosY: this.props.position.y
};
this.flipAnimatedValue = new Animated.Value(
this.props.isFacingUp ? 180 : 0
);
this.flipAnimatedInterpolate = this.flipAnimatedValue.interpolate({
inputRange: [0, 90, 180],
outputRange: ["0deg", "90deg", "0deg"]
});
// create animated value for card Position X and Y
this.cardPosXAnimatedValue = new Animated.Value(this.props.position.x);
this.cardPosYAnimatedValue = new Animated.Value(this.props.position.y);
}
componentDidMount() {
// addListener for cardPos Animated Value
// when animated values change, update the component state
this.cardPosXAnimatedValue.addListener(({ value }) => {
this.setState({ cardPosX: value });
});
this.cardPosYAnimatedValue.addListener(({ value }) => {
this.setState({ cardPosY: value });
});
}
render(){
return (
<View
style={{
width: this.cardWidth,
height: this.cardHeight,
position: "absolute",
top: this.state.cardPosY, //card position sync with animated value
left: this.state.cardPosX
}}
>
... //child components
</View>
);
}

Minimum width/height in React Native

How can I set minimum width or height for React Native component?
In css, I can use min-width/min-height
https://facebook.github.io/react-native/docs/flexbox.html#content
There is no minWidth/minHeight on react-native docs
minHeight, maxHeight, minWidth and maxWidth properties are supported as of react-native version 0.29.0 for iOS and Android.
Here is the maxHeight description from react-native documentation. This description also applies for other min/max style properties.
maxHeight is the maximum height for this component, in
logical pixels.
It works similarly to max-height in CSS, but in React Native you must
use points or percentages. Ems and other units are not supported.
See https://developer.mozilla.org/en-US/docs/Web/CSS/max-height for
more details.
A dummy example:
<View
style={{
minWidth: '20%',
maxWidth: 500,
minHeight: '10%',
maxHeight: 150,
}}
/>
This answer is outdated now, use halilb's answer.
I solved this by using the onLayout prop, its very easy:
Example:
Step 1: I create a prop in our state that will be holding the current height of the image called curImgHeight.
constructor(){
super(props);
this.state={curImgHeight:0}
}
Step 2: Use the prop in any View or Element that supports the onLayout prop.
Here I use it with an Image. Then all we have to do is, change that state property whenever the actual image height is than our minimum height.
render(){
<Image
source={{uri: "https://placehold.it/350x150"}}
resizeMode='cover'
style={[styles.image, {height:(this.state.curImgHeight<=0?null:this.state.curImgHeight)}]}
onLayout={(e)=>{
let {height} = e.nativeEvent.layout;
let minimumImgHeight = 400; //We set the minimum height we want here.
if(height<= minimumImgHeight){ //Whenever the real height of the image is less than the minimum height
this.setState({curImgHeight:minimumImgHeight}); //just change the curImgHeight state property to the minimum height.
}
}}
/>
}
Thats how I solved it for me.
p.s: During my search I found that react-native unofficially supports minHeight and maxHeight but only for iOS and not for Android. I wouldn't dare using them though. The above code works well and gives me control.
I was able to work out the solution for you. Here's a working demo...
https://rnplay.org/apps/vaD1iA
And here are the key parts.
First, you pull in the device dimensions...
var Dimensions = require('Dimensions');
var {
width,
height
} = Dimensions.get('window');
Here's the button component, which uses the device width as the basis for the button's with
const Button = React.createClass({
render(){
return(
<TouchableHighlight>
<Text style={[styles.button,{width: width - 20}]}>
{this.props.children}
</Text>
</TouchableHighlight>
)
}
});
Then, as you can see here, the button width will be the same regardless of label content width.
You can use minHeight or flexBasis - it is similar.
You can do something like that:
_getButtonStyle() {
var style = {height:36,padding:8}
if(this.props.buttonText.length < 4){
style.width = 64
}
return style
}