Scroll doesn't work with ScrollView and absolute positioned component - react-native

I have a scrollView and a component with absolute position. The scrollView doesn't scroll at all. Have a look at the code: https://snack.expo.io/#codebyte99/overlap-test
<ScrollView contentContainerStyle={{ flex: 1 }}>
<View style={styles.container}>
<Image
source={{
uri: 'https://facebook.github.io/react/logo-og.png',
cache: 'only-if-cached',
}}
style={{width: 400, height: 100}}
/>
<View style={styles.overlap}>
<Text>Event1</Text>
<Text>Event2</Text>
<Text>Event3</Text>
<Text>Event4</Text>
<Text>Event5</Text>
<Text>Event6</Text>
<Text>Event7</Text>
<View style={{ backgroundColor: 'orange', height: 800, width: 500 }}/>
<View style={{ backgroundColor: 'green', height: 800, width: 500 }}/>
</View>
</View>
</ScrollView>
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
paddingTop: Constants.statusBarHeight,
backgroundColor: '#ecf0f1',
padding: 8,
},
overlap: {
width: 300,
backgroundColor: 'red',
borderColor: 'red',
borderWidth: 1,
position: 'absolute',
top: 80,
zIndex: 9999,
overflow: 'visible'
},
});
If the flex is not used in scrollView, the absolute positioned component are not visible completely and if it is used, the scroll doesn't work.

With flex: 1, the ScrollView container takes all available space, however this does not include the items positioned absolutely inside it since they are out of the document flow.
So you should remove it, and instead you should give the container style enough height to display every item inside:
container: {
flex: 0,
alignItems: 'center',
paddingTop: Constants.statusBarHeight,
backgroundColor: '#ecf0f1',
padding: 8,
height: 2000,
},

Did you try to change flex: 1 to flexGrow: 1 in contentContainerStyle.

Related

Vertically center image that's not affected by KeyboardAwareScrollView in React Native

