We simply want to make an image movable around the screen and then snap back to the middle, we have our code below and that's working fine currently styled with a blue box that you can move around, but we're unable to figure out how we can amend the blue box into being our image that we have required in.
import React, { useRef } from "react";
import { Animated, PanResponder, StyleSheet, View, Image } from "react-native";
const Users = [{id:"1", uri: require('./assets/present.jpeg'), keyword: 'present1'}]
const DraggableView = () => {
const position = useRef(new Animated.ValueXY()).current;
console.log(pan)
const panResponder = PanResponder.create({
onStartShouldSetPanResponder: () => true,
onPanResponderMove: Animated.event([
null,
{
dx: pan.x, // x,y are Animated.Value
dy: pan.y,
},
]),
onPanResponderRelease: () => {
Animated.spring(
pan, // Auto-multiplexed
{ toValue: { x: 0, y: 0 } } // Back to zero
).start();
},
});
return (
<View style={styles.container}>
<Animated.View
{...panResponder.panHandlers}
style={[pan.getLayout(), <Image source={require('./assets/present.jpeg')}/>}
/>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: "center",
alignItems: "center",
},
box: {
backgroundColor: "#61dafb",
width: 80,
height: 80,
borderRadius: 4,
},
});
export default DraggableView;
The <Image /> component should be a child of the <Animated.View /> component. Currently, you have it defined in the style prop which doesn't work.
const DraggableView = () => {
const position = useRef(new Animated.ValueXY()).current;
console.log(pan)
const panResponder = PanResponder.create({
onStartShouldSetPanResponder: () => true,
onPanResponderMove: Animated.event([
null,
{
dx: pan.x, // x,y are Animated.Value
dy: pan.y,
},
]),
onPanResponderRelease: () => {
Animated.spring(
pan, // Auto-multiplexed
{ toValue: { x: 0, y: 0 } } // Back to zero
).start();
},
});
return (
<View style={styles.container}>
<Animated.View
{...panResponder.panHandlers}
style={pan.getLayout()}
>
<Image source={require('./assets/present.jpeg')}/>
</Animated.View>
</View>
);
};
Related
I am trying to create Height adjustable Views with React Native for an app I am building. I keep getting stuck on this one aspect. I am trying to create two stacked Views, with a line inbetween them so they are height adjustable when dragging the line up or down, adjusting content in it as well. Image below is a representation of what I am trying to make. "Home Option 2" is default state, "Home Option 1.3 is when the slider is dragged down, and "Home Option 1.2" is opposite - slider dragged up.
With an app bar at the bottom. (I dont have it made yet)
Any thoughts or help is appreciated!
Here is my code for App.tsx
import * as React from 'react';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
import { StatusBar } from 'expo-status-bar';
import { StyleSheet, TouchableOpacity, View } from 'react-native';
import BottomSheet, { BottomSheetRefProps } from './components/BottomSheet';
import { useCallback, useRef } from 'react';
import MapView, { Marker, Geojson } from "react-native-maps";
import { PROVIDER_GOOGLE } from "react-native-maps";
export default function App() {
const ref = useRef<BottomSheetRefProps>(null);
const [topViewHeight, setTopViewHeight] = React.useState(0);
const onPress = useCallback(() => {
const isActive = ref?.current?.isActive();
if (isActive) {
ref?.current?.scrollTo(0);
} else {
ref?.current?.scrollTo(-200);
}
}, []);
return (
<GestureHandlerRootView style={{ flex: 1 }}>
<View style={styles.mapViewContainer}>
<MapView
provider={PROVIDER_GOOGLE}
showsUserLocation={true}
style={styles.mapView}
initialRegion={{
latitude: 00.00 ,
longitude: -00.00 ,
latitudeDelta: 00.00 ,
longitudeDelta: 00.00 ,
}}
>
<Marker coordinate={{ latitude: 00.00, longitude: 00.00 }} />
</MapView>
</View>
<View style={styles.container}>
<StatusBar style="light" />
<TouchableOpacity style={styles.button} onPress={onPress} />
<BottomSheet ref={ref} {...{setTopViewHeight, topViewHeight}}>
<View style={{ flex: 1, backgroundColor: 'orange' }} />
</BottomSheet>
</View>
</GestureHandlerRootView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#111',
alignItems: 'center',
justifyContent: 'center',
},
button: {
height: 50,
borderRadius: 25,
aspectRatio: 1,
backgroundColor: 'white',
opacity: 0.6,
},
mapViewContainer: {
height: "50%",
width: "95%",
overflow: "hidden",
background: "transparent",
borderRadius: 13,
},
mapView: {
height: "100%",
width: "100%",
},
});
Code for BottomSheet.tsx (Which i was using as a reference for the ideal UX)
import { Dimensions, StyleSheet, Text, View } from 'react-native';
import React, { useCallback, useEffect, useImperativeHandle } from 'react';
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
import Animated, {
Extrapolate,
interpolate,
useAnimatedStyle,
useSharedValue,
withSpring,
withTiming,
} from 'react-native-reanimated';
const { height: SCREEN_HEIGHT } = Dimensions.get('window');
const TOP_VIEW_HEIGHT = 50;
const VIEW_RESIZE = 2.5;
const MAX_TRANSLATE_Y = -SCREEN_HEIGHT / VIEW_RESIZE;
type BottomSheetProps = {
children?: React.ReactNode;
setTopViewHeight: (height: number) => void;
topViewHeight: number;
};
export type BottomSheetRefProps = {
scrollTo: (destination: number) => void;
isActive: () => boolean;
};
const BottomSheet = React.forwardRef<BottomSheetRefProps, BottomSheetProps>(
({ children }, ref) => {
const translateY = useSharedValue(0);
const active = useSharedValue(false);
const scrollTo = useCallback((destination: number) => {
'worklet';
active.value = destination !== 0;
translateY.value = withSpring(destination, { damping: 50 });
}, []);
const isActive = useCallback(() => {
return active.value;
}, []);
useImperativeHandle(ref, () => ({ scrollTo, isActive }), [
scrollTo,
isActive,
]);
const context = useSharedValue({ y: 0 });
const gesture = Gesture.Pan()
.onStart(() => {
context.value = { y: translateY.value };
})
.onUpdate((event) => {
translateY.value = event.translationY + context.value.y;
translateY.value = Math.max(translateY.value, MAX_TRANSLATE_Y);
console.log(translateY.value);
})
.onEnd(() => {
if (translateY.value > -SCREEN_HEIGHT / 3) {
scrollTo(0);
} else if (translateY.value < -SCREEN_HEIGHT / 1.5) {
scrollTo(MAX_TRANSLATE_Y);
}
console.log('end: ' + translateY.value)
});
const rBottomSheetStyle = useAnimatedStyle(() => {
const borderRadius = interpolate(
translateY.value,
[MAX_TRANSLATE_Y + 50, MAX_TRANSLATE_Y],
[25, 5],
Extrapolate.CLAMP
);
return {
borderRadius,
transform: [{ translateY: translateY.value }],
maxHeight: 500,
};
});
return (
<GestureDetector gesture={gesture}>
<Animated.View style={[styles.bottomSheetContainer, rBottomSheetStyle] }>
<View style={styles.line} />
{children}
</Animated.View>
</GestureDetector>
);
}
);
const styles = StyleSheet.create({
bottomSheetContainer: {
minHeight: SCREEN_HEIGHT - 50,
width: '100%',
backgroundColor: 'white',
position: 'relative',
top: SCREEN_HEIGHT - 500,
borderRadius: 25,
},
line: {
width: 75,
height: 4,
backgroundColor: 'grey',
alignSelf: 'center',
marginVertical: 15,
borderRadius: 2,
},
});
export default BottomSheet;
The Bar component will have the GestureHandler tied to it. Interpolate yTranslation into a value between 0 and 1. The Bar component's SharedValue is passed as prop so that other components in its parent contain utilize it:
import {
StyleSheet,
ViewStyle,
Dimensions,
View,
useWindowDimensions,
} from 'react-native';
import Animated, {
SharedValue,
useAnimatedStyle,
interpolate,
withTiming,
} from 'react-native-reanimated';
import { GestureDetector, Gesture } from 'react-native-gesture-handler';
type Props = {
anim: SharedValue<number>;
style?: ViewStyle;
};
const snapPoints = [0.2, 0.5, 0.8];
export default function Bar({ anim, style }: Props) {
const { height } = useWindowDimensions();
const gesture = Gesture.Pan()
.onUpdate((e) => {
// interpolate yTranslation to a value that snapPoints can work with
anim.value = interpolate(
e.translationY,
[-height * 0.5, height * 0.5],
[0, 1]
);
})
// snap to nearest point
.onEnd(() => {
const snapPoint = snapPoints.reduce((prev, curr) => {
const prevDist = Math.abs(prev - anim.value);
const currDist = Math.abs(curr - anim.value);
return prevDist < currDist ? prev : curr;
}, snapPoints[0]);
console.log('snapping to ', snapPoint);
// animate snapping to snapPoint
anim.value = withTiming(snapPoint);
});
return (
<GestureDetector gesture={gesture}>
<View style={styles.barContainer}>
<View style={styles.bar} />
</View>
</GestureDetector>
);
}
const styles = StyleSheet.create({
barContainer: {
backgroundColor: 'transparent',
width: '100%',
//padding to make bar easier to press
padding: 10,
justifyContent: 'center',
},
bar: {
backgroundColor: '#c4c4c4',
width: '80%',
height: 7,
alignSelf: 'center',
borderRadius: 25,
},
});
Now that translationY is a percentage it can be used to determine the amount of flex each view have:
import React from 'react';
import {
View,
StyleSheet,
} from 'react-native';
import Constants from 'expo-constants';
import Animated, {
useSharedValue,
useAnimatedStyle,
} from 'react-native-reanimated';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
import SliderBar from './SliderBar';
import View1 from './View1';
import View2 from './View2';
import { footerHeight, ScreenWidth, ScreenHeight, MAX_FLEX } from './Constants';
export default function App() {
const barValue = useSharedValue(0.5);
const view1Style = useAnimatedStyle(() => {
return {
flex: barValue.value * MAX_FLEX,
};
});
const view2Style = useAnimatedStyle(() => {
return {
flex: Math.abs(barValue.value - 1) * MAX_FLEX,
};
});
return (
<GestureHandlerRootView
style={{ width: ScreenWidth, height: ScreenHeight }}>
<View style={styles.container}>
<Animated.View style={[styles.viewStyle, view1Style]}>
<View1 />
</Animated.View>
<SliderBar anim={barValue} />
<Animated.View style={[styles.viewStyle, view2Style]}>
<View2 />
</Animated.View>
<View style={styles.footer} />
</View>
</GestureHandlerRootView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
paddingTop: Constants.statusBarHeight,
padding: 8,
margin: 5,
},
viewStyle: {
backgroundColor: '#c4c4c4',
flex: 1,
marginVertical: 10,
borderRadius: 10,
},
footer: {
backgroundColor: '#6f6f6f',
height: footerHeight,
borderRadius: 10,
},
});
Demo
I am Creating a Tinder Clone in React Native and working on UI. But having some difficulty in showing images on top of the screen. I used zIndex but it didn't effect because it doesn't work on Android, I used elevation but it is giving me the error plus warning too. Here is the Code
import React, {useEffect, useState} from 'react';
import {
View,
StyleSheet,
useWindowDimensions,
Image,
Platform,
} from 'react-native';
import Card from './src/components/TinderCard';
import users from './assets/data/users';
import Animated, {
useSharedValue,
useAnimatedStyle,
useAnimatedGestureHandler,
useDerivedValue,
interpolate,
withSpring,
runOnJS,
} from 'react-native-reanimated';
import 'react-native-gesture-handler';
import {PanGestureHandler} from 'react-native-gesture-handler';
import like from './assets/images/LIKE.png';
import nope from './assets/images/nope.png';
const ROTATION = 60;
const SWIPE_VELOCITY = 800;
const App = () => {
const [currentIndex, setCurrentIndex] = useState(0);
const [nexttIndex, setNextIndex] = useState(currentIndex + 1);
const currentProfile = users[currentIndex];
const nextProfile = users[nexttIndex];
const {width: screenWidth} = useWindowDimensions();
const hiddenTranslateX = 2 * screenWidth;
const translateX = useSharedValue(0);
const rotate = useDerivedValue(
() =>
interpolate(translateX.value, [0, hiddenTranslateX], [0, ROTATION]) +
'deg',
);
const cardStyle = useAnimatedStyle(() => ({
transform: [
{
translateX: translateX.value,
},
{rotate: rotate.value},
],
}));
const nextCardStyle = useAnimatedStyle(() => ({
transform: [
{
scale: interpolate(
translateX.value,
[-hiddenTranslateX, 0, hiddenTranslateX],
[1, 0.8, 1],
),
},
],
opacity: interpolate(
translateX.value,
[-hiddenTranslateX, 0, hiddenTranslateX],
[1, 0.5, 1],
),
}));
const gestureHandler = useAnimatedGestureHandler({
onStart: (_, context) => {
context.startX = translateX.value;
},
onActive: (event, context) => {
translateX.value = context.startX + event.translationX;
},
onEnd: event => {
if (Math.abs(event.velocityX) < SWIPE_VELOCITY) {
translateX.value = withSpring(0);
return;
}
translateX.value = withSpring(
hiddenTranslateX * Math.sign(event.velocityX),
{},
() => runOnJS(setCurrentIndex)(currentIndex + 1),
);
},
});
useEffect(() => {
translateX.value = 0;
setNextIndex(currentIndex + 1);
}, [currentIndex, translateX]);
return (
<View style={styles.container}>
{nextProfile && (
<View style={styles.nextCardContainer}>
<Animated.View style={[styles.animatedCard, nextCardStyle]}>
<Card user={nextProfile} />
</Animated.View>
</View>
)}
{currentProfile && (
<PanGestureHandler onGestureEvent={gestureHandler}>
<Animated.View style={[styles.animatedCard, cardStyle]}>
<Image source={like} style={styles.like} resizeMode="contain" />
<Image source={nope} style={styles.like} resizeMode="contain" />
<Card user={currentProfile} />
</Animated.View>
</PanGestureHandler>
)}
</View>
);
};
const styles = StyleSheet.create({
container: {
justifyContent: 'center',
alignItems: 'center',
flex: 1,
},
animatedCard: {
width: '90%',
height: '70%',
justifyContent: 'center',
alignItems: 'center',
},
nextCardContainer: {
width: '100%',
justifyContent: 'center',
alignItems: 'center',
...StyleSheet.absoluteFillObject,
},
like: {
width: 150,
height: 150,
position: 'absolute',
top: 10,
zIndex: 1,
elevation: 1,
},
});
export default App;
Help me to figure out what i am doing wrong here.
Have you tried using an ImageBackground instead? If you're trying to add box-shadow then and React native image background example
Ive seen a few questions about this topic but none have a solid answer.
I am basically re-writing an animated component which is written as a class based component into a functional component. The issue is that in my functional component the animation jumps every time decay is finished animating and the view is touched again. The solution for this is to set the offset of the animated view's x and y values to its own current x and y values. I am attempting to implement that same logic in the functional component but for some reason it does not work the same way as the class based component. Am I doing something wrong? Here are both components side by side. Again, the class based component works perfectly but the functional based component jumps every time the screen is touched after animation has been initiated.
CLASS BASED COMPONENT
import React, { Component } from "react";
import {
StyleSheet,
View,
Animated,
PanResponder,
} from "react-native";
export default class animations extends Component {
state = {
animation: new Animated.ValueXY(0),
};
componentWillMount() {
this._x = 0;
this._y = 0;
this.state.animation.addListener(value => {
this._x = value.x;
this._y = value.y;
});
this._panResponder = PanResponder.create({
onStartShouldSetPanResponder: () => true,
onMoveShouldSetPanResponder: () => true,
onPanResponderGrant: () => {
this.state.animation.setOffset({ x: this._x, y: this._y });
this.state.animation.setValue({ x: 0, y: 0 });
},
onPanResponderMove: Animated.event([
null,
{ dx: this.state.animation.x, dy: this.state.animation.y },
]),
onPanResponderRelease: (e, { vx, vy }) => {
Animated.decay(this.state.animation, {
velocity: { x: vx, y: vy },
deceleration: 0.997,
}).start();
},
});
}
render() {
const animatedStyle = {
transform: this.state.animation.getTranslateTransform(),
};
return (
<View style={styles.container}>
<Animated.View
style={[styles.box, animatedStyle]}
{...this._panResponder.panHandlers}
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: "center",
justifyContent: "center",
},
box: {
width: 50,
height: 50,
backgroundColor: "tomato",
},
});
FUNCTIONAL COMPONENT
import React, { useState, useEffect, useRef } from 'react'
import { View, Text, StyleSheet, Animated, PanResponder } from 'react-native'
const Decays = (props) => {
const AnimatedValue = useRef(new Animated.ValueXY(0)).current
let _x = useRef(0).current
let _y = useRef(0).current
useEffect(() => {
AnimatedValue.addListener((value) => {
_x = value.x
_y = value.y
})
})
const panResponder = PanResponder.create({
onStartShouldSetPanResponder: () => true,
onMoveShouldSetPanResponder: () => true,
onPanResponderGrant: () => {
AnimatedValue.setOffset({
x: _x,
y: _y,
})
AnimatedValue.setValue({ x: 0, y: 0 })
},
onPanResponderMove: Animated.event(
[
null,
{
dx: AnimatedValue.x,
dy: AnimatedValue.y,
},
],
{ useNativeDriver: false }
),
onPanResponderRelease: (e, { vx, vy }) => {
Animated.decay(AnimatedValue, {
velocity: { x: vx, y: vy },
deceleration: 0.996,
useNativeDriver: true,
}).start()
},
})
const animatedStyle = {
transform: AnimatedValue.getTranslateTransform(),
}
return (
<View style={styles.screen}>
<Animated.View
style={[styles.box, animatedStyle]}
{...panResponder.panHandlers}
></Animated.View>
</View>
)
}
const styles = StyleSheet.create({
screen: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
box: {
backgroundColor: 'tomato',
width: 200,
height: 200,
alignItems: 'center',
justifyContent: 'center',
},
})
export default Decays
I finally figured it out.
Turns out my logic is not the issue. I have successfully converted the component from class based to function based. The jumping issue is caused by the useNativeDriver: true set on the decay animation. When this is set to false it does not jump.
I have a Animated.View Component which has a PanResponder to make it draggable.
This Animated.View Component has a Text child which has the onLongPress event listener set to perform some action later on...
The problem is that it seams like the onLongPress event listener is also "capturing" the normal press action(which is needed for the PanResponder too.
Is it possible to pass the normal onPress of the Text to the PanResponder, cancel it or trigger onStartShouldSetPanResponder manually, to not interrupt the PanResponder unless it's an actual long press?
contructor
constructor(props) {
super(props);
this.state = {
showDraggable: true,
dropAreaValues: null,
pan: new Animated.ValueXY(),
opacity: new Animated.Value(1),
text: "Hello World"
};
this._val = { x: 0, y: 0 };
this.state.pan.addListener(value => (this._val = value));
this.panResponder = PanResponder.create({
onStartShouldSetPanResponder: (e, gesture) => true,
onPanResponderGrant: (e, gesture) => {
this.state.pan.setOffset({
x: this._val.x,
y: this._val.y
});
this.state.pan.setValue({ x: 0, y: 0 });
},
onPanResponderMove: Animated.event([
null,
{ dx: this.state.pan.x, dy: this.state.pan.y }
])
});
}
render
render() {
const panStyle = {
transform: this.state.pan.getTranslateTransform()
};
return (
<View
style={{
position: "absolute",
top: this.props.top
}}
>
<Animated.View {...this.panResponder.panHandlers} style={[panStyle]}>
<View style={{ zIndex: 250,
padding: 10,
borderWidth: 2,
borderColor: "yellow"}}>
<Text style={{fontSize: 24}} onLongPress={() => {
console.log("Long press recognized")
}}>{this.state.text}</Text>
</View>
</Animated.View>
</View>
);
}
(The border is to make the Views boundaries visible and is not of importance regarding the question)
The solution was to use Animated.Text
it works with the PanResponder.
Let's say I have a view that is positioned absolute at the bottom of the screen. This view contains a text input. When the text input is focused, I want the bottom of the view to touch the top of the keyboard.
I've been messing around with KeyboardAvoidingView, but the keyboard keeps going over my view. Is it not possible to make this work with position absolute?
What other method can I try? Thanks!
Few days ago I have the same problem (although I have a complex view with TextInput as a child) and wanted not only the TextInput to be focused but the whole view to be "attached" to the keyboard. What's finally is working for me is the following code:
constructor(props) {
super(props);
this.paddingInput = new Animated.Value(0);
}
componentWillMount() {
this.keyboardWillShowSub = Keyboard.addListener('keyboardWillShow', this.keyboardWillShow);
this.keyboardWillHideSub = Keyboard.addListener('keyboardWillHide', this.keyboardWillHide);
}
componentWillUnmount() {
this.keyboardWillShowSub.remove();
this.keyboardWillHideSub.remove();
}
keyboardWillShow = (event) => {
Animated.timing(this.paddingInput, {
duration: event.duration,
toValue: 60,
}).start();
};
keyboardWillHide = (event) => {
Animated.timing(this.paddingInput, {
duration: event.duration,
toValue: 0,
}).start();
};
render() {
return (
<KeyboardAvoidingView behavior='padding' style={{ flex: 1 }}>
[...]
<Animated.View style={{ marginBottom: this.paddingInput }}>
<TextTranslateInput />
</Animated.View>
</KeyboardAvoidingView>
);
}
where [..] you have other views.
Custom hook:
import { useRef, useEffect } from 'react';
import { Animated, Keyboard, KeyboardEvent } from 'react-native';
export const useKeyboardHeight = () => {
const keyboardHeight = useRef(new Animated.Value(0)).current;
useEffect(() => {
const keyboardWillShow = (e: KeyboardEvent) => {
Animated.timing(keyboardHeight, {
duration: e.duration,
toValue: e.endCoordinates.height,
useNativeDriver: true,
}).start();
};
const keyboardWillHide = (e: KeyboardEvent) => {
Animated.timing(keyboardHeight, {
duration: e.duration,
toValue: 0,
useNativeDriver: true,
}).start();
};
const keyboardWillShowSub = Keyboard.addListener(
'keyboardWillShow',
keyboardWillShow
);
const keyboardWillHideSub = Keyboard.addListener(
'keyboardWillHide',
keyboardWillHide
);
return () => {
keyboardWillHideSub.remove();
keyboardWillShowSub.remove();
};
}, [keyboardHeight]);
return keyboardHeight;
};
#jazzdle example works great! Thank you for that!
Just one addition - in keyboardWillShow method, one can add event.endCoordinates.height so paddingBottom is exact height as keyboard.
keyboardWillShow = (event) => {
Animated.timing(this.paddingInput, {
duration: event.duration,
toValue: event.endCoordinates.height,
}).start();
}
Using Functional Component. This works for both iOS and Android
useEffect(() => {
const keyboardVisibleListener = Keyboard.addListener(
Platform.OS === "ios" ? "keyboardWillShow" : "keyboardDidShow",
handleKeyboardVisible
);
const keyboardHiddenListener = Keyboard.addListener(
Platform.OS === "ios" ? "keyboardWillHide" : "keyboardDidHide",
handleKeyboardHidden
);
return () => {
keyboardHiddenListener.remove();
keyboardVisibleListener.remove();
};}, []);
const handleKeyboardVisible = (event) => {
Animated.timing(paddingInput, {
duration: event.duration,
toValue: 60,
useNativeDriver: false,
});};
const handleKeyboardHidden = (event: any) => {
Animated.timing(paddingInput, {
duration: event.duration,
toValue: 0,
useNativeDriver: false,
});};
React Native now supports an InputAccessoryView which can be used for exactly this purpose - even for anchored TextInputs.
Here's a specific example: https://github.com/facebook/react-native/blob/main/packages/rn-tester/js/examples/InputAccessoryView/InputAccessoryViewExample.js
You can use flexbox to bottom position the element. Here's simple example -
render() {
return (
<View style={styles.container}>
<View style={styles.top}/>
<View style={styles.bottom}>
<View style={styles.input}>
<TextInput/>
</View>
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
top: {
flex: .8,
},
bottom: {
flex: .2,
},
input: {
width: 200,
},
});