如何在 React Native 中从底部滑入和滑出 <View/>?

IT技术 javascript reactjs react-native animation redux
2021-04-16 01:42:25

在 React Native iOS 中,我想在下图中滑入和滑出一个喜欢。

在下面的例子中,当按下一个按钮时,Payment Information视图从底部弹出,当按下折叠按钮时,它返回并消失。

这样做的正确方法是什么?

在此处输入图片说明

先感谢您!

编辑

在此处输入图片说明

5个回答

基本上,您需要将视图绝对定位到屏幕底部。然后将其 y 值转换为等于其高度。(子视图必须具有特定的高度才能知道移动多少)

代码:

'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);
谢谢!稍后再试
2021-05-31 01:42:25
@JoKo 就像添加一个新按钮一样简单,onpress 映射到一个新功能。
2021-06-03 01:42:25
进行了尝试,新的/第二个子视图从底部向上滑动但上升太多(用屏幕截图更新原始帖子。粉红色是第二个子视图,黑色是第一个子视图,如底部所示)。事实证明,这与container style没有alignItems: 'center. 当它从底部向上滑动时,它被固定在底部(像第一个子视图一样),没有alignItems: 'center'因为,第二个内容subview都与中心对齐,我不希望那样。
2021-06-05 01:42:25
它有效,非常感谢!还有一个问题,它会显示一个按钮,一旦点击,我希望出现相同的子视图。我该怎么做?谢谢!
2021-06-15 01:42:25
只是看看你是否看过我以前的评论。请告诉我们。
2021-06-22 01:42:25

我知道这有点晚了,但认为它可能对某人有用。您应该尝试一个名为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在您的 react-native 根目录中安装它


我希望它可以帮助某人:D

我创建了一个接受任何内容可重用 BottomSheet组件。

这是它的样子:

在此处输入图片说明

这是组件的代码(在 TypeScript 中)BottomSheet

import * as React from 'react'
import {
  Animated,
  Easing,
  Pressable,
  StyleSheet,
  useWindowDimensions,
  View,
} 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) {
  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.bottomSheet, { height, bottom }]}>
        {children}
      </Animated.View>
    </>
  )
}

const styles = StyleSheet.create({
  outerOverlay: {
    position: 'absolute',
    width: '100%',
    zIndex: 1,
    backgroundColor: 'black',
    opacity: 0.3,
  },
  bottomSheet: {
    position: 'absolute',
    width: '100%',
    zIndex: 1,
    // Here you can set a common style for all bottom sheets, or nothing if you
    // want different designs
    backgroundColor: 'dodgerblue',
    borderRadius: 16,
  },
})

我把这段代码放在一个名为BottomSheet.tsx.

这是您使用的方式BottomSheet

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.safeAreaView}>
      <StatusBar barStyle={'dark-content'} />
      <View style={styles.container}>
        <Pressable
          onPress={() => {
            setShowBottomSheet(true)
          }}
          style={styles.showButton}
        >
          <Text style={styles.buttonText}>Show bottom sheet</Text>
        </Pressable>
      </View>

      <BottomSheet show={showBottomSheet} height={290} onOuterClick={hide}>
        <View style={styles.bottomSheetContent}>
          <Text style={styles.bottomSheetText}>Hey boys, hey girls!</Text>
          <Pressable onPress={hide} style={styles.bottomSheetCloseButton}>
            <Text style={styles.buttonText}>X Close</Text>
          </Pressable>
        </View>
      </BottomSheet>
    </SafeAreaView>
  )
}

const styles = StyleSheet.create({
  safeAreaView: {
    flex: 1,
  },
  container: {
    flex: 1,
  },
  showButton: {
    marginTop: 48,
    padding: 16,
    backgroundColor: 'mediumspringgreen',
    alignSelf: 'center',
    borderRadius: 8,
  },
  buttonText: {
    fontSize: 20,
  },
  bottomSheetContent: {
    padding: 40,
    alignItems: 'center',
  },
  bottomSheetText: {
    fontSize: 24,
    marginBottom: 80,
  },
  bottomSheetCloseButton: {
    padding: 16,
    backgroundColor: 'deeppink',
    borderRadius: 8,
  },
})

export default App

笔记:

  • 我已经放置了一个外部半透明覆盖层,但您可以通过删除它来摆脱它。
  • 当您单击外部半透明叠加层时,我选择关闭模态。这是通过onOuterClick回调完成的,这是可选的 - 如果您不想做任何事情,请不要传递它。
  • 我已经添加了一些适用于所有底部工作表的样式(蓝色背景 + 边框半径),但如果您想要不同的样式,则可以将其删除。

经过相当长的搜索,我发现了一个非常好的库,名为react-native-swipe-down with MIT license它将帮助您<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 react-native-raw-bottom-sheet

但是,如果我们想与做nativelly请看下面一码,我会尽力证明答案:)

对于一些动画效果,我将参考吹网站 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

演示

在此处输入图片说明