Как вставить <View /> внутрь и наружу снизу в React Native?
В React Native iOS я бы хотел вставлять и высовывать изображение, как на картинке.
В следующем примере, когда кнопка нажата, Payment Information
вид выскакивает снизу и при нажатии на кнопку сворачивания возвращается вниз и исчезает.
Каков будет правильный и правильный способ сделать это?
Заранее спасибо!
РЕДАКТИРОВАТЬ
5 ответов
По сути, вам необходимо позиционировать ваш вид абсолютно внизу экрана. Затем вы переводите его значение y в его высоту. (Подвид должен иметь определенную высоту, чтобы знать, на сколько его можно переместить)
Вот игровая площадка, показывающая результат: https://rnplay.org/apps/n9Gxfg
Код:
'use strict';
import React, {Component} from 'react';
import ReactNative from 'react-native';
const {
AppRegistry,
StyleSheet,
Text,
View,
TouchableHighlight,
Animated
} = ReactNative;
var isHidden = true;
class AppContainer extends Component {
constructor(props) {
super(props);
this.state = {
bounceValue: new Animated.Value(100), //This is the initial position of the subview
buttonText: "Show Subview"
};
}
_toggleSubview() {
this.setState({
buttonText: !isHidden ? "Show Subview" : "Hide Subview"
});
var toValue = 100;
if(isHidden) {
toValue = 0;
}
//This will animate the transalteY of the subview between 0 & 100 depending on its current state
//100 comes from the style below, which is the height of the subview.
Animated.spring(
this.state.bounceValue,
{
toValue: toValue,
velocity: 3,
tension: 2,
friction: 8,
}
).start();
isHidden = !isHidden;
}
render() {
return (
<View style={styles.container}>
<TouchableHighlight style={styles.button} onPress={()=> {this._toggleSubview()}}>
<Text style={styles.buttonText}>{this.state.buttonText}</Text>
</TouchableHighlight>
<Animated.View
style={[styles.subView,
{transform: [{translateY: this.state.bounceValue}]}]}
>
<Text>This is a sub view</Text>
</Animated.View>
</View>
);
}
}
var styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
marginTop: 66
},
button: {
padding: 8,
},
buttonText: {
fontSize: 17,
color: "#007AFF"
},
subView: {
position: "absolute",
bottom: 0,
left: 0,
right: 0,
backgroundColor: "#FFFFFF",
height: 100,
}
});
AppRegistry.registerComponent('AppContainer', () => AppContainer);
Я знаю, что уже немного поздно, но подумал, что это может быть полезно для кого-то. Вы должны попробовать компонент под названием rn-sliding-out-panel
, Это работает потрясающе. https://github.com/octopitus/rn-sliding-up-panel
<SlidingUpPanel
draggableRange={top: 1000, bottom: 0}
showBackdrop={true|false /*For making it modal-like*/}
ref={c => this._panel = c}
visible={ture|false /*If you want it to be visible on load*/}
></SlidingUpPanel>
И вы даже можете открыть его с внешней кнопки:
<Button onPress={()=>{this._panel.transitionTo(1000)}} title='Expand'></Button>
Вы можете установить его через npm: sudo npm install rn-sliding-out-panel --save
в вашем корневом каталоге реакции.
Надеюсь, это кому-нибудь поможет:D
Я создал многоразовый
BottomSheet
компонент, принимающий любой контент.
Вот как это выглядит:
Вот код (на TypeScript):
import * as React from 'react'
import {
Animated,
Easing,
Pressable,
StyleSheet,
useWindowDimensions,
View,
ViewProps,
} from 'react-native'
const DEFAULT_HEIGHT = 300
function useAnimatedBottom(show: boolean, height: number = DEFAULT_HEIGHT) {
const animatedValue = React.useRef(new Animated.Value(0))
const bottom = animatedValue.current.interpolate({
inputRange: [0, 1],
outputRange: [-height, 0],
})
React.useEffect(() => {
if (show) {
Animated.timing(animatedValue.current, {
toValue: 1,
duration: 350,
// Accelerate then decelerate - https://cubic-bezier.com/#.28,0,.63,1
easing: Easing.bezier(0.28, 0, 0.63, 1),
useNativeDriver: false, // 'bottom' is not supported by native animated module
}).start()
} else {
Animated.timing(animatedValue.current, {
toValue: 0,
duration: 250,
// Accelerate - https://easings.net/#easeInCubic
easing: Easing.cubic,
useNativeDriver: false,
}).start()
}
}, [show])
return bottom
}
interface Props {
children: React.ReactNode
show: boolean
height?: number
onOuterClick?: () => void
}
export function BottomSheet({
children,
show,
height = DEFAULT_HEIGHT,
onOuterClick,
...props
}: Props & ViewProps) {
const { height: screenHeight } = useWindowDimensions()
const bottom = useAnimatedBottom(show, height)
return (
<>
{/* Outer semitransparent overlay - remove it if you don't want it */}
{show && (
<Pressable
onPress={onOuterClick}
style={[styles.outerOverlay, { height: screenHeight }]}
>
<View />
</Pressable>
)}
<Animated.View style={[styles.container, { height, bottom }]}>
<View {...props} style={[styles.modal, props.style]}>
{children}
</View>
</Animated.View>
</>
)
}
const styles = StyleSheet.create({
outerOverlay: {
position: 'absolute',
width: '100%',
zIndex: 1,
backgroundColor: '#000000',
opacity: 0.3,
},
container: {
position: 'absolute',
width: '100%',
zIndex: 1,
},
modal: {
height: '100%',
backgroundColor: '#0099ff',
borderRadius: 12,
padding: 40,
alignItems: 'center',
},
})
И вот как вы его используете:
import * as React from 'react'
import {
Pressable,
SafeAreaView,
StatusBar,
StyleSheet,
Text,
View,
} from 'react-native'
import { BottomSheet } from './src/BottomSheet'
const App = () => {
const [showBottomSheet, setShowBottomSheet] = React.useState(false)
const hide = () => {
setShowBottomSheet(false)
}
return (
<SafeAreaView style={styles.container}>
<StatusBar barStyle={'dark-content'} />
<View style={styles.container2}>
<Pressable
onPress={() => {
setShowBottomSheet(true)
}}
style={styles.showButton}
>
<Text style={styles.buttonText}>Show bottom sheet</Text>
</Pressable>
</View>
<BottomSheet show={showBottomSheet} height={290} onOuterClick={hide}>
<Text style={styles.bottomSheetText}>Hey boys, hey girls!</Text>
<Pressable onPress={hide} style={styles.bottomSheetCloseButton}>
<Text style={styles.buttonText}>X Close</Text>
</Pressable>
</BottomSheet>
</SafeAreaView>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
container2: {
flex: 1,
},
showButton: {
marginTop: 16,
padding: 16,
backgroundColor: 'pink',
alignSelf: 'center',
borderRadius: 8,
},
buttonText: {
fontSize: 20,
},
bottomSheetText: {
fontSize: 24,
marginBottom: 80,
},
bottomSheetCloseButton: {
marginTop: 16,
padding: 16,
backgroundColor: '#ff4343',
alignSelf: 'center',
borderRadius: 8,
},
})
export default App
Я решил закрыть модальное окно при нажатии снаружи. Это делается с помощью
onOuterClick
обратный вызов, который не является обязательным - не передавайте его, если не хотите ничего делать.
Обратите внимание, что вы можете полностью избавиться от внешнего полупрозрачного оверлея, удалив его.
После довольно долгого поиска я нашел очень хорошую библиотеку под названием react-native-swipe-down с лицензией MIT. Это поможет вам сделать слайдер<View />
без усилий.
Надеюсь, эта библиотека вам поможет.
import SwipeUpDown from 'react-native-swipe-up-down';
<SwipeUpDown
itemMini={<ItemMini />} // Pass props component when collapsed
itemFull={<ItemFull />} // Pass props component when show full
onShowMini={() => console.log('mini')}
onShowFull={() => console.log('full')}
onMoveDown={() => console.log('down')}
onMoveUp={() => console.log('up')}
disablePressToShow={false} // Press item mini to show full
style={{ backgroundColor: 'green' }} // style for swipe
/>
Чтобы достичь вышеуказанного типа требований, мы можем воспользоваться некоторыми большими библиотеками.
1 @ gorhom / bottom-sheet 2 реагировать-native-raw-bottom-sheet
Но если мы хотим работать с nativelly, пожалуйста, найдите код ниже, я постараюсь обосновать ответ :)
Для некоторых эффектов анимации я воспользуюсь ссылкой на сайте blow-site how-to-create-moving-animations-in-react-native
import React, { useState } from 'react'
import { SafeAreaView, View, ScrollView, Text, Dimensions, TouchableOpacity, Animated } from 'react-native'
const App = () => {
const { height, width } = Dimensions.get('window')
const SCREEN_HEIGHT = Math.round(height)
const SCREEN_WIDTH = Math.round(width)
// Animation
const startValue = new Animated.Value(Math.round(height + height * 0.3))
const endValue = Math.round(height - height * 0.3)
const duration = 1000
const _showBottomView = (key) => {
const toValue = key === 'HIDE' ? height : endValue
Animated.timing(startValue, {
toValue,
duration: duration,
useNativeDriver: true,
}).start()
}
return (
<SafeAreaView style={{ flex: 1, backgroundColor: 'rgba(0,0,0,0.1)' }}>
{/* Header */}
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center', borderWidth: 1, borderColor: 'black', margin: 5 }}>
<Text>
Header
</Text>
</View>
<View style={{ flex: 9, borderWidth: 1, borderColor: 'black', margin: 5 }}>
<ScrollView
style={{ flex: 1 }}>
{/* Title View */}
<View style={{ height: SCREEN_HEIGHT * 0.1, width: '95%', borderColor: 'black', borderWidth: 1, marginLeft: '2.5%', marginTop: SCREEN_HEIGHT * 0.01, alignItems: 'center', justifyContent: 'center', }}>
<Text>
Content ONE
</Text>
</View>
<View style={{ height: SCREEN_HEIGHT * 0.5, width: '95%', borderColor: 'black', borderWidth: 1, marginLeft: '2.5%', marginTop: SCREEN_HEIGHT * 0.01, alignItems: 'center', justifyContent: 'center', }}>
<Text>
Content TWO
</Text>
</View>
<View style={{ height: SCREEN_HEIGHT * 0.2, width: '95%', borderColor: 'black', borderWidth: 1, marginLeft: '2.5%', marginTop: SCREEN_HEIGHT * 0.01, alignItems: 'center', justifyContent: 'center', }}>
<TouchableOpacity
activeOpacity={0.85}
onPress={() => _showBottomView()}
style={{ height: SCREEN_HEIGHT * 0.065, width: '75%', borderRadius: 5, borderWidth: 1, borderColor: 'green', alignItems: 'center', justifyContent: 'center', }}>
<Text>
SHOW BOTTOM VIEW
</Text>
</TouchableOpacity>
</View>
<View style={{ height: SCREEN_HEIGHT * 0.3, width: '95%', borderColor: 'black', borderWidth: 1, marginLeft: '2.5%', marginTop: SCREEN_HEIGHT * 0.01, alignItems: 'center', justifyContent: 'center', marginBottom: SCREEN_HEIGHT * 0.01 }}>
<Text>
...REST_CONTENT...
</Text>
</View>
</ScrollView>
</View>
{/* Bottom view */}
<Animated.View
style={[
{
position: 'absolute',
height: height * 0.3,
width: width,
backgroundColor: 'white',
alignItems: 'center', justifyContent: 'center',
borderTopRightRadius: 23, borderTopLeftRadius: 23,
transform: [
{
translateY: startValue
},
],
},
]} >
<TouchableOpacity
activeOpacity={0.85}
onPress={() => _showBottomView('HIDE')}
style={{ height: SCREEN_HEIGHT * 0.065, width: '75%', borderRadius: 5, borderWidth: 1, borderColor: 'green', alignItems: 'center', justifyContent: 'center', }}>
<Text>
HIDE BOTTOM VIEW
</Text>
</TouchableOpacity>
</Animated.View>
</SafeAreaView>
)
}
export default App
Демо