react-native中的toast提示框

Toast

  • 在common文件夹下新建Toast文件夹,把这里面的两个文件放进去(Toast
  • Toast.js
    // Toast.js
    
    'use strict';
    
    import React, { Component } from 'react';
    import { View } from 'react-native';
    
    import Overlay from '../Overlay/Overlay';
    import ToastView from './ToastView';
    
    export default class Toast extends Overlay {
      static ToastView = ToastView;
      static defaultDuration = 'short';
      static defaultPosition = 'center';
      static messageDefaultDuration = 'short';
      static messageDefaultPosition = 'bottom';
    
      static show(options) {
        let { duration, ...others } =
          options && typeof options === 'object' ? options : {};
    
        let key = super.show(<this.ToastView {...others} />);
        if (typeof duration !== 'number') {
          switch (duration) {
            case 'long':
              duration = 3500;
              break;
            default:
              duration = 2000;
              break;
          }
        }
        setTimeout(() => this.hide(key), duration);
    
        return key;
      }
    
      static message(
        text,
        duration = this.messageDefaultDuration,
        position = this.messageDefaultPosition,
      ) {
        return this.show({ text, duration, position });
      }
    
      static success(
        text,
        duration = this.defaultDuration,
        position = this.defaultPosition,
      ) {
        return this.show({ text, duration, position, icon: 'success' });
      }
    
      static fail(
        text,
        duration = this.defaultDuration,
        position = this.defaultPosition,
      ) {
        return this.show({ text, duration, position, icon: 'fail' });
      }
    
      static smile(
        text,
        duration = this.defaultDuration,
        position = this.defaultPosition,
      ) {
        return this.show({ text, duration, position, icon: 'smile' });
      }
    
      static sad(
        text,
        duration = this.defaultDuration,
        position = this.defaultPosition,
      ) {
        return this.show({ text, duration, position, icon: 'sad' });
      }
    
      static info(
        text,
        duration = this.defaultDuration,
        position = this.defaultPosition,
      ) {
        return this.show({ text, duration, position, icon: 'info' });
      }
    
      static stop(
        text,
        duration = this.defaultDuration,
        position = this.defaultPosition,
      ) {
        return this.show({ text, duration, position, icon: 'stop' });
      }
    }
    
    
  • ToastView.js
    // ToastView.js
    
    'use strict';
    
    import React from 'react';
    import PropTypes from 'prop-types';
    import { View, Image, Text } from 'react-native';
    
    import Theme from '../../../themes/Theme';
    import Overlay from '../Overlay/Overlay';
    
    export default class ToastView extends Overlay.View {
      static propTypes = {
        ...Overlay.View.propTypes,
        text: PropTypes.oneOfType([
          PropTypes.element,
          PropTypes.string,
          PropTypes.number,
        ]),
        icon: PropTypes.oneOfType([
          PropTypes.element,
          PropTypes.shape({ uri: PropTypes.string }), //{uri: 'http://...'}
          PropTypes.number, //require('path/to/image')
          PropTypes.oneOf([
            'none',
            'success',
            'fail',
            'smile',
            'sad',
            'info',
            'stop',
          ]),
        ]),
        position: PropTypes.oneOf(['top', 'bottom', 'center']),
      };
    
      static defaultProps = {
        ...Overlay.View.defaultProps,
        overlayOpacity: 0,
        overlayPointerEvents: 'none',
        closeOnHardwareBackPress: false,
        position: 'center',
      };
    
      get overlayPointerEvents() {
        let { overlayPointerEvents, modal } = this.props;
        return modal ? 'auto' : overlayPointerEvents;
      }
    
      buildStyle() {
        let { style, position } = this.props;
        style = [
          {
            paddingLeft: Theme.toastScreenPaddingLeft,
            paddingRight: Theme.toastScreenPaddingRight,
            paddingTop: Theme.toastScreenPaddingTop,
            paddingBottom: Theme.toastScreenPaddingBottom,
            justifyContent:
              position === 'top'
                ? 'flex-start'
                : position === 'bottom'
                ? 'flex-end'
                : 'center',
            alignItems: 'center',
          },
        ].concat(super.buildStyle());
        return style;
      }
    
      renderIcon() {
        let { icon } = this.props;
        if (!icon && icon !== 0) {
          return null;
        }
    
        let image;
        if (!React.isValidElement(icon)) {
          let imageSource;
          if (typeof icon === 'string') {
            switch (icon) {
              case 'success':
                imageSource = require('../../../asserts/icons/success.png');
                break;
              case 'fail':
                imageSource = require('../../../asserts/icons/fail.png');
                break;
              case 'smile':
                imageSource = require('../../../asserts/icons/smile.png');
                break;
              case 'sad':
                imageSource = require('../../../asserts/icons/sad.png');
                break;
              case 'info':
                imageSource = require('../../../asserts/icons/info.png');
                break;
              case 'stop':
                imageSource = require('../../../asserts/icons/stop.png');
                break;
              default:
                imageSource = null;
                break;
            }
          } else {
            imageSource = icon;
          }
          image = (
            <Image
              style={{
                width: Theme.toastIconWidth,
                height: Theme.toastIconHeight,
                tintColor: Theme.toastIconTintColor,
              }}
              source={imageSource}
            />
          );
        } else {
          image = icon;
        }
        return (
          <View
            style={{
              paddingTop: Theme.toastIconPaddingTop,
              paddingBottom: Theme.toastIconPaddingBottom,
            }}
          >
            {image}
          </View>
        );
      }
    
      renderText() {
        let { text } = this.props;
        if (typeof text === 'string' || typeof text === 'number') {
          text = (
            <Text
              style={{ color: Theme.toastTextColor, fontSize: Theme.toastFontSize }}
            >
              {text}
            </Text>
          );
        }
        return text;
      }
    
      renderContent() {
        let contentStyle = {
          backgroundColor: Theme.toastColor,
          paddingLeft: Theme.toastPaddingLeft,
          paddingRight: Theme.toastPaddingRight,
          paddingTop: Theme.toastPaddingTop,
          paddingBottom: Theme.toastPaddingBottom,
          borderRadius: Theme.toastBorderRadius,
          alignItems: 'center',
        };
        return (
          <View style={contentStyle}>
            {this.renderIcon()}
            {this.renderText()}
          </View>
        );
      }
    }
    

Overlay

  • 因为Toast需要Overlay,所以还得把Overlay也引入
  • 在common文件夹下新建文件夹Overlay
  • Overlay.js
// Overlay.js
'use strict';

import React, { Component } from 'react';
import { View } from 'react-native';

import TopView from './TopView';
import OverlayView from './OverlayView';
import OverlayPullView from './OverlayPullView';
import OverlayPopView from './OverlayPopView';
import OverlayPopoverView from './OverlayPopoverView';

export default class Overlay {
  static View = OverlayView;
  static PullView = OverlayPullView;
  static PopView = OverlayPopView;
  static PopoverView = OverlayPopoverView;

  // base props
  //   style: ViewPropTypes.style,
  //   modal: PropTypes.bool,
  //   animated: PropTypes.bool,
  //   overlayOpacity: PropTypes.number,
  //   overlayPointerEvents: ViewPropTypes.pointerEvents,
  static show(overlayView) {
    let key;
    let onDisappearCompletedSave = overlayView.props.onDisappearCompleted;
    let element = React.cloneElement(overlayView, {
      onDisappearCompleted: () => {
        TopView.remove(key);
        onDisappearCompletedSave && onDisappearCompletedSave();
      },
    });
    key = TopView.add(element);
    return key;
  }

  static hide(key) {
    TopView.remove(key);
  }

  static transformRoot(transform, animated, animatesOnly = null) {
    TopView.transform(transform, animated, animatesOnly);
  }

  static restoreRoot(animated, animatesOnly = null) {
    TopView.restore(animated, animatesOnly);
  }
}
  • OverlayPopoverView.js
// OverlayPopoverView.js

'use strict';

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { View, Dimensions, Platform, StatusBar } from 'react-native';

import OverlayView from './OverlayView';
import Popover from '../Popover/Popover';

export default class OverlayPopoverView extends OverlayView {
  static propTypes = {
    ...OverlayView.propTypes,
    popoverStyle: Popover.propTypes.style,
    fromBounds: PropTypes.shape({
      x: PropTypes.number.isRequired,
      y: PropTypes.number.isRequired,
      width: PropTypes.number,
      height: PropTypes.number,
    }).isRequired,
    direction: PropTypes.oneOf(['down', 'up', 'right', 'left']),
    autoDirection: PropTypes.bool, //down -> up, or right -> left
    directionInsets: PropTypes.number,
    align: PropTypes.oneOf(['start', 'center', 'end']),
    alignInsets: PropTypes.number,
    showArrow: PropTypes.bool,
    paddingCorner: Popover.propTypes.paddingCorner,
  };

  static defaultProps = {
    ...OverlayView.defaultProps,
    overlayOpacity: 0,
    direction: 'down',
    autoDirection: true,
    align: 'end',
    showArrow: true,
  };

  constructor(props) {
    super(props);
    Object.assign(this.state, {
      fromBounds: props.fromBounds,
      popoverWidth: null,
      popoverHeight: null,
    });
    this.defaultDirectionInsets = 0;
  }

  componentDidUpdate(prevProps, prevState, snapshot) {
    super.componentDidUpdate &&
      super.componentDidUpdate(prevProps, prevState, snapshot);
    if (
      JSON.stringify(this.props.fromBounds) !==
      JSON.stringify(this.state.fromBounds)
    ) {
      this.setState({ fromBounds: this.props.fromBounds });
    }
  }

  updateFromBounds(bounds) {
    this.setState({ fromBounds: bounds });
  }

  onPopoverLayout(e) {
    if (
      Platform.OS === 'android' &&
      (this.state.popoverWidth !== null || this.state.popoverHeight != null)
    ) {
      //android calls many times...
      return;
    }
    let { width, height } = e.nativeEvent.layout;
    if (
      width !== this.state.popoverWidth ||
      height !== this.state.popoverHeight
    ) {
      this.setState({ popoverWidth: width, popoverHeight: height });
    }
  }

  buildPopoverStyle() {
    let { fromBounds, popoverWidth, popoverHeight } = this.state;
    let {
      popoverStyle,
      direction,
      autoDirection,
      directionInsets,
      align,
      alignInsets,
      showArrow,
      arrow,
    } = this.props;
    if (popoverWidth === null || popoverHeight === null) {
      popoverStyle = []
        .concat(popoverStyle)
        .concat({ position: 'absolute', left: 0, top: 0, opacity: 0 });
      if (!showArrow) {
        arrow = 'none';
      } else {
        switch (direction) {
          case 'right':
            arrow = 'left';
            break;
          case 'left':
            arrow = 'right';
            break;
          case 'up':
            arrow = 'bottom';
            break;
          default:
            arrow = 'top';
            break;
        }
      }
      return { popoverStyle, arrow };
    }

    let screenWidth = Dimensions.get('window').width;
    let screenHeight = Dimensions.get('window').height;
    let { x, y, width, height } = fromBounds ? fromBounds : {};

    if (!x && x !== 0) {
      x = screenWidth / 2;
    }
    if (!y && y !== 0) {
      y = screenHeight / 2;
    }
    if (!width) {
      width = 0;
    }
    if (!height) {
      height = 0;
    }
    if (!directionInsets && directionInsets !== 0) {
      directionInsets = this.defaultDirectionInsets;
    }
    if (!alignInsets) {
      alignInsets = 0;
    }

    //auto direction
    let ph = popoverHeight + directionInsets;
    let pw = popoverWidth + directionInsets;
    switch (direction) {
      case 'right':
        if (autoDirection && x + width + pw > screenWidth && x >= pw) {
          direction = 'left';
        }
        break;
      case 'left':
        if (autoDirection && x + width + pw <= screenWidth && x < pw) {
          direction = 'right';
        }
        break;
      case 'up':
        if (autoDirection && y + height + ph <= screenHeight && y < ph) {
          direction = 'down';
        }
        break;
      default:
        if (autoDirection && y + height + ph > screenHeight && y >= ph) {
          direction = 'up';
        }
        break;
    }

    //calculate popover top-left position and arrow type
    let px, py;
    switch (direction) {
      case 'right':
        px = x + width + directionInsets;
        arrow = 'left';
        break;
      case 'left':
        px = x - popoverWidth - directionInsets;
        arrow = 'right';
        break;
      case 'up':
        py = y - popoverHeight - directionInsets;
        arrow = 'bottom';
        break;
      default:
        py = y + height + directionInsets;
        arrow = 'top';
        break;
    }
    if (direction == 'down' || direction == 'up') {
      switch (align) {
        case 'start':
          px = x - alignInsets;
          arrow += 'Left';
          break;
        case 'center':
          px = x + width / 2 - popoverWidth / 2;
          break;
        default:
          px = x + width - popoverWidth + alignInsets;
          arrow += 'Right';
          break;
      }
    } else if (direction == 'right' || direction == 'left') {
      switch (align) {
        case 'end':
          py = y + height - popoverHeight + alignInsets;
          arrow += 'Bottom';
          break;
        case 'center':
          py = y + height / 2 - popoverHeight / 2;
          break;
        default:
          py = y - alignInsets;
          arrow += 'Top';
          break;
      }
    }

    popoverStyle = [].concat(popoverStyle).concat({
      position: 'absolute',
      left: px,
      top: py,
    });
    if (!showArrow) {
      arrow = 'none';
    }

    return { popoverStyle, arrow };
  }

  renderContent(content = null) {
    let { paddingCorner, children } = this.props;
    let { popoverStyle, arrow } = this.buildPopoverStyle();
    return (
      <Popover
        style={popoverStyle}
        arrow={arrow}
        paddingCorner={paddingCorner}
        onLayout={(e) => this.onPopoverLayout(e)}
      >
        {content ? content : children}
      </Popover>
    );
  }
}
  • OverlayPopView.js
// OverlayPopView.js

'use strict';

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Animated, View, ViewPropTypes } from 'react-native';

