键盘避免视图无法正常工作

键盘避免视图无法正常工作

我试图使用键盘避免与 behavior="padding"视图。

由于某种原因,当我试图在 TextInput中输入任何文本时,TextInput 下面有一个空格。附件是正在发生的事情以及代码的图片。有人知道这里发生了什么吗?

Whats-App-Image-2018-01-24-at-5-07-46-PM

  render() {
return (


<KeyboardAvoidingView  style={{ flex: 1}}  behavior="padding">
< View
style={{
flex: 1,
           

backgroundColor: "#FFFFFF",
         

}}
>
        

<ScrollView
contentContainerStyle={{ justifyContent: "flex-end", flex: 1 }}>
<ChatInfo />
</ScrollView>


        

<View style={styles.container}>
<TextInput
style={styles.input}
underlineColorAndroid="transparent"
autoCapitalize="none"
onChangeText={text => this.setState({ text: text })}
value={this.state.text}
/>


<TouchableOpacity
style={styles.submitButton}
onPress={this.submitName}
>
<Text style={styles.submitButtonText}> SEND </Text>
</TouchableOpacity>
</View>
       

</ View>
</KeyboardAvoidingView>
);
}
}


export default connect()(ChatScreen);


const styles = StyleSheet.create({
input: {
margin: 2,
paddingLeft: 15,
flex: 1,
height: 40,
padding: 10,
fontSize: 14,
fontWeight: "400"
},


container: {
borderTopWidth: 1,
minWidth: "100%",
borderColor: "#cccccc",
height: 44,
flexDirection: "row",
justifyContent: "space-between",
backgroundColor: "#fff"
        

},


submitButtonText: {
color: "#0a9ffc",
fontSize: 14,
fontWeight: "500"
},


submitButton: {
backgroundColor: "#fff",
padding: 10,
margin: 2,
height: 40,
alignItems: "center",
justifyContent: "center"
}
});
136627 次浏览

This is a known issue with KeyboardAvoidingView and Android. There are multiple ways to address this issue.

React Native documentation says:

Android may behave better when given no behavior prop at all, whereas iOS is the opposite.

So, if you are working only with Android you may remove behavior prop and it should work straight away. For best results add android:windowSoftInputMode="adjustResize" to your Manifest.

Alternatively you can give an offset value that works for you something like this: KeyboardAvoidingView keyboardVerticalOffset={-500} behavior="padding"

For ios do the same thing conditionally:

behavior= {(Platform.OS === 'ios')? "padding" : null}

keyboardVerticalOffset={Platform.select({ios: 0, android: 500})}

The KeyboardAvoidingView must be a ScrollView child, not the other way around. This way it behaves normal(normal for what purpose I am using it). Try it and let me know how it went.

<ScrollView>
<KeyboardAvoidingView styles={styles.container} behavior='padding'>


</KeyboardAvoidingView>
</ScrollView>

If you are using react-navigation, this is affected by the header of the react-navigation. The height of the header is vary on different mobile screen. So you have to get the height of the header and pass into the keyboardVerticalOffset props.

import * as React from 'react'
import { KeyboardAvoidingView } from 'react-native'
import { useHeaderHeight } from '@react-navigation/elements'


type Props = {
children: React.ReactNode
}


export const KeyboardShift = ({ children }: Props) => {
const height = useHeaderHeight()


return (
<KeyboardAvoidingView
keyboardVerticalOffset={height + 47}
behavior="padding"
style=\{\{ flex: 1 }}
enabled>
{children}
</KeyboardAvoidingView>
)
}

WARNING

This appears to be only a partial solution, although it works initially, if the android phone is locked on the screen with the keyboard avoiding layout, when you unlock you end up with the extra padding above the keyboard again.

tl;dr

Remove android:windowSoftInputMode="adjustResize" from the AndroidManifest.xml

Before

...
<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
android:windowSoftInputMode="adjustResize"
>
...

After

...
<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
>
...

Why

If I understand the issue correctly, I have been dealing with the same thing. By having android:windowSoftInputMode="adjustResize" in the manifest, the android system will try to do the same job as the KeyboardAvoidingView. This results in extra spacing being added above the keyboard on Android only.

