Vertically center image that's not affected by KeyboardAwareScrollView in React Native - 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:

Related

Place Item to Bottom React Native

I'm creating a react native app i have a item - a plus button(shown in image)
I want this to the bottom of my page onto footer -
but the problem is that when i place this button on bottom with position: 'absolute', bottom: 0 it's only going to the bottom of the hero section & not to the footer
Code -
<View style={styles.main}>
<View style={styles.hero}>
<View style={styles.flex}>
<View style={styles.container}>
</View>
</View>
<View style={styles.flex2}>
</TouchableOpacity>
</View>
<View style={styles.container12}>
</View>
</View>
</View>
<TouchableOpacity>
<View style={styles.container33}>
<EntypoIcon name="plus" style={styles.icon28}></EntypoIcon>
</View>
</TouchableOpacity>
Main Styles -
main: {
width: '100%',
height: '100%',
},
hero: {
alignSelf: 'stretch',
height: 250,
backgroundColor: '#fff',
borderBottomLeftRadius: 50,
borderBottomRightRadius: 50,
},
container33: {
width: 50,
height: 50,
backgroundColor: '#1DA6FA',
// flex: 1,
borderRadius: 50,
position: 'absolute',
bottom: 0,
right: 10,
alignItems: 'center',
justifyContent: 'center'
},
The problem si that you are applying the absolute css position to the View inside the TouchableOpacity and not to the TouchableOpacity itself.
To achieve what you want to do, you should write something like:
<View style={styles.absoluteOpacity}>
<TouchableOpacity>
<View style={styles.container33}>
<EntypoIcon name="plus" style={styles.icon28}></EntypoIcon>
</View>
</TouchableOpacity>
</View>
And then the css:
container33: {
width: 50,
height: 50,
backgroundColor: '#1DA6FA',
// flex: 1,
borderRadius: 50,
right: 10,
alignItems: 'center',
justifyContent: 'center'
},
absoluteOpacity: {
position: 'absolute',
bottom: 0,
}

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
},

Component won't stay wrapped within bounds (React Native)

I am trying to make my own tab navigation like Instagram has instagram example. I noticed that the items within the footer view I made does not stay within the bounds and moves to the top left of the screen. Images shown - What I am trying to achieve What happens frequently
Here is the most important part of my code. I have done inline styles temporarily to try and figure out the issue. Here is the part where the issue lays <View style = {{ width: Dimensions.get('window').width, backgroundColor: '#fff', height: 80}} <TouchableOpacity onPress = {() => this.props.navigation.navigate('Profile')} style = {{justifyContent: 'flex-end'}}>
<Text style = {{justifyContent: 'flex-end'}}> Profile </Text>
</TouchableOpacity> </View>-
render() {
return (
<MapView style = {[style.container, style.map]}
region = {this.state.initialPosition}>
<MapView.Marker coordinate = {this.state.markerPosition}>
<View style = {style.radius}>
<View style = {style.marker}/>
</View>
</MapView.Marker>
<View style = {{ width: Dimensions.get('window').width, backgroundColor: '#fff', height: 80}} >
<TouchableOpacity onPress = {() => this.props.navigation.navigate('Profile')} style = {{justifyContent: 'flex-end'}}>
<Text style = {{justifyContent: 'flex-end'}}> Profile </Text>
</TouchableOpacity>
</View>
</MapView>
);
}
}
const style = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'flex-end',
//alignItems: 'center',
},
text: {
color: '#fff'
},
map: {
left: 0,
right: 0,
top: 0,
bottom: 0,
position: 'absolute'
},
radius:{
height: 50,
width: 50,
borderRadius: 50 / 2,
overflow: 'hidden',
backgroundColor: 'rgba(1,213,106,0.3)',
borderWidth: 1,
borderColor: 'rgba(1,213,106,0.3)',
alignItems:'center',
justifyContent: 'center'
},
marker: {
height: 20,
width: 20,
borderWidth: 3,
borderColor: '#fff',
borderRadius: 20 / 2,
borderWidth: 3,
overflow: 'hidden',
backgroundColor: '#01D56A'
},
menuSection: {
backgroundColor: 'rgba(255, 255, 255, 0.7)',
width: Dimensions.get('window').width,
height: Dimensions.get('window').height/3,
justifyContent: 'flex-end',
alignItems: 'center'
},
icon:{
width: 50,
height: 50,
backgroundColor: 'black'
}
})

In React Native, how do I put a view on top of another view, with part of it lying outside the bounds of the view behind?