import OverlayView from './OverlayView';

export default class OverlayPopView extends OverlayView {
  static propTypes = {
    ...OverlayView.propTypes,
    type: PropTypes.oneOf(['zoomOut', 'zoomIn', 'custom']),
    containerStyle: ViewPropTypes.style,
    customBounds: PropTypes.shape({
      x: PropTypes.number.isRequired,
      y: PropTypes.number.isRequired,
      width: PropTypes.number.isRequired,
      height: PropTypes.number.isRequired,
    }),
  };

  static defaultProps = {
    ...OverlayView.defaultProps,
    type: 'zoomOut',
    animated: true,
  };

  constructor(props) {
    super(props);
    this.viewLayout = { x: 0, y: 0, width: 0, height: 0 };
    Object.assign(this.state, {
      opacity: new Animated.Value(1),
      translateX: new Animated.Value(0),
      translateY: new Animated.Value(0),
      scaleX: new Animated.Value(1),
      scaleY: new Animated.Value(1),
      showed: false,
    });
  }

  get appearAnimates() {
    let animates = super.appearAnimates;
    let duration = 200;
    animates = animates.concat([
      Animated.timing(this.state.opacity, {
        toValue: 1,
        duration,
        useNativeDriver: false,
      }),
      Animated.timing(this.state.translateX, {
        toValue: 0,
        duration,
        useNativeDriver: false,
      }),
      Animated.timing(this.state.translateY, {
        toValue: 0,
        duration,
        useNativeDriver: false,
      }),
      Animated.timing(this.state.scaleX, {
        toValue: 1,
        duration,
        useNativeDriver: false,
      }),
      Animated.timing(this.state.scaleY, {
        toValue: 1,
        duration,
        useNativeDriver: false,
      }),
    ]);
    return animates;
  }