If working on both platforms you are going to have to deal with this on iOS every time you are working with keyboard input, so best to remove the android specific behaviour by android:windowSoftInputMode="adjustResize" from the manifest and using the KeyboardAvoidingView every time.

<KeyboardAvoidingView styles={styles.container} behavior = 'padding'  enabled>
<ScrollView>


<View>
....
</View>


</ScrollView>
</KeyboardAvoidingView>

I think this is because the behavior props value, so I think adding this line in the keyboardavoidview will help

<KeyboardAvoidingView
style = \{\{ flex: 1 }}
behavior={Platform.OS === "ios" ? "padding" : null}>
</KeyboardAvoidingView>
 <KeyboardAvoidingView style={styles.keyboardcontainer} behavior="padding"
keyboardVerticalOffset={Platform.select({ios :120, android : 500})}
enabled>


<View  style=\{\{flex: 1 }}>
// Your Code
</View>


</KeyboardAvoidingView>

Many answers here have shown a conditional behavior prop value. Like this.

// incorrect 👎
<KeyboardAvoidingView
style = \{\{ flex: 1 }}
behavior={Platform.OS === "ios" ? "padding" : null}>
</KeyboardAvoidingView>

But this sets the behavior prop to null on Android.

The documentation says...

Android and iOS both interact with this prop differently.
Android may behave better when given no behavior prop at all, whereas iOS is the opposite.

Conditional spreading the behavior prop provides an exact solution.
It adds the prop on iOS and leaves it out on Android.

// correct 👍
<KeyboardAvoidingView
style = \{\{ flex: 1 }}
{...(Platform.OS === 'ios' && { behavior: 'padding' })}
</KeyboardAvoidingView>

And here's a solution if using styled-components that doesn't use an unnecessary KeyboardAvoidingView on Android.

import { KeyboardAvoidingView as Kav, Platform, View } from 'react-native';
import styled from 'styled-components/native';


// If ios we change the component type and, via the `attrs` method, add a behavior prop. This
// approach leaves Android alone. Because it already works.
export const ScreenContainer = styled(Platform.OS === 'ios' ? Kav : View).attrs({
behavior: Platform.OS === 'ios' && 'padding',
})`
flex: 1;
`;

The main issue with KeyboardAvoidingView is that; the understanding of how it works is missing.

I found below link very helpful

https://medium.com/@nickyang0501/keyboardavoidingview-not-working-properly-c413c0a200d4

  1. First thing, use of flex:1 in KeyboardAvoidingView and behavior: 'padding'
  2. Next is use of flex:1 in "MainView" which needs to go inside KeyboardAvoidingView
  3. Last is adding justifyContent: "flex-end" to ""MainView""

Hope it helps

My issue was with the keyboardHidesTabBar option. The following setup worked for me:

const AppBottomTabNavigator = createBottomTabNavigator(
{
...
},
{
tabBarOptions: {
keyboardHidesTabBar: Platform.OS !== 'ios',
},
},
);

Component:

import React from 'react';
import {
Keyboard,
KeyboardAvoidingView,
Platform,
StyleSheet,
Text,
TextInput,
TouchableWithoutFeedback,
View,
} from 'react-native';
import { Header } from 'react-navigation-stack';


const styles = StyleSheet.create({
container: {
flex: 1,
},
center: {
justifyContent: 'center',
alignItems: 'center',
},
textInput: {
height: 40,
borderColor: 'gray',
borderWidth: 1,
},
});


const MyScreen = () => {
return (
<KeyboardAvoidingView
behavior={Platform.OS === 'ios' ? 'padding' : null}
keyboardVerticalOffset={Header.HEIGHT}
style={styles.container}
>
<TouchableWithoutFeedback onPress={Keyboard.dismiss}>
<View style={styles.container}>
<View style={[styles.container, styles.center]}>
<Text>Hello!</Text>
</View>
<TextInput style={styles.textInput} placeholder="Message" />
</View>
</TouchableWithoutFeedback>
</KeyboardAvoidingView>
);
};


export default MyScreen;
<KeyboardAvoidingView behavior={Platform.OS === "ios" ? "padding" : null}
style=\{\{flex: 1 }}>