I'm trying to make a layout as per below with React Native.
How do I specify the position of B relative to A?
With iOS Interface Builder and autoconstraints, this can very explicitly be done and is a breeze. It's not so obvious how one might achieve this with React Native.
Add the following style to the "floating" view:
position: 'absolute'
You may also need to add a top and left value for positioning.
The above solutions were not working for me. I solved it by creating a View with the same background colour as the parent and added negative margin to move the image upwards.
<ScrollView style={{ backgroundColor: 'blue' }}>
<View
style={{
width: '95%',
paddingLeft: '5%',
marginTop: 80,
height: 800,
}}>
<View style={{ backgroundColor: 'white' }}>
<Thumbnail square large source={{uri: uri}} style={{ marginTop: -30 }}/>
<Text>Some Text</Text>
</View>
</View>
</ScrollView>
and I got the following result.
You can use zIndex for placing a view on top of another. It works like the CSS z-index property - components with a larger zIndex will render on top.
You can refer: Layout Props
Snippet:
<ScrollView>
<StatusBar backgroundColor="black" barStyle="light-content" />
<Image style={styles.headerImage} source={{ uri: "http://www.artwallpaperhi.com/thumbnails/detail/20140814/cityscapes%20buildings%20hong%20kong_www.artwallpaperhi.com_18.jpg" }}>
<View style={styles.back}>
<TouchableOpacity>
<Icons name="arrow-back" size={25} color="#ffffff" />
</TouchableOpacity>
</View>
<Image style={styles.subHeaderImage} borderRadius={55} source={{ uri: "https://upload.wikimedia.org/wikipedia/commons/thumb/1/14/Albert_Einstein_1947.jpg/220px-Albert_Einstein_1947.jpg" }} />
</Image>
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "white"
},
headerImage: {
height: height(150),
width: deviceWidth
},
subHeaderImage: {
height: 110,
width: 110,
marginTop: height(35),
marginLeft: width(25),
borderColor: "white",
borderWidth: 2,
zIndex: 5
},
You can use this OverlayContainer. The trick is to use absolute with 100% size. Check below an example:
// #flow
import React from 'react'
import { View, StyleSheet } from 'react-native'
type Props = {
behind: React.Component,
front: React.Component,
under: React.Component
}
// Show something on top of other
export default class OverlayContainer extends React.Component<Props> {
render() {
const { behind, front, under } = this.props
return (
<View style={styles.container}>
<View style={styles.center}>
<View style={styles.behind}>
{behind}
</View>
{front}
</View>
{under}
</View>
)
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
height: '100%',
justifyContent: 'center',
},
center: {
width: '100%',
height: '100%',
alignItems: 'center',
justifyContent: 'center',
},
behind: {
alignItems: 'center',
justifyContent: 'center',
position: 'absolute',
left: 0,
top: 0,
width: '100%',
height: '100%'
}
})
import React, {Component} from 'react';
import {StyleSheet, View} from 'react-native';
export default class App extends Component {
render() {
return (
<View>// you need to wrap the two Views an another View
<View style={styles.box1}></View>
<View style={styles.box2}></View>
</View>
);
}
}
const styles = StyleSheet.create({
box1:{
height:100,
width:100,
backgroundColor:'red'
},
box2:{
height:100,
width:100,
backgroundColor:'green',
position: 'absolute',
top:10,
left:30
},
});
You can use react-native-view-overflow plugin for placing a view on top of another. It works like the CSS z-index property.
import ViewOverflow from 'react-native-view-overflow';
<ViewOverflow />
<View style={[styles2.cardBox, { marginTop: 50 }]}>
<View style={[styles2.cardItem]} >
<Text style={styles2.cardHeader}>userList</Text>
</View>
<View style={[styles2.cardContent]}>
<Text style={styles2.text}>overflow: "visible"</Text>
</View>
<View style={[styles2.cardItemFooter]} >
<Text style={styles2.cardTextFooter}>...</Text>
</View>
</View>
</ViewOverflow>
const styles2 = StyleSheet.create({
cardBox: {
borderLeftWidth: 0,
borderTopWidth: 0,
backgroundColor: "transparent",
borderWidth: 1,
borderColor: "#d0d0d0",
width: '94%',
alignSelf: 'center',
height: 200,
position: "relative",
borderRadius: 15,
overflow: "visible" // doesn't do anything
},
cardContent: {
textAlign: "right",
backgroundColor: "transparent",
marginTop: 15,
alignSelf: 'flex-end',
padding: 5,
},
cardHeader: {
color: '#fff',
fontFamily: 'Vazir',
fontSize: 12
},
cardItem: {
backgroundColor: "#3c4252",
borderRadius: 3,
position: "absolute",
top: -10,
right: -5,
width: 50,
height: 20,
paddingRight: 5,
},
})
The easiest way to achieve this is with a negative margin.
const deviceWidth = RN.Dimensions.get('window').width
a: {
alignItems: 'center',
backgroundColor: 'blue',
width: deviceWidth,
},
b: {
marginTop: -16,
marginStart: 20,
},
You can use elevation property for Android if you don't mind the shadow.
{
elevation:1
}
Try this:
style = {{position: 'absolute', bottom: 20, left: 20, elevation: 100}}
Based on the example above i've created a component which stacks all childeren on top of each other. You could even nest OverlayContainers inside OverlayContainers.
Usage:
<OverlayContainer>
<View style={{backgroundColor:'red', width:150, height: 150}}></View>
<View style={{backgroundColor:'yellow', width:50, height: 50}}></View>
<Text>Just some text</Text>
</OverlayContainer>
Output:
import React, { FC, PropsWithChildren } from 'react'
import { StyleSheet, View } from 'react-native'
export const OverlayContainer: FC<PropsWithChildren<unknown>> = (props) => {
return (
<View style={styles.container}>
{props.children.map((child, index) => (
<View style={styles.child} key={index}>
{child}
</View>
))}
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
height: '100%',
},
child: {
position: 'absolute',
left: 0,
top: 0,
width: '100%',
height: '100%'
}
})
<SafeAreaView style={{ flex: 1 }} >
<View style={{ height: Dimensions.get('window').height / 2, backgroundColor: 'blue', justifyContent: 'center' }}>
<Text style={{ fontSize: 25, alignSelf: 'center' }} >A</Text>
<View style={{ justifyContent: 'center', height: 100, width: 100, backgroundColor: 'yellow', position: 'absolute', left: 20, top: Dimensions.get('window').height / 2 - 70 }}>
<Text style={{ fontSize: 22, alignSelf: 'center' }} >B</Text>
</View>
</View>
</SafeAreaView>