  get disappearAnimates() {
    let animates = super.disappearAnimates;
    let duration = 200;
    let ft = this.fromTransform;
    animates = animates.concat([
      Animated.timing(this.state.opacity, {
        toValue: 0,
        duration,
        useNativeDriver: false,
      }),
      Animated.timing(this.state.translateX, {
        toValue: ft.translateX,
        duration,
        useNativeDriver: false,
      }),
      Animated.timing(this.state.translateY, {
        toValue: ft.translateY,
        duration,
        useNativeDriver: false,
      }),
      Animated.timing(this.state.scaleX, {
        toValue: ft.scaleX,
        duration,
        useNativeDriver: false,
      }),
      Animated.timing(this.state.scaleY, {
        toValue: ft.scaleY,
        duration,
        useNativeDriver: false,
      }),
    ]);
    return animates;
  }

  get appearAfterMount() {
    return false;
  }

  get fromBounds() {
    let { type, customBounds } = this.props;
    let bounds;
    if (type === 'custom' && !customBounds) {
      console.error(
        'OverlayPopView: customBounds can not be null when type is "custom"',
      );
    }
    if (type === 'custom' && customBounds) {
      bounds = customBounds;
    } else {
      let zoomRate = type === 'zoomIn' ? 0.3 : 1.2;
      let { x, y, width, height } = this.viewLayout;
      bounds = {
        x: x - (width * zoomRate - width) / 2,
        y: y - (height * zoomRate - height) / 2,
        width: width * zoomRate,
        height: height * zoomRate,
      };
    }
    return bounds;
  }

