Reputation: 3453
I have made one custom animated bottom sheet. It has two snap points. It starts top of the screen, if scroll the Animated card down it will come middle of the screen , again scroll down the card it will come bottom of the screen. If user scroll the Animated Card hard down then it will go all the way bottom. I made this component as a re-useable component.
I have made re-useable Search component.
I imported both of the components to the main-app component. Inside the Animated-Bottom-sheet I put the Search-component.
I have made condition for keyboard and Animated.View
If the Animated view move from middle to bottom of the screen and search onFocus(keyboard appear) then Keyboard will dismiss.
If Animated View is Bottom of the screen and search input onfocus
(keyboard appear) then it will move the Animated View middle of the screen. For this logic, I made Keyboard.addListener('keyboardDidHide',()=>{....})
My this logic works perfectly in IOS but in Android, it push all elements top of the screen.
There is lots of suggestion about KeyboardAvoidingView but in my case, it does not work.
In react-native's android's AndroidManifest.xml file. I made android:windowSoftInputMode="adjustPan"
and it listen the Keyboard.addListener('keyboardDidHide'
and push all the elements top of the screen, if I make android:windowSoftInputMode="adjustPan|adjustResize"
then it does not listen the Event-lister
. As a result, My logic does not work and it hides the Animated View under Android Keyboard. According to RN-documentation, Event-lister only listen adjustResize or adjustPan
.
I REALLY DON'T KNOW HOW TO FIX THIS ISSUE
This is is my Animated View component
import React, { useState } from "react";
import { StyleSheet, Dimensions, Platform, View, Keyboard } from "react-native";
import {
PanGestureHandler,
PanGestureHandlerGestureEvent,
} from "react-native-gesture-handler";
import Animated, {
useAnimatedGestureHandler,
useAnimatedStyle,
useSharedValue,
withTiming,
withSpring,
runOnJS,
Easing,
} from "react-native-reanimated";
import styled from "styled-components/native";
interface Props {
children: React.ReactNode;
}
const { height: SCREEN_HEIGHT } = Dimensions.get("screen");
const IPHONE_DEVICE_MIDDLE_SCREEN = Platform.OS === "ios" ? 4 : 4;
const IPHONE_DEVICE_BOTTOM_SCREEN = Platform.OS === "ios" ? 7.6 : 7.36;
const ANIMATED_DURATION = 300;
const LoadingContainer = styled.View`
height: ${SCREEN_HEIGHT - 300}px;
background-color: #fff;
justify-content: center;
`;
const ScrollBottomSheet = ({ children }: Props) => {
const contentTop = useSharedValue(SCREEN_HEIGHT);
const [bottomSheetState, setBottomSheetState] = useState("top");
// Event-listener
Keyboard.addListener("keyboardDidShow", () => {
if (bottomSheetState === "bottom") {
contentTop.value = withSpring(
SCREEN_HEIGHT * IPHONE_DEVICE_MIDDLE_SCREEN
);
}
});
const animatedStyle = useAnimatedStyle(() => {
"worklet";
return {
top: contentTop.value * 0.1,
bottom: 0,
};
});
const gestureHandler = useAnimatedGestureHandler(
{
onStart(_, context) {
context.translateY = contentTop.value;
},
onActive(event, context) {
contentTop.value = context.translateY + event.translationY;
},
onEnd(event, _) {
if (event.y > 0 && event.y < 200) {
// MIDDLE SCREEN LOGIC
contentTop.value = withTiming(
SCREEN_HEIGHT * IPHONE_DEVICE_MIDDLE_SCREEN,
{
duration: ANIMATED_DURATION,
easing: Easing.inOut(Easing.ease),
}
);
runOnJS(setBottomSheetState)("middle");
runOnJS(Keyboard.dismiss)(); // dismiss Keyboard
} else if (event.y > 200) {
// BOTTOM SCREEN LOGIC
contentTop.value = withTiming(
SCREEN_HEIGHT * IPHONE_DEVICE_BOTTOM_SCREEN,
{
duration: ANIMATED_DURATION,
easing: Easing.inOut(Easing.ease),
}
);
runOnJS(Keyboard.dismiss)();
runOnJS(setBottomSheetState)("bottom");
} else if (event.translationY < 0) {
contentTop.value = withTiming(SCREEN_HEIGHT, {
duration: ANIMATED_DURATION,
easing: Easing.inOut(Easing.ease),
});
runOnJS(setBottomSheetState)("top");
}
},
},
[contentTop]
);
return (
<PanGestureHandler onGestureEvent={gestureHandler}>
<Animated.View style={[styles.container, animatedStyle]}>
<View style={styles.grbber} />
{children}
</Animated.View>
</PanGestureHandler>
);
};
export default ScrollBottomSheet;
const styles = StyleSheet.create({
container: {
position: "absolute",
left: 0,
right: 0,
top: 0,
backgroundColor: "#fff",
shadowOffset: {
height: -6,
width: 0,
},
shadowOpacity: 0.1,
shadowRadius: 5,
borderTopEndRadius: 15,
borderTopLeftRadius: 15,
},
grbber: {
width: 80,
height: 5,
marginBottom: 4,
alignSelf: "center",
marginTop: 5,
borderTopWidth: 5,
borderTopColor: "#aaa",
},
});
Search Component
import React, { useRef, useEffect } from "react";
import {
Animated,
KeyboardTypeOptions,
TextInputProps,
TextInput,
Text,
Keyboard,
} from "react-native";
import styled from "styled-components/native";
type Props = TextInputProps & {
text: string,
hint: string,
onChangeText?: ((text: string) => void) | undefined,
onClearText: () => void,
animatedStyle?: { height: Animated.AnimatedInterpolation },
keyboardType?: KeyboardTypeOptions,
autoFocus?: boolean,
};
const Container = styled(Animated.View)`
flex-direction: row;
background-color: grey;
border-radius: 6px;
align-items: center;
`;
const IconTouchableOpacity = styled.TouchableOpacity`
padding: 16px;
`;
const SearchInput = styled.TextInput`
flex: 1;
padding-right: 16px;
color: #fff;
font-size: 16px;
line-height: 20px;
height: 44px;
`;
const SearchBar = ({
text,
hint,
onChangeText,
onClearText,
animatedStyle,
keyboardType,
maxLength,
autoFocus,
}: Props) => {
const searchInput = useRef(null);
const onSearchPress = () => {
searchInput?.current?.focus();
};
useEffect(() => {
if (autoFocus) {
onSearchPress();
}
}, [autoFocus]);
return (
<Container accessible={false} style={animatedStyle}>
<SearchInput
ref={searchInput}
onChangeText={onChangeText}
value={text}
placeholder={hint}
maxLength={maxLength}
underlineColorAndroid={"transparent"}
placeholderTextColor={"grey"}
keyboardType={keyboardType}
autoFocus={autoFocus}
onKeyPress={() => hideKeyBoard}
/>
</Container>
);
};
export default SearchBar;
**App Component
import React, { useState, useEffect } from "react";
import { StyleSheet, Button } from "react-native";
import { TouchableHighlight } from "react-native-gesture-handler";
import MapView from "react-native-maps";
import styled from "styled-components";
import ScrollBottomSheet from "./components/ActionSheet";
import SearchBar from "./components/SearchBar";
const initialRegion = {
latitudeDelta: 15,
longitudeDelta: 15,
latitude: 60.1098678,
longitude: 24.7385084,
};
export default function App() {
return (
<>
<MapView style={styles.mapStyle} initialRegion={initialRegion} />
<ScrollBottomSheet>
<SearchContainer>
<SearchBar hint={"search"} />
</SearchContainer>
</ScrollBottomSheet>
</>
);
}
const styles = StyleSheet.create({
mapStyle: {
height: "100%",
},
});
const SearchContainer = styled.View`
padding: 10px;
`;
Upvotes: 5
Views: 3487