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

Viewed 333

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:

desired result

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:

current 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:

2 Answers

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:

Here is the output of code on android/ios

Related