  get fromTransform() {
    let fb = this.fromBounds;
    let tb = this.viewLayout;
    let transform = {
      translateX: fb.x + fb.width / 2 - (tb.x + tb.width / 2),
      translateY: fb.y + fb.height / 2 - (tb.y + tb.height / 2),
      scaleX: fb.width / tb.width,
      scaleY: fb.height / tb.height,
    };
    return transform;
  }

  appear(animated = this.props.animated) {
    if (animated) {
      let { opacity, translateX, translateY, scaleX, scaleY } = this.state;
      let ft = this.fromTransform;
      opacity.setValue(0);
      translateX.setValue(ft.translateX);
      translateY.setValue(ft.translateY);
      scaleX.setValue(ft.scaleX);
      scaleY.setValue(ft.scaleY);
    }
    super.appear(animated);
  }

  onLayout(e) {
    this.viewLayout = e.nativeEvent.layout;
    if (!this.state.showed) {
      this.setState({ showed: true });
      this.appear();
    }
  }

  renderContent(content = null) {
    let { containerStyle, children } = this.props;
    let { opacity, translateX, translateY, scaleX, scaleY } = this.state;

    containerStyle = [
      {
        backgroundColor: 'rgba(0, 0, 0, 0)',
        minWidth: 1,
        minHeight: 1,
      },
    ]
      .concat(containerStyle)
      .concat({
        opacity: this.state.showed ? opacity : 0,
        transform: [{ translateX }, { translateY }, { scaleX }, { scaleY }],
      });

    return (
      <Animated.View
        style={containerStyle}
        pointerEvents="box-none"
        onLayout={(e) => this.onLayout(e)}
      >
        {content ? content : children}
      </Animated.View>
    );
  }
}

  • OverlayPullView.js
// OverlayPullView.js

'use strict';

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Animated, View, ViewPropTypes } from 'react-native';

import Theme from '../../../themes/Theme';
import TopView from './TopView';
import OverlayView from './OverlayView';

export default class OverlayPullView extends OverlayView {
  static propTypes = {
    ...OverlayView.propTypes,
    side: PropTypes.oneOf(['top', 'bottom', 'left', 'right']),
    containerStyle: ViewPropTypes.style,
    rootTransform: PropTypes.oneOfType([
      PropTypes.oneOf(['none', 'translate', 'scale']),
      PropTypes.arrayOf(
        PropTypes.shape({
          translateX: PropTypes.number,
          translateY: PropTypes.number,
          scaleX: PropTypes.number,
          scaleY: PropTypes.number,
        }),
      ),
    ]),
  };