Alright, so this has got me busy for quite a few hours already. I am trying to create a login screen where the main components are rendered on the bottom, with the logo in the remaining space. This is kind of what I would like to achieve:
To support my textinputs, I use KeyboardAwareScrollView, as it works better for me as opposed to KeyboardAvoidingView. My code currently looks like this (I plan on using a background image with a 50% color overlay rather than the red background, so the ImageBackground has to stay in place too):
<ImageBackground
source={require('./assets/img/background-clouds.png')}
resizeMode="cover"
style={styles.backgroundImage}>
<View style={styles.backgroundOverlay} />
<View style={styles.dummyView}>
<Text>elloha</Text>
</View>
<Image
source={require('./assets/img/logo.png')}
style={styles.backgroundLogo}
resizeMode="contain"
/>
<KeyboardAwareScrollView
keyboardShouldPersistTaps="always"
keyboardOpeningTime={0}
alwaysBounceHorizontal={false}
alwaysBounceVertical={false}
contentInsetAdjustmentBehavior="automatic"
showsHorizontalScrollIndicator={false}
showsVerticalScrollIndicator={false}
automaticallyAdjustContentInsets={false}
extraScrollHeight={30}
enableOnAndroid>
<StatusBar
backgroundColor="transparent"
barStyle="light-content"
hidden={false}
translucent
/>
<TouchableWithoutFeedback
onPress={Keyboard.dismiss}
accessible={false}>
<View style={styles.content}>
<View style={styles.backgroundContainer}>
<SafeAreaView style={{ flex: 0 }} />
<View style={styles.loginContainer}>
<View style={styles.loginScreen}>
// textinputs and buttons go here
</View>
<SafeAreaView style={{ backgroundColor: 'white' }} />
</View>
<View
style={{
backgroundColor: 'white',
height: Dimensions.get('window').height,
position: 'absolute',
width: Dimensions.get('window').width,
top: Dimensions.get('window').height,
}}
/>
</View>
</View>
</TouchableWithoutFeedback>
</KeyboardAwareScrollView>
</ImageBackground>
Relevant styles:
const styles = StyleSheet.create({
container: {
backgroundColor: "white",
},
content: {
flex: 1,
},
backgroundImage: {
flex: 1,
position: "absolute",
top: 0,
bottom: 0,
left: 0,
right: 0,
width: Dimensions.get("window").width,
height: Dimensions.get("window").height,
},
backgroundContainer: {
justifyContent: "flex-end",
flex: 1,
width: Dimensions.get("window").width,
height: Dimensions.get("window").height,
},
backgroundOverlay: {
backgroundColor: "red",
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
},
logoContainer: {
top: "10%",
width: "100%",
},
backgroundLogo: {
alignSelf: "center",
position: "absolute",
width: 126,
height: 96,
},
dummyView: {
backgroundColor: "red",
flex: 1,
},
loginContainer: {
borderTopEndRadius: 30,
borderTopStartRadius: 30,
width: "100%",
backgroundColor: "white",
height: 500,
alignItems: "center",
paddingTop: Dimensions.get("window").width * 0.1,
},
loginScreen: {
width: "80%",
backgroundColor: "white",
},
});
This yields the following result:
I can get it done by adding top: 160 to the backgroundLogo style, but that's a fixed value. I want it to be always in the center of the available space, but I'm unable to add a view between the background and the loginContainer, as all the logic for the keyboard and such is handled in between.
Is there a way to achieve what I want? Ideally, I should also be able to check the available height, and only show the logo if there is enough space (e.g. available height > 100, otherwise don't show logo).
Important:
I want the logo to stay fixed, so if the keyboard is shown, the logo should not move up. The loginContainer should go "over" the logo
EDIT:
Wrap Image inside a View with this style and give the loginContainer style height: '70%' :
...
<View style={styles.dummyView}>
<Text>elloha</Text>
</View>
<View
style={{
justifyContent: 'center',
alignItems: 'center',
height: '30%',
position: 'absolute',
width: '100%',
}}>
<Image
source={require('./assets/img/logo.png')}
style={styles.backgroundLogo}
resizeMode="contain"
/>
</View>
<KeyboardAwareScrollView
keyboardShouldPersistTaps="always"
...
...
loginContainer: {
borderTopEndRadius: 30,
borderTopStartRadius: 30,
width: '100%',
backgroundColor: 'orange',
height: '70%',
alignItems: 'center',
paddingTop: Dimensions.get('window').width * 0.1,
},
...
hie! I think using Dimension to get a specific screen's height and deciding it has 70% of the screen covered via form sheet and rest is free for a logo to be in and we can ask it to be down a little using rest of height's 50% as margin-top ( the image will be in the center of that image )
here is a SNACK LINK to see your example working with my suggested solution.
here is the draft code:
import * as React from 'react';
import { Text, View, StyleSheet, Dimensions, ImageBackground,TextInput,
Image, TouchableWithoutFeedback, Keyboard, SafeAreaView, StatusBar} from 'react-native';
import Constants from 'expo-constants';
import {KeyboardAwareScrollView} from 'react-native-keyboard-aware-scroll-view';
export default function App() {
return (
<ImageBackground
source={{
uri: 'https://cdn.pixabay.com/photo/2021/08/23/18/37/tea-6568547_960_720.jpg',
}}
resizeMode="cover"
style={styles.backgroundImage}>
<View style={styles.backgroundOverlay} />
<Image
source={require('./assets/snack-icon.png')}
style={styles.backgroundLogo}
resizeMode="contain"
/>
<KeyboardAwareScrollView
keyboardShouldPersistTaps="always"
keyboardOpeningTime={0}
alwaysBounceHorizontal={false}
alwaysBounceVertical={false}
contentInsetAdjustmentBehavior="automatic"
showsHorizontalScrollIndicator={false}
showsVerticalScrollIndicator={false}
automaticallyAdjustContentInsets={false}
extraScrollHeight={30}
enableOnAndroid>
<StatusBar
backgroundColor="transparent"
barStyle="light-content"
hidden={false}
translucent
/>
<TouchableWithoutFeedback onPress={Keyboard.dismiss} accessible={false}>
<View style={styles.content}>
<View style={styles.backgroundContainer}>
<SafeAreaView style={{flex: 0}} />
<View style={styles.loginContainer}>
<View style={styles.loginScreen}>
<TextInput value={'email'} style={{borderBottomWidth:1, padding:10}}/>
<TextInput value={'password'} style={{borderBottomWidth:1, padding:10}}/>
</View>
<SafeAreaView style={{backgroundColor: 'white'}} />
</View>
<View
style={{
backgroundColor: 'white',
height: Dimensions.get('window').height,
position: 'absolute',
width: Dimensions.get('window').width,
top: Dimensions.get('window').height,
}}
/>
</View>
</View>
</TouchableWithoutFeedback>
</KeyboardAwareScrollView>
</ImageBackground>
);
}
const styles = StyleSheet.create({
container: {
backgroundColor: 'white',
},
content: {
flex: 1,
},
backgroundImage: {
flex: 1,
position: 'absolute',
top: 0,
bottom: 0,
left: 0,
right: 0,
width: Dimensions.get('window').width,
height: Dimensions.get('window').height,
},
backgroundContainer: {
justifyContent: 'flex-end',
flex: 1,
width: Dimensions.get('window').width,
height: Dimensions.get('window').height,
},
backgroundOverlay: {
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
},
logoContainer: {
top: '10%',
width: '100%',
},
backgroundLogo: {
alignSelf: 'center',
position: 'absolute',
marginTop: Dimensions.get('window').height/7, // top for rest of the screen to push logo down
width: 126,
height: 96,
},
dummyView: {
backgroundColor: 'red',
flex: 1,
},
loginContainer: {
borderTopEndRadius: 30,
borderTopStartRadius: 30,
width: '100%',
backgroundColor: 'white',
height: Dimensions.get('window').height/1.5, //container of form height
alignItems: 'center',
paddingTop: Dimensions.get('window').width * 0.1,
},
loginScreen: {
width: '80%',
backgroundColor: 'white',
},
});
Here is the output of code on android/ios:

Flex Layout with React Native

I'm having an issue where my container is collapsing on my child components which consists of a list of 4 images and one icon at the end of the container in a row. If I set the height explicitly then I can make it large enough to fit the content for one device. However, I have the images resizing themselves right now based off of width of the phone and a percent and then scaling to conserve aspect ratio. Is there a way to make my container grow just large enough to ensure it fits its child components when its child components scale based off of device width?
I thought that React Native flex default was to do this but for whatever reason my container is not fitting the images inside it but instead shrinking and all inside content is shrinking down except the images which lay over the top of my now shrunk containers.
export default function BadgeQuickView() {
return (
<View style={styles.badgeViewContainer}>
<View style={styles.badgeContainerTop}>
<View style={styles.badgeContainerLeftBorder}></View>
<Text style={styles.badgeContainerTitle}>FEATURED BADGES</Text>
<View style={styles.badgeContainerRightBorder}></View>
</View>
<View style={styles.quickBadgeList}>
{TEST_BADGE_ARRAY.map(option => (
<View style={styles.badgeInfoContainer} key={option.id}>
<Image style={styles.badgeImage} source={{uri: option.url}} fadeDuration={0} />
<Text style={styles.badgeDescText}>{option.name}</Text>
</View>
))}
<View style={styles.moreBadgesButtonContainer}>
<TouchableOpacity style={styles.moreBadgesButton}>
<Text style={styles.moreBadgesText}>All {`\n`} Badges</Text>
<Entypo name={'dots-three-horizontal'} size={moderateScale(20)} color={profileColors.editProfileText}/>
</TouchableOpacity>
</View>
</View>
</View>
);
}
const styles = StyleSheet.create({
badgeViewContainer: {
width: '100%',
height: moderateScale(130),
borderStyle: 'solid',
borderColor: userInterface.borderColor,
borderBottomWidth: 2,
backgroundColor: 'white',
paddingBottom: 5
},
moreBadgesText: {
color: 'black',
fontFamily: 'montserrat',
fontSize: moderateScale(8),
textAlign: 'center',
color: profileColors.editProfileText,
textAlign: 'center',
},
badgeDescText: {
color: 'black',
fontFamily: 'montserrat',
fontSize: moderateScale(8),
textAlign: 'center',
color: profileColors.editProfileText,
textAlign: 'center',
},
quickBadgeList: {
flex: 1,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-around',
paddingLeft: 15,
},
badgeImage: {
aspectRatio: 1,
width: '100%',
height: undefined,
resizeMode: 'contain',
marginBottom: 10,
},
moreBadgesButton: {
height: '100%',
width: '100%',
alignItems: 'center',
justifyContent: 'center',
textAlign: 'center'
},
moreBadgesButtonContainer: {
height: '100%',
width: '15%',
},
badgeInfoContainer: {
height: '100%',
width: '20%',
flexDirection: 'column',
padding: 5,
},
badgeContainerTop: {
flexDirection: 'row',
alignItems: 'center',
},
badgeContainerLeftBorder: {
borderStyle: 'solid',
borderColor: userInterface.borderColor,
borderTopWidth: 2,
width: '5%',
bottom: moderateScale(13) / 2,
},
badgeContainerTitle: {
fontFamily: 'montserrat-bold',
color: profileColors.editProfileText,
marginHorizontal: 10,
fontSize: moderateScale(13),
bottom: moderateScale(13) /2,
},
badgeContainerRightBorder: {
borderStyle: 'solid',
borderColor: userInterface.borderColor,
borderTopWidth: 2,
width: '100%',
bottom: moderateScale(13) / 2,
},
});
** Edit: Added current working code for reference. moderateScale(...) is just taking the screenHeight and scaling the height and working for most devices that I have tested thus far. I would like to not have to set this explicitly though if possible and instead have it expand based on its content.
Below is the desired and undesired looks. When I comment out the height: from the view container and set it to flex it won't expand to fit its content. When real images are added in the images show over the top of the bottom border and the text is not visible at all that will show under the red box typically. Added the red just as an indicator of where the image and text renders.

React Native - issue with flexDirection

I am trying to put a button (custom component made from TouchableOpacity inside View) and an image in a view.....and have them sit side by side.
See code below
<View
style={{
borderColor: 'black',
borderWidth: 1,
flex: 1,
flexDirection: 'row',
}}
>
<MyButton
to_extraStyle={{
backgroundColor: '#03A9F4',
width: 100,
height: 60,
alignSelf: 'flex-start',
}}
onPress={this._imagePickHandler}
>
CHOOSE PIC
</MyButton>
<Image
source={{ uri: this.state.localImgUrl }}
style={{
width: 100,
height: 60,
borderColor: 'black',
borderWidth: 1,
alignSelf: 'flex-end',
}}
/>
</View>
When I remove flexDirection: 'row' you can see both the image and the button....they sit on top of each other....but with flexDirection: 'row' included, the image disappears and you can only see the button.
Any ideas?
Use the value of width as %.
<View
style={{
borderColor: 'black',
borderWidth: 1,
flex: 1,
flexDirection: 'row'
}}
>
<MyButton
to_extraStyle={{
backgroundColor: '#03A9F4',
width: "50%",
height: 60,
}}
onPress={this._imagePickHandler}
>
CHOOSE PIC
</MyButton>
<Image
source={{ uri: this.state.localImgUrl }}
style={{
width: "50%",
height: 60,
borderColor: 'black',
borderWidth: 1,
}}
/>
</View>
Example
export default class App extends Component {
render() {
return (
<View style={s.container}
>
<TouchableOpacity style={s.touchable}>
<Text style={{color:"#ffffff"}}>Button </Text>
</TouchableOpacity>
<Image source={{uri: 'http://i.imgur.com/IGlBYaC.jpg'}} style={s.backgroundImage} />
</View>
);
}
}
const s = StyleSheet.create({
backgroundImage: {
width: "50%",
height: 60,
borderColor: 'black',
borderWidth: 1,
},
touchable: {
backgroundColor: '#03A9F4',
width: "50%",
height: 60,
justifyContent:"center",
alignItems:"center"
},
container: {
borderColor: 'white',
borderWidth: 1,
flex: 1,
flexDirection: 'row'
}
});
Remove alignSelf on both MyButton and Image and they will sit side by side.
If what you wanted to obtain with alignSelf is to align MyButton at the very left and Image at the very right then add justifyContent: 'space-between' to your view.

Image overlapping in react native

I am using absolute position to overlap a component over an image. There is another component after it which is a view with an orange colored background. It goes behind the absolute positioned component. How can keep it after the absolute positioned component (the height of this component might vary so I cannot use margin or height etc here)?
Have a look at the snack: https://snack.expo.io/#codebyte99/overlap-test
export default class App extends React.Component {
render() {
return (
<View style={styles.container}>
<Image
source={{
uri: 'https://facebook.github.io/react/logo-og.png',
cache: 'only-if-cached',
}}
style={{width: 400, height: 100}}
/>
<View style={styles.overlap}>
<Text>Event1</Text>
<Text>Event2</Text>
<Text>Event3</Text>
<Text>Event4</Text>
<Text>Event5</Text>
<Text>Event6</Text>
<Text>Event7</Text>
</View>
<View style={{ backgroundColor: 'orange', height: 200, width: 500 }}/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
paddingTop: Constants.statusBarHeight,
backgroundColor: '#ecf0f1',
padding: 8,
},
overlap: {
width: 300,
backgroundColor: 'red',
borderColor: 'red',
borderWidth: 1,
position: 'absolute',
top: 80,
zIndex: 9999,
overflow: 'visible'
},
});
How it looks now:
what I want:
Do the orange and red box in one container and set this to an absolute position:
<View style={{position: "absolute", flexDirection:"column"}}
<View style={styles.YourStyleWithoutPositionAbsolute}>
<Text>Event1</Text>
<Text>Event2</Text>
<Text>Event3</Text>
<Text>Event4</Text>
<Text>Event5</Text>
<Text>Event6</Text>
<Text>Event7</Text>
</View>
<View style={{ backgroundColor: 'orange', height: 200, width: 500 }}/>
</View>

React Native Flex Box Align

I am attempting to align an image within and ImageBackground component though no matter what I do, it doesn't appear to follow the flexbox rules.
Below is the desired result
But I am currently getting
Below is the code I have used
render () {
console.log(this.props.data.attributes.main_image_url)
return (
<TouchableOpacity onPress={this._handlePress} style={styles.container}>
<ImageBackground source={{uri: "https:"+this.props.data.attributes.main_image_url}} style={styles.background}>
<View style={styles.logoContainer}>
<Image
// defaultSource={require('../Images/logo-placeholder.png')}
source={{uri: "https:"+this.props.data.attributes.logo_url}}
resizeMode="contain"
style={styles.logo}
/>
</View>
</ImageBackground>
</TouchableOpacity>
)
}
Styles
container: {
marginBottom: Metrics.doubleSection,
flex: 1,
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'stretch',
},
background: {
height: Metrics.screenWidth / 2,
width: Metrics.screenWidth-(Metrics.baseMargin*2),
margin: Metrics.baseMargin
},
logoContainer: {
backgroundColor: Colors.coal,
justifyContent: 'flex-end',
alignItems: 'left',
left: Metrics.doubleBaseMargin,
height: Metrics.images.xl,
width: Metrics.images.xl
},
logo: {
height: Metrics.images.xl,
width: Metrics.images.xl
}
Change your logoContainer style as below set position:'absolute' and set bottom:negative value:
logoContainer: {
backgroundColor: Colors.coal,
justifyContent: 'flex-end',
alignItems: 'flex-start',
position:'absolute', // set position to absolute
bottom:-10, //give relative value here
left: Metrics.doubleBaseMargin,
height: Metrics.images.xl,
width: Metrics.images.xl
},