ReactNative 抽屉组件的简单实现

1,272 阅读1分钟

支持四个方向的抽屉效果,支持弹窗和全屏。

SlideView.tsx

import React, { Component, ReactElement, ReactNode } from 'react';
import { StyleSheet, Animated, Dimensions, ViewStyle } from 'react-native';

type ViewMode = 'fullscreen' | 'modal' | 'side';
type SlideTo = 'left' | 'top' | 'right' | 'bottom';

interface Props {
  slideTo: SlideTo;
  mode: ViewMode;
  visible: boolean;
  onDismiss: any;
  children: ReactNode;
  style: {zIndex?:number, elevation?:number}; 
  modalBackdrop: ReactElement | null;
}

type Style = {
  left: number;
  top: number;
  right: number;
  bottom: number;
  transform?: any;
};

const { width: winWidth, height: winHeight } = Dimensions.get('window');

export default class SlideView extends Component<Props> {
  slideAnim: any;
  startPosition = 0;
  duration = 180;
  style: Style = {
    left: 0,
    top: 0,
    right: 0,
    bottom: 0,
  };

  constructor(props: Props) {
    super(props);

    this.initSlide();
  }

  componentDidUpdate(prevProps: Props) {
    if (this.props.visible !== prevProps.visible && this.props.visible === false) {
      this.slideOut();
    }
  }

  componentDidMount() {
    this.slideIn();
  }

  slideIn = () => {
    Animated.timing(
      this.slideAnim,
      {
        toValue: 0,
        duration: this.duration,
        useNativeDriver: true,
      }
    ).start();
  };

  slideOut = () => {
    Animated.timing(
      this.slideAnim,
      {
        toValue: this.startPosition,
        duration: this.duration,
        useNativeDriver: true,
      }
    ).start(() => {
      this.props.onDismiss();
    });
  };

  initSlide() {
    const sideMode = this.props.mode === 'side';
    const to = this.props.slideTo;
    const translateType = (to === 'left' || to === 'right')
      ? 'translateX'
      : 'translateY';
    let margin = 0;
    let size = winWidth;

    if (to == 'left' || to === 'right') {
      if (sideMode) {
        margin = Math.min(80, size * 0.2);
      }
    } else {
      size = winHeight;
      this.duration = 220;

      if (sideMode) {
        margin = Math.min(100, size * 0.2);
      }
    }

    if (margin) {
      size -= margin;
      this.style[to] = margin;
    }

    this.startPosition = (to === 'right' || to === 'bottom') ? -size : size;
    this.slideAnim = new Animated.Value(this.startPosition);
    this.style.transform = [{ [translateType]: this.slideAnim }];
  }


  render() {
    return (
      <Animated.View style={[this.props.style, this.style]}>
        {this.props.modalBackdrop}
        {this.props.children}
      </Animated.View>
    )
  }
}

const styles = StyleSheet.create({
});

example.tsx

<SlideView
  visible={this.state.visible}
  style={{zIndex:10,elevation:10}}
  modalBackdrop={modalBackdrop}
  onDismiss={this.props.onDismiss}
  mode={this.mode}
  slideTo={this.slideTo}
  children={this.props.children} />