  static defaultProps = {
    ...OverlayView.defaultProps,
    side: 'bottom',
    animated: true,
    rootTransform: 'none',
  };

  constructor(props) {
    super(props);
    this.viewLayout = { x: 0, y: 0, width: 0, height: 0 };
    Object.assign(this.state, {
      marginValue: new Animated.Value(0),
      showed: false,
    });
  }

  get appearAnimates() {
    let animates = super.appearAnimates;
    animates.push(
      Animated.spring(this.state.marginValue, {
        toValue: 0,
        friction: 9,
        useNativeDriver: false,
      }),
    );
    return animates;
  }

  get disappearAnimates() {
    let animates = super.disappearAnimates;
    animates.push(
      Animated.spring(this.state.marginValue, {
        toValue: this.marginSize,
        friction: 9,
        useNativeDriver: false,
      }),
    );
    return animates;
  }

  get appearAfterMount() {
    return false;
  }

  get marginSize() {
    let { side } = this.props;
    if (side === 'left' || side === 'right') {
      return -this.viewLayout.width;
    } else {
      return -this.viewLayout.height;
    }
  }

  get rootTransformValue() {
    let { side, rootTransform } = this.props;
    if (!rootTransform || rootTransform === 'none') {
      return [];
    }
    let transform;
    switch (rootTransform) {
      case 'translate':
        switch (side) {
          case 'top':
            return [{ translateY: this.viewLayout.height }];
          case 'left':
            return [{ translateX: this.viewLayout.width }];
          case 'right':
            return [{ translateX: -this.viewLayout.width }];
          default:
            return [{ translateY: -this.viewLayout.height }];
        }
        break;
      case 'scale':
        return [
          { scaleX: Theme.overlayRootScale },
          { scaleY: Theme.overlayRootScale },
        ];
      default:
        return rootTransform;
    }
  }

  appear(animated = this.props.animated) {
    if (animated) {
      this.state.marginValue.setValue(this.marginSize);
    }
    super.appear(animated);

    let { rootTransform } = this.props;
    if (rootTransform && rootTransform !== 'none') {
      TopView.transform(this.rootTransformValue, animated);
    }
  }

  disappear(animated = this.props.animated) {
    let { rootTransform } = this.props;
    if (rootTransform && rootTransform !== 'none') {
      TopView.restore(animated);
    }

    super.disappear(animated);
  }

  onLayout(e) {
    this.viewLayout = e.nativeEvent.layout;
    if (!this.state.showed) {
      this.setState({ showed: true });
      this.appear();
    }
  }

  buildStyle() {
    let { side } = this.props;
    let sideStyle;
    //Set flexDirection so that the content view will fill the side
    switch (side) {
      case 'top':
        sideStyle = {
          flexDirection: 'column',
          justifyContent: 'flex-start',
          alignItems: 'stretch',
        };
        break;
      case 'left':
        sideStyle = {
          flexDirection: 'row',
          justifyContent: 'flex-start',
          alignItems: 'stretch',
        };
        break;
      case 'right':
        sideStyle = {
          flexDirection: 'row',
          justifyContent: 'flex-end',
          alignItems: 'stretch',
        };
        break;
      default:
        sideStyle = {
          flexDirection: 'column',
          justifyContent: 'flex-end',
          alignItems: 'stretch',
        };
    }
    return super.buildStyle().concat(sideStyle);
  }

  renderContent(content = null) {
    let { side, containerStyle, children } = this.props;

    let contentStyle;
    switch (side) {
      case 'top':
        contentStyle = { marginTop: this.state.marginValue };
        break;
      case 'left':
        contentStyle = { marginLeft: this.state.marginValue };
        break;
      case 'right':
        contentStyle = { marginRight: this.state.marginValue };
        break;
      default:
        contentStyle = { marginBottom: this.state.marginValue };
    }
    contentStyle.opacity = this.state.showed ? 1 : 0;
    containerStyle = [
      {
        backgroundColor: Theme.defaultColor,
      },
    ]
      .concat(containerStyle)
      .concat(contentStyle);

    return (
      <Animated.View style={containerStyle} onLayout={(e) => this.onLayout(e)}>
        {content ? content : children}
      </Animated.View>
    );
  }
}

  • OverlayView.js
// OverlayView.js

'use strict';

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import ReactNative, {
  StyleSheet,
  Animated,
  View,
  PanResponder,
  Platform,
  ViewPropTypes,
} from 'react-native';

import Theme from '../../../themes/Theme';
import KeyboardSpace from '../KeyboardSpace/KeyboardSpace';

export default class OverlayView extends Component {
  static propTypes = {
    style: ViewPropTypes.style,
    modal: PropTypes.bool,
    animated: PropTypes.bool,
    overlayOpacity: PropTypes.number,
    overlayPointerEvents: ViewPropTypes.pointerEvents,
    autoKeyboardInsets: PropTypes.bool,
    closeOnHardwareBackPress: PropTypes.bool, //android only
    onAppearCompleted: PropTypes.func,
    onDisappearCompleted: PropTypes.func,
    onCloseRequest: PropTypes.func, //(overlayView)
  };