In the above snippet, flex is set to 1, which renders the text Input field just above the keyboard by default even when the keyboard isn't opened. And, when the keyboard pops up, the input field is further pushed up by a factor of keyboard height's offset.

Setting flex to 0 shall fix the issue, as it did in my case.

 <KeyboardAvoidingView behavior={Platform.OS === "ios" ? "padding" : null}
style=\{\{flex: 0 }}>
<View>
...
</View>
</KeyboardAvoidingView>

My problem was not checking this platform type

adding the code below to KeyboardAvoidView fixed it for me

behavior={Platform.OS === "ios" ? "padding" : 'height'}

Although Its not an right answer but there is very popular library for solving this type of issues is there called where it can use scrollview with the sense of keyboard area available.You can go through the below link https://www.npmjs.com/package/react-native-keyboard-aware-scrollview

I think the best approach is to create a HOC for this, in addition ,by using getBottomSpace from react-native-iphone-x-helper you can solve overlapping issue for IPhone X and..

 import React, { ComponentType, ReactNode } from 'react';
import { Platform, KeyboardAvoidingView, View, Pressable, Keyboard } from
'react-native';
import { getBottomSpace } from 'react-native-iphone-x-helper';


interface IProps {
children: ReactNode;
}


const KeyboardAvoidingViewHoc = (Component: ComponentType) => {
return ({ children, ...props }: IProps) => {
return (
<KeyboardAvoidingView  {...props} keyboardVerticalOffset=
{getBottomSpace()} behavior= {Platform.OS === 'ios' ? 'padding' : undefined}>
<Pressable onPress={Keyboard.dismiss}>
<Component {...props}>{children}</Component>
</Pressable>
</KeyboardAvoidingView>
);
};
};


export const AvoidKeyboardAvoidingViewHoc = KeyboardAvoidingViewHoc(View);

I had a similar issue because I'm using @react-navigation with bottom tabs.

Starting with "@react-navigation/bottom-tabs": "^5.11.2" you can get the height of the bottom tab bar with one of the following two ways (API reference):

import { BottomTabBarHeightContext } from '@react-navigation/bottom-tabs';


// ...


<BottomTabBarHeightContext.Consumer>
{tabBarHeight => (
/* render something */
)}
</BottomTabBarHeightContext.Consumer>

or

import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs';


// ...


const tabBarHeight = useBottomTabBarHeight();

and then you set it as offset to your KeyboardAvoidingView:

<KeyboardAvoidingView keyboardVerticalOffset={tabBarHeight} behavior={Platform.OS === "ios" ? "padding" : null} style={styles.container}>
// ...
</KeyboardAvoidingView>

After applying ScrollView the problem was solved for me.

Keep in mind is always your top Parent with flex:1 then the child is then you text input container if you use this method it work always its test method.

<KeyboardAvoidingView style=\{\{  flex: 1 }}
behavior={Platform.OS === "ios" ? "position" : null} enabled>
<ScrollView>
<View>
<View >
<Text maxFontSizeMultiplier={1.5} >
Sign in to your account{" "}
</Text>
<View
behavior="padding"
enabled
>
<TextInput
placeholder="Email address"
placeholderTextColor={Colors.grey}
style={styles.textInput}
onChangeText={(e) => setEmail(e.trim())}
autoCapitalize="none"
returnKeyType={"done"}
/>
</View>
</View>
</ScrollView>
</KeyboardAvoidingView>

For React Native Navigation v5, you can use the following:

import { useHeaderHeight } from '@react-navigation/stack';


...


