Как вставить <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

Демо

Другие вопросы по тегам