  static defaultProps = {
    modal: false,
    animated: false,
    overlayPointerEvents: 'auto',
    autoKeyboardInsets: false,
    closeOnHardwareBackPress: true,
  };

  constructor(props) {
    super(props);
    this.panResponder = PanResponder.create({
      onStartShouldSetPanResponder: (e, gestureState) => true,
      onPanResponderGrant: (e, gestureState) =>
        (this.touchStateID = gestureState.stateID),
      onPanResponderRelease: (e, gestureState) =>
        this.touchStateID == gestureState.stateID ? this.closeRequest() : null,
    });
    this.state = {
      overlayOpacity: new Animated.Value(0),
    };
  }

  componentDidMount() {
    this.appearAfterMount && this.appear();
    if (Platform.OS === 'android') {
      let BackHandler = ReactNative.BackHandler
        ? ReactNative.BackHandler
        : ReactNative.BackAndroid;
      this.backListener = BackHandler.addEventListener(
        'hardwareBackPress',
        () => {
          if (this.props.closeOnHardwareBackPress) {
            this.closeRequest();
            return true;
          } else {
            return false;
          }
        },
      );
    }
  }

  componentWillUnmount() {
    this.removeBackListener();
  }

  removeBackListener() {
    if (this.backListener) {
      this.backListener.remove();
      this.backListener = null;
    }
  }

  get overlayOpacity() {
    let { overlayOpacity } = this.props;
    return overlayOpacity || overlayOpacity === 0
      ? overlayOpacity
      : Theme.overlayOpacity;
  }

  get appearAnimates() {
    let duration = 200;
    let animates = [
      Animated.timing(this.state.overlayOpacity, {
        toValue: this.overlayOpacity,
        duration,
        useNativeDriver: false,
      }),
    ];
    return animates;
  }

  get disappearAnimates() {
    let duration = 200;
    let animates = [
      Animated.timing(this.state.overlayOpacity, {
        toValue: 0,
        duration,
        useNativeDriver: false,
      }),
    ];
    return animates;
  }

  get appearAfterMount() {
    return true;
  }

  get overlayPointerEvents() {
    //override in Toast
    return this.props.overlayPointerEvents;
  }

  appear(animated = this.props.animated, additionAnimates = null) {
    if (animated) {
      this.state.overlayOpacity.setValue(0);
      Animated.parallel(
        this.appearAnimates.concat(additionAnimates),
      ).start((e) => this.appearCompleted());
    } else {
      this.state.overlayOpacity.setValue(this.overlayOpacity);
      this.appearCompleted();
    }
  }

  disappear(animated = this.props.animated, additionAnimates = null) {
    if (animated) {
      Animated.parallel(
        this.disappearAnimates.concat(additionAnimates),
      ).start((e) => this.disappearCompleted());
      this.state.overlayOpacity.addListener((e) => {
        if (e.value < 0.01) {
          this.state.overlayOpacity.stopAnimation();
          this.state.overlayOpacity.removeAllListeners();
        }
      });
    } else {
      this.disappearCompleted();
    }
  }

  appearCompleted() {
    let { onAppearCompleted } = this.props;
    onAppearCompleted && onAppearCompleted();
  }

  disappearCompleted() {
    let { onDisappearCompleted } = this.props;
    onDisappearCompleted && onDisappearCompleted();
  }

  close(animated = this.props.animated) {
    if (this.closed) {
      return true;
    }
    this.closed = true;
    this.removeBackListener();
    this.disappear(animated);
    return true;
  }

  closeRequest() {
    let { modal, onCloseRequest } = this.props;
    if (onCloseRequest) {
      onCloseRequest(this);
    } else if (!modal) {
      this.close();
    }
  }

  buildStyle() {
    let { style } = this.props;
    style = [{ backgroundColor: 'rgba(0, 0, 0, 0)', flex: 1 }].concat(style);
    return style;
  }

  renderContent() {
    return this.props.children;
  }

  render() {
    let { autoKeyboardInsets } = this.props;
    return (
      <View style={styles.screen} pointerEvents={this.overlayPointerEvents}>
        <Animated.View
          style={[
            styles.screen,
            { backgroundColor: '#000', opacity: this.state.overlayOpacity },
          ]}
          {...this.panResponder.panHandlers}
        />
        <View style={this.buildStyle()} pointerEvents="box-none">
          {this.renderContent()}
        </View>
        {autoKeyboardInsets ? <KeyboardSpace /> : null}
      </View>
    );
  }
}

var styles = StyleSheet.create({
  screen: {
    backgroundColor: 'rgba(0, 0, 0, 0)',
    position: 'absolute',
    top: 0,
    left: 0,
    right: 0,
    bottom: 0,
  },
});

  • TopView.js
// TopView.js

'use strict';