const Component = () => (


<KeyboardAvoidingView
keyboardVerticalOffset={ useHeaderHeight() } // <-- for v5
behavior="padding"
style=\{\{ flex: 1 }}
>
<TextInput
style=\{\{ height: 30, width: "100%, borderWidth: 1 }}
/>
</KeyboardAvoidingView


)

https://reactnavigation.org/docs/stack-navigator#headertransparent

For anyone coming here in 2021, a few things for an updated answer:

  • useHeaderHeight is no longer exported by @react-navigation/stack, it is in @react-navigation/elements.

  • React Native also now recommends setting the behavior prop for both iOS and Android. So the full solution would be:

import { useHeaderHeight } from '@react-navigation/elements';


export const MyComponent = () => {
const headerHeight = useHeaderHeight();
return (
<KeyboardAvoidingView
keyboardVerticalOffset={headerHeight}
style={style.container}
behavior={Platform.OS === "ios" ? "padding" : "height"}
>
{/* rest of your component */}
</KeyboardAvoidingView>
);
}

I didn't have to add any additional value to headerHeight in keyboardVerticalOffset, and it's working great on iOS and Android.

EDIT: The problem now goes deeper, as KeyboardAvoidingView does not support all types of Android keyboards. The solution must rely on opening the keyboard to a specific element then (for example a text input). This custom component would look like this:

import React, { PropsWithChildren, useEffect, useState } from 'react';
import { Platform, Animated, Dimensions, Keyboard, KeyboardAvoidingView, StyleSheet, TextInput } from 'react-native';
import {useHeaderHeight} from '@react-navigation/elements';
import { useKeyboard } from '@react-native-community/hooks';


export default function KeyboardShift (props: PropsWithChildren<{}>) {
const [shift, setShift] = useState(new Animated.Value(0))
const keyboard = useKeyboard()


// On mount, add keyboard show and hide listeners
// On unmount, remove them
useEffect(() => {
Keyboard.addListener('keyboardDidShow', handleKeyboardDidShow);
Keyboard.addListener('keyboardDidHide', handleKeyboardDidHide);
return () => {
Keyboard.removeAllListeners('keyboardDidShow');
Keyboard.removeAllListeners('keyboardDidHide');
}
}, [])


const handleKeyboardDidShow = () => {
const { height: windowHeight } = Dimensions.get('window');
const keyboardHeight = keyboard.keyboardHeight;
const currentlyFocusedInputRef = TextInput.State.currentlyFocusedInput();
currentlyFocusedInputRef.measure((x, y, width, height, pageX, pageY) => {
const fieldHeight = height;
const fieldTop = pageY;
const gap = (windowHeight - keyboardHeight) - (fieldTop + fieldHeight);
if (gap >= 0) {
return;
}
Animated.timing(
shift,
{
toValue: gap,
duration: 1000,
useNativeDriver: true,
}
).start();
})
}


const handleKeyboardDidHide = () => {
Animated.timing(
shift,
{
toValue: 0,
duration: 1000,
useNativeDriver: true,
}
).start();
}


const { children } = props;


// Android: we need an animated view since the keyboard style can vary widely
// And React Native's KeyboardAvoidingView isn't always reliable
if (Platform.OS === 'android') {
return (
<Animated.View style={[styles.container, { transform: [{translateY: shift}] }]}>
{children}
</Animated.View>
);
}


// iOS: React Native's KeyboardAvoidingView with header offset and
// behavior 'padding' works fine on all ios devices (and keyboard types)
const headerHeight = useHeaderHeight();
return (
<KeyboardAvoidingView
keyboardVerticalOffset={headerHeight}
style={styles.container}
behavior={'padding'}>
{children}
</KeyboardAvoidingView>
)
}


const styles = StyleSheet.create({
container: {
flex: 1
}
});

Yes, it's unfortunately long-winded, but it gets the job done for all types of phones on both iOS and Android.

Read more about it here.

SECOND EDIT: As of April 22nd, 2022, I've replaced the complex solution above the following:

Step 1:

behavior={Platform.OS === "ios" ? "padding" : undefined}

Step 2:

Be sure to REMOVE

android:windowSoftInputMode="adjustPan"

(Or any windowSoftInputMode for that matter) from your AndroidManifest.xml file.

So far this solution has been working as well (and is way less code - always better, right?)

UPDATE: 2021(October)

If you are using react-navigation(currently, I'm using v6), import useHeaderHeight hook(require additional dependencies)

If you want to use ScrollView with KeyboardAvoidingView, you have to wrap ScrollView inside KeyboardAvoidingView(see example below)

NOTE: If your screen have bottomTabsNavigation, be sure to DISABLE on iOS platform tabBarHideOnKeyboard: Platform.OS !== 'ios'

   const headerHeight = useHeaderHeight();
    

<KeyboardAvoidingView
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
style=\{\{flex: 1}}
keyboardVerticalOffset={headerHeight}>
<ScrollView style=\{\{flex: 1}} contentContainerStyle=\{\{flexWrap: 1}}>
...content
</ScrollView>
    

</KeyboardAvoidingView/>

import { HeaderHeightContext } from "react-navigation-stack"; ..., KeyboardAvoidingView, } from "react-native";

<HeaderHeightContext.Consumer>
{(headerHeight) => (
<KeyboardAvoidingView
{...(Platform.OS === "ios" && {
behavior: "padding",
keyboardVerticalOffset: headerHeight
})}
style=\{\{ flex: 1 }}>


code is here


</KeyboardAvoidingView>

)} </HeaderHeightContext.Consumer> enter image description here

enter image description here

A handy alternative when wanting to avoid the keyboard with a ScrollView is the popular react-native-keyboard-aware-scroll-view package:

https://github.com/APSL/react-native-keyboard-aware-scroll-view

import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view'


<KeyboardAwareScrollView>
<View>
<TextInput />
</View>
</KeyboardAwareScrollView>

I went through most of these comments and there are 2 major real life scenarios here:

  1. Chat - the keyboard must avoid the message input and push the conversation content above.

import { useHeaderHeight } from "@react-navigation/elements"
import {
Keyboard,
Platform,
TouchableWithoutFeedback,
View,
KeyboardAvoidingView
} from "react-native"


const Chat = () => {
// This is the crucial variable we will place it in
// KeyboardAvoidingView -> keyboardVerticalOffset
const headerHeight = useHeaderHeight()
    

return (
<TouchableWithoutFeedback onPress={Keyboard.dismiss}>
<KeyboardAvoidingView
style=\{\{ position: "absolute", bottom: 0, left: 0, right: 0 }}
behavior={Platform.OS === "ios" ? "padding" : "height"}
// If you want the input not to stick exactly to the keyboard
// add a const here for example headerHeight + 20
keyboardVerticalOffset={headerHeight}
>
<ScrollView style=\{\{ flex: 1 }}>
// Messages here
<ScrollView />
<View style=\{\{ flex: 1, justifyContent: "flex-end" }}>
<InputWrapper>
<RawInput />
</InputWrapper>
</View>
</KeyboardAvoidingView>
</TouchableWithoutFeedback>
)
}

  1. Form - https://medium.com/@nickyang0501/keyboardavoidingview-not-working-properly-c413c0a200d4 this article is a really good example how to handle it with the form. In order to make it even better use the approach I used for chat.
const headerHeight = useHeaderHeight()


<KeyboardAvoidingView
style=\{\{ position: "absolute", bottom: 0, left: 0, right: 0 }}
behavior={Platform.OS === "ios" ? "padding" : "height"}
keyboardVerticalOffset={headerHeight}
>
....
</KeyboardAvoidingView>

What worked for me in 2022

The problem I was having (on Android devices only) was that when I tapped on an input text field, the keyboard would show. Upon dismissing the keyboard, either by tapping the tick icon on it or tapping anywhere outside the keyboard, the <KeyboardAvoidingView /> component (part of React Native) would not resize properly, so the screen was half the height (device height minus keyboard height).

Various approaches worked but quite poorly, until I took this approach. Below, is a code snippet of how I put this together.

import React, { useEffect } from 'react';
import {
KeyboardAvoidingView,
NativeModules,
Platform,
TextInput,
} from 'react-native';


const isIos: boolean = Platform.OS === 'ios';
const isAndroid: boolean = Platform.OS === 'android';


export const MyComponent = (): JSX.Element => {


useEffect((): (() => void) => {
if (isAndroid) {
NativeModules.AndroidUtils.setWindowAdjustResize()
}


return (): void => {
if (isAndroid) {
NativeModules.AndroidUtils.setWindowAdjustPan()
}
}
}, [])




return (
<KeyboardAvoidingView
behavior={isIos ? 'padding' : undefined}
style=\{\{ flex: 1 }}
>
<ScrollView>
<View style=\{\{ paddingTop: 300 }}>
<TextInput />
</View>
</ScrollView>
</KeyboardAvoidingView>
);
}


Other info

  • using React Native 0.68.3 with Wix React Native Navigation
  • android:windowSoftInputMode="adjustPan" has NOT been removed from .MainActivity inside the AndroidManifest.xml file.