import React, { Component, PureComponent } from 'react';
import {
  StyleSheet,
  AppRegistry,
  DeviceEventEmitter,
  View,
  Animated,
} from 'react-native';
import PropTypes from 'prop-types';

import Theme from '../../../themes/Theme';

let keyValue = 0;

export default class TopView extends Component {
  static add(element) {
    let key = ++keyValue;
    DeviceEventEmitter.emit('addOverlay', { key, element });
    return key;
  }

  static remove(key) {
    DeviceEventEmitter.emit('removeOverlay', { key });
  }

  static removeAll() {
    DeviceEventEmitter.emit('removeAllOverlay', {});
  }

  static transform(transform, animated, animatesOnly = null) {
    DeviceEventEmitter.emit('transformRoot', {
      transform,
      animated,
      animatesOnly,
    });
  }

  static restore(animated, animatesOnly = null) {
    DeviceEventEmitter.emit('restoreRoot', { animated, animatesOnly });
  }

  constructor(props) {
    super(props);
    this.state = {
      elements: [],
      translateX: new Animated.Value(0),
      translateY: new Animated.Value(0),
      scaleX: new Animated.Value(1),
      scaleY: new Animated.Value(1),
    };
    this.handlers = [];
  }

  static contextTypes = {
    registerTopViewHandler: PropTypes.func,
    unregisterTopViewHandler: PropTypes.func,
  };

  static childContextTypes = {
    registerTopViewHandler: PropTypes.func,
    unregisterTopViewHandler: PropTypes.func,
  };

  getChildContext() {
    let { registerTopViewHandler, unregisterTopViewHandler } = this.context;
    if (!registerTopViewHandler) {
      registerTopViewHandler = (handler) => {
        this.handlers.push(handler);
      };
      unregisterTopViewHandler = (handler) => {
        for (let i = this.handlers.length - 1; i >= 0; --i) {
          if (this.handlers[i] === handler) {
            this.handlers.splice(i, 1);
            return true;
          }
        }
        return false;
      };
    }
    return { registerTopViewHandler, unregisterTopViewHandler };
  }

  get handler() {
    return this.handlers.length > 0
      ? this.handlers[this.handlers.length - 1]
      : this;
  }

  componentDidMount() {
    let { registerTopViewHandler } = this.context;
    if (registerTopViewHandler) {
      registerTopViewHandler(this);
      return;
    }

    DeviceEventEmitter.addListener('addOverlay', (e) => this.handler.add(e));
    DeviceEventEmitter.addListener('removeOverlay', (e) =>
      this.handler.remove(e),
    );
    DeviceEventEmitter.addListener('removeAllOverlay', (e) =>
      this.handler.removeAll(e),
    );
    DeviceEventEmitter.addListener('transformRoot', (e) =>
      this.handler.transform(e),
    );
    DeviceEventEmitter.addListener('restoreRoot', (e) =>
      this.handler.restore(e),
    );
  }

  componentWillUnmount() {
    let { unregisterTopViewHandler } = this.context;
    if (unregisterTopViewHandler) {
      unregisterTopViewHandler(this);
      return;
    }

    DeviceEventEmitter.removeAllListeners('addOverlay');
    DeviceEventEmitter.removeAllListeners('removeOverlay');
    DeviceEventEmitter.removeAllListeners('removeAllOverlay');
    DeviceEventEmitter.removeAllListeners('transformRoot');
    DeviceEventEmitter.removeAllListeners('restoreRoot');
  }

  add(e) {
    let { elements } = this.state;
    elements.push(e);
    this.setState({ elements });
  }

  remove(e) {
    let { elements } = this.state;
    for (let i = elements.length - 1; i >= 0; --i) {
      if (elements[i].key === e.key) {
        elements.splice(i, 1);
        break;
      }
    }
    this.setState({ elements });
  }

  removeAll(e) {
    let { elements } = this.state;
    this.setState({ elements: [] });
  }

  transform(e) {
    let { translateX, translateY, scaleX, scaleY } = this.state;
    let { transform, animated, animatesOnly } = e;
    let tx = 0,
      ty = 0,
      sx = 1,
      sy = 1;
    transform.map((item) => {
      if (item && typeof item === 'object') {
        for (let name in item) {
          let value = item[name];
          switch (name) {
            case 'translateX':
              tx = value;
              break;
            case 'translateY':
              ty = value;
              break;
            case 'scaleX':
              sx = value;
              break;
            case 'scaleY':
              sy = value;
              break;
          }
        }
      }
    });
    if (animated) {
      let animates = [
        Animated.spring(translateX, {
          toValue: tx,
          friction: 9,
          useNativeDriver: false,
        }),
        Animated.spring(translateY, {
          toValue: ty,
          friction: 9,
          useNativeDriver: false,
        }),
        Animated.spring(scaleX, {
          toValue: sx,
          friction: 9,
          useNativeDriver: false,
        }),
        Animated.spring(scaleY, {
          toValue: sy,
          friction: 9,
          useNativeDriver: false,
        }),
      ];
      animatesOnly
        ? animatesOnly(animates)
        : Animated.parallel(animates).start();
    } else {
      if (animatesOnly) {
        let animates = [
          Animated.timing(translateX, {
            toValue: tx,
            duration: 1,
            useNativeDriver: false,
          }),
          Animated.timing(translateY, {
            toValue: ty,
            duration: 1,
            useNativeDriver: false,
          }),
          Animated.timing(scaleX, {
            toValue: sx,
            duration: 1,
            useNativeDriver: false,
          }),
          Animated.timing(scaleY, {
            toValue: sy,
            duration: 1,
            useNativeDriver: false,
          }),
        ];
        animatesOnly(animates);
      } else {
        translateX.setValue(tx);
        translateY.setValue(ty);
        scaleX.setValue(sx);
        scaleY.setValue(sy);
      }
    }
  }

  restore(e) {
    let { translateX, translateY, scaleX, scaleY } = this.state;
    let { animated, animatesOnly } = e;
    if (animated) {
      let animates = [
        Animated.spring(translateX, {
          toValue: 0,
          friction: 9,
          useNativeDriver: false,
        }),
        Animated.spring(translateY, {
          toValue: 0,
          friction: 9,
          useNativeDriver: false,
        }),
        Animated.spring(scaleX, {
          toValue: 1,
          friction: 9,
          useNativeDriver: false,
        }),
        Animated.spring(scaleY, {
          toValue: 1,
          friction: 9,
          useNativeDriver: false,
        }),
      ];
      animatesOnly
        ? animatesOnly(animates)
        : Animated.parallel(animates).start();
    } else {
      if (animatesOnly) {
        let animates = [
          Animated.timing(translateX, {
            toValue: 0,
            duration: 1,
            useNativeDriver: false,
          }),
          Animated.timing(translateY, {
            toValue: 0,
            duration: 1,
            useNativeDriver: false,
          }),
          Animated.timing(scaleX, {
            toValue: 1,
            duration: 1,
            useNativeDriver: false,
          }),
          Animated.timing(scaleY, {
            toValue: 1,
            duration: 1,
            useNativeDriver: false,
          }),
        ];
        animatesOnly(animates);
      } else {
        translateX.setValue(0);
        translateY.setValue(0);
        scaleX.setValue(1);
        scaleY.setValue(1);
      }
    }
  }

  render() {
    let { elements, translateX, translateY, scaleX, scaleY } = this.state;
    let transform = [{ translateX }, { translateY }, { scaleX }, { scaleY }];
    return (
      <View style={{ backgroundColor: Theme.screenColor, flex: 1 }}>
        <Animated.View style={{ flex: 1, transform: transform }}>
          <PureView>{this.props.children}</PureView>
        </Animated.View>
        {elements.map((item, index) => {
          return (
            <View
              key={'topView' + item.key}
              style={styles.overlay}
              pointerEvents="box-none"
            >
              {item.element}
            </View>
          );
        })}
      </View>
    );
  }
}

var styles = StyleSheet.create({
  overlay: {
    backgroundColor: 'rgba(0, 0, 0, 0)',
    position: 'absolute',
    top: 0,
    left: 0,
    right: 0,
    bottom: 0,
  },
});

class PureView extends PureComponent {
  render() {
    return <View style={{ flex: 1 }}>{this.props.children}</View>;
  }
}

if (!AppRegistry.registerComponentOld) {
  AppRegistry.registerComponentOld = AppRegistry.registerComponent;
}

AppRegistry.registerComponent = function (appKey, componentProvider) {
  class RootElement extends Component {
    render() {
      let Component = componentProvider();
      return (
        <TopView>
          <Component {...this.props} />
        </TopView>
      );
    }
  }

  return AppRegistry.registerComponentOld(appKey, () => RootElement);
};

自己创建的Toast等待中的提示

import React from "react";
import { ActivityIndicator } from "react-native";
import {Toast,Theme  } from "teaset";

let customKey = null;

// 显示转圈圈
Toast.showLoading=(text)=> {
  if (customKey) return;
  customKey = Toast.show({
    // 转圈圈里面的文本
    text,
    // ActivityIndicator 就是那个转圈圈的
    icon: <ActivityIndicator size='large' color={Theme.toastIconTintColor} />,
    position: 'center',
    duration: 100000,
  });
}

// 隐藏转圈圈
Toast.hideLoading=()=> {
  if (!customKey) return;
  Toast.hide(customKey);
  customKey = null;
}

export default Toast;

// ————————————————————————————————————————————————————————————————————————
// 示例:
// 在react-native中想要看到一些弹窗提示,把调试关掉ctrl + m
// Toast.message('Taooo', 10000);
// Toast.message("验证码不正确",2000,"center");

如何使用刚才导入的Toast

  • 有message、success、fail、smile、sad、info、stop、这几种提示类型
  • 示例
import Toast from '../...'
// 位置有'top', 'bottom', 'center'这几种
Toast.message('提示文字信息', '提示时长', '位置');
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值