React Native Modal 的封装与使用实例详解

目录
  • 背景
  • Android FullScreenModal 的封装使用
    • Android 原生实现全屏 Dialog
    • 封装给 RN 进行相关的调用
    • Android 原生部分实现
    • JS 部分实现
  • 使用 RootSiblings 封装 Modal
    • 实现界面 Render 相关
    • 实现 Modal 展示动画相关
  • 使用 View 封装 Modal
  • 整体 Modal 控件的封装
  • 其他
    • Android Back 键的注意
    • View 封装 Modal 时候的注意
  • 最后

背景

在使用 React Native(以下简称 RN ,使用版本为 0.59.5) 开发 App 的过程中,有许许多多使用到弹窗控件的场景,虽然 RN 自带了一个 Modal 控件,但是在使用过程中它有一些不太好的体验和问题。

  • Android 端的 Modal 控件无法全屏,也就是内容无法从状态栏处开始布局。
  • ios 端的 Modal 控件的层级太高,是基于 window 的,如果在 Modal 中打开一个新的 ViewController 界面的时候,将会被 Modal 控件给覆盖住,同时 ios 的 Modal 控件只能弹出一个。

针对上面所发现的问题,我们需要对 RN 的 Modal 控件整体做一个修改和封装,以便于在使用中可以应对各种不同样的业务场景。

Android FullScreenModal 的封装使用

针对第一个问题,查看了 RN Modal 组件在 Android 端的实现,发现它是对 Android Dialog 组件的一个封装调用,那么假如我能实现一个全屏展示的 Dialog,那么是不是在 RN 上也就可以实现全屏弹窗了。

Android 原生实现全屏 Dialog

FullScreenDialog 主要实现代码如下

public class FullScreenDialog extends Dialog {

    private boolean isDarkMode;
    private View rootView;

    public void setDarkMode(boolean isDarkMode) {
        this.isDarkMode = isDarkMode;
    }

    public FullScreenDialog(@NonNull Context context, @StyleRes int themeResId) {
        super(context, themeResId);
    }

    @Override
    public void setContentView(@NonNull View view) {
        super.setContentView(view);
        this.rootView = view;
    }

    @Override
    public void show() {
        super.show();
        StatusBarUtil.setTransparent(getWindow());
        if (isDarkMode) {
            StatusBarUtil.setDarkMode(getWindow());
        } else {
            StatusBarUtil.setLightMode(getWindow());
        }
        AndroidBug5497Workaround.assistView(rootView, getWindow());
    }
}

在这里主要起作用的是 StatusBarUtil.setTransparent(getWindow()); 方法,它的主要作用是将状态栏背景透明,并且让布局内容可以从 Android 状态栏开始。

/**
  * 使状态栏透明,并且是从状态栏处开始布局
  */
 @TargetApi(Build.VERSION_CODES.KITKAT)
 private static void transparentStatusBar(Window window) {
     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
         View decorView = window.getDecorView();
         int option = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
         decorView.setSystemUiVisibility(option);
         window.setStatusBarColor(Color.TRANSPARENT);
     } else {
         window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
     }
 }

这里需要注意的是,该方法只有在 Android 4.4 以上才会有效果,不过如今已经是 9012 年了,主流 Android 用户使用的版本应该没有低于 Android 4.4 了吧。

封装给 RN 进行相关的调用

Android 原生部分实现

有了 FullScreenDialog ,下一步就是封装组件给 RN 进行调用了,这里主要的步骤就是参考 RN Modal 的 Android 端实现,然后替换其中的 Dialog 为 FullScreenDialog,最后封装给 RN 进行调用。

public class FullScreenModalManager extends ViewGroupManager<FullScreenModalView> {
    @Override
    public String getName() {
        return "RCTFullScreenModalHostView";
    }
    public enum Events {
        ON_SHOW("onFullScreenShow"),
        ON_REQUEST_CLOSE("onFullScreenRequstClose");
        private final String mName;
        Events(final String name) {
            mName = name;
        }
        @Override
        public String toString() {
            return mName;
        }
    }
    @Override
    @Nullable
    public Map getExportedCustomDirectEventTypeConstants() {
        MapBuilder.Builder builder = MapBuilder.builder();
        for (Events event : Events.values()) {
            builder.put(event.toString(), MapBuilder.of("registrationName", event.toString()));
        }
        return builder.build();
    }
    @Override
    protected FullScreenModalView createViewInstance(ThemedReactContext reactContext) {
        final FullScreenModalView view = new FullScreenModalView(reactContext);
        final RCTEventEmitter mEventEmitter = reactContext.getJSModule(RCTEventEmitter.class);
        view.setOnRequestCloseListener(new FullScreenModalView.OnRequestCloseListener() {
            @Override
            public void onRequestClose(DialogInterface dialog) {
                mEventEmitter.receiveEvent(view.getId(), Events.ON_REQUEST_CLOSE.toString(), null);
            }
        });
        view.setOnShowListener(new DialogInterface.OnShowListener() {
            @Override
            public void onShow(DialogInterface dialog) {
                mEventEmitter.receiveEvent(view.getId(), Events.ON_SHOW.toString(), null);
            }
        });
        return view;
    }
    @Override
    public LayoutShadowNode createShadowNodeInstance() {
        return new FullScreenModalHostShadowNode();
    }
    @Override
    public Class<? extends LayoutShadowNode> getShadowNodeClass() {
        return FullScreenModalHostShadowNode.class;
    }
    @Override
    public void onDropViewInstance(FullScreenModalView view) {
        super.onDropViewInstance(view);
        view.onDropInstance();
    }
    @ReactProp(name = "autoKeyboard")
    public void setAutoKeyboard(FullScreenModalView view, boolean autoKeyboard) {
        view.setAutoKeyboard(autoKeyboard);
    }
    @ReactProp(name = "isDarkMode")
    public void setDarkMode(FullScreenModalView view, boolean isDarkMode) {
        view.setDarkMode(isDarkMode);
    }
    @ReactProp(name = "animationType")
    public void setAnimationType(FullScreenModalView view, String animationType) {
        view.setAnimationType(animationType);
    }
    @ReactProp(name = "transparent")
    public void setTransparent(FullScreenModalView view, boolean transparent) {
        view.setTransparent(transparent);
    }
    @ReactProp(name = "hardwareAccelerated")
    public void setHardwareAccelerated(FullScreenModalView view, boolean hardwareAccelerated) {
        view.setHardwareAccelerated(hardwareAccelerated);
    }
    @Override
    protected void onAfterUpdateTransaction(FullScreenModalView view) {
        super.onAfterUpdateTransaction(view);
        view.showOrUpdate();
    }
}

在这里有几点需要注意的

  • 由于 RN Modal 已经存在了 onShow 和 onRequestClose 回调,这里不能再使用这两个命名,所以这里改成了 onFullScreenShow 和 onFullScreenRequstClose,但是在 js 端还是重新命名成 onShow 和 onRequestClose ,所以在使用过程中还是没有任何变化
  • 增加了 isDarkMode 属性,对应上面的状态栏字体的颜色
  • 增加了 autoKeyboard 属性,根据该属性判断是否需要自动弹起软件盘

其他的一些属性和用法也就跟 RN Modal 的一样了。

JS 部分实现

在 JS 部分,我们只需要 Android 的实现就好了,ios 还是沿用原来的 Modal 控件。这里参照 RN Modal 的 JS 端实现如下

import React, {Component} from "react";
import {requireNativeComponent, View}  from "react-native";
const FullScreenModal = requireNativeComponent('RCTFullScreenModalHostView', FullScreenModalView)
export default class FullScreenModalView extends Component {
    _shouldSetResponder = () => {
        return true;
    }
    render() {
        if (this.props.visible === false) {
            return null;
        }
        const containerStyles = {
            backgroundColor: this.props.transparent ? 'transparent' : 'white',
        };
        return (
            <FullScreenModal
                style={{position: 'absolute'}}  {...this.props}
                onStartShouldSetResponder={this._shouldSetResponder}
                onFullScreenShow={() => this.props.onShow && this.props.onShow()}
                onFullScreenRequstClose={() => this.props.onRequestClose && this.props.onRequestClose()}>
                <View style={[{position: 'absolute', left: 0, top: 0}, containerStyles]}>
                    {this.props.children}
                </View>
            </FullScreenModal>
        )
    }
}

使用 RootSiblings 封装 Modal

针对第二个问题,一种方法是通过 ios 原生去封装实现一个 Modal 控件,但是在 RN 的开发过程中,发现了一个第三方库 react-native-root-siblings , 它重写了系统的 AppRegistry.registerComponent 方法,当我们通过这个方法注册根组件的时候,替换根组件为我们自己的实现的包装类。包装类中监听了目标通知 siblings.update,接收到通知就将通知传入的组件视图添加到包装类顶层,然后进行刷新显示。通过 RootSiblings 也可以实现一个 Modal 组件,而且它的层级是在当前界面的最上层的。

实现界面 Render 相关

由于 RootSiblings 的实现是通过将组件添加到它注册到根节点中的,并不直接通过 Component 的 Render 进行布局,而 RN Modal 的显示隐藏是通过 visible 属性进行控制,所以在 componentWillReceiveProps(nextProps) 中根据 visible 进行相关的控制,部分实现代码如下

render() {
  if (this.props.visible) {
    this.RootSiblings && this.RootSiblings.update(this.renderRootSiblings());
  }
  return null;
}
componentWillReceiveProps(nextProps) {
    const { onShow, animationType, onDismiss } = this.props;
    const { visible } = nextProps;
    if (!this.RootSiblings && visible === true) { // 表示从没有到要显示了
      this.RootSiblings = new RootSiblings(this.renderRootSiblings(), () => {
        if (animationType === 'fade') {
          this._animationFadeIn(onShow);
        } else if (animationType === 'slide') {
          this._animationSlideIn(onShow);
        } else {
          this._animationNoneIn(onShow);
        }
      });
    } else if (this.RootSiblings && visible === false) { // 表示显示之后要隐藏了
      if (animationType === 'fade') {
        this._animationFadeOut(onDismiss);
      } else if (animationType === 'slide') {
        this._animationSlideOut(onDismiss);
      } else {
        this._animationNoneOut(onDismiss);
      }
   }
}

实现 Modal 展示动画相关

RN Modal 实现了三种动画模式,所以这里在通过 RootSiblings 实现 Modal 组件的时候也实现了这三种动画模式,这里借助的是 RN 提供的 Animated 和 Easing 进行相关的实现

  • ‘none’ 这种不必多说,直接进行展示,没有动画效果
  • ‘fade’ 淡入浅出动画,也就是透明度的一个变化,这里使用了 Easing.in 插值器使得效果更加平滑
  • ‘slide’ 幻灯片的滑入画出动画,这是是组件 Y 方向位置的一个变化,这里使用了 Easing.in 插值器使得效果更加平滑

完整的一个 使用 RootSiblings 封装 Modal 实现代码如下

import React, { Component } from 'react';
import {
  Animated, Easing, Dimensions, StyleSheet,
} from 'react-native';
import RootSiblings from 'react-native-root-siblings';
const { height } = Dimensions.get('window');
const animationShortTime = 250; // 动画时长为250ms
export default class ModalView extends Component {
  constructor(props) {
    super(props);
    this.state = {
      animationSlide: new Animated.Value(0),
      animationFade: new Animated.Value(0),
    };
  }
  render() {
    if (this.props.visible) {
      this.RootSiblings && this.RootSiblings.update(this.renderRootSiblings());
    }
    return null;
  }
  componentWillReceiveProps(nextProps) {
    const { onShow, animationType, onDismiss } = this.props;
    const { visible } = nextProps;
    if (!this.RootSiblings && visible === true) { // 表示从没有到要显示了
      this.RootSiblings = new RootSiblings(this.renderRootSiblings(), () => {
        if (animationType === 'fade') {
          this._animationFadeIn(onShow);
        } else if (animationType === 'slide') {
          this._animationSlideIn(onShow);
        } else {
          this._animationNoneIn(onShow);
        }
      });
    } else if (this.RootSiblings && visible === false) { // 表示显示之后要隐藏了
      if (animationType === 'fade') {
        this._animationFadeOut(onDismiss);
      } else if (animationType === 'slide') {
        this._animationSlideOut(onDismiss);
      } else {
        this._animationNoneOut(onDismiss);
      }
    }
  }
  renderRootSiblings = () => {
    return (
      <Animated.View style={[styles.root,
        { opacity: this.state.animationFade },
        {
          transform: [{
            translateY: this.state.animationSlide.interpolate({
              inputRange: [0, 1],
              outputRange: [height, 0],
            }),
          }],
        }]}>
        {this.props.children}
      </Animated.View>
    );
  }
  _animationNoneIn = (callback) => {
    this.state.animationSlide.setValue(1);
    this.state.animationFade.setValue(1);
    callback && callback();
  }
  _animationNoneOut = (callback) => {
    this._animationCallback(callback);
  }
  _animationSlideIn = (callback) => {
    this.state.animationSlide.setValue(0);
    this.state.animationFade.setValue(1);
    Animated.timing(this.state.animationSlide, {
      easing: Easing.in(),
      duration: animationShortTime,
      toValue: 1,
    }).start(() => callback && callback());
  }
  _animationSlideOut = (callback) => {
    this.state.animationSlide.setValue(1);
    this.state.animationFade.setValue(1);
    Animated.timing(this.state.animationSlide, {
      easing: Easing.in(),
      duration: animationShortTime,
      toValue: 0,
    }).start(() => this._animationCallback(callback));
  }
  _animationFadeIn = (callback) => {
    this.state.animationSlide.setValue(1);
    this.state.animationFade.setValue(0);
    Animated.timing(this.state.animationFade, {
      easing: Easing.in(),
      duration: animationShortTime,
      toValue: 1,
    }).start(() => callback && callback());
  }
  _animationFadeOut = (callback) => {
    this.state.animationSlide.setValue(1);
    this.state.animationFade.setValue(1);
    Animated.timing(this.state.animationFade, {
      easing: Easing.in(),
      duration: animationShortTime,
      toValue: 0,
    }).start(() => this._animationCallback(callback));
  }
  _animationCallback = (callback) => {
    this.RootSiblings && this.RootSiblings.destroy(() => {
      callback && callback();
      this.RootSiblings = undefined;
    });
  }
}
const styles = StyleSheet.create({
  root: {
    position: 'absolute',
    left: 0,
    top: 0,
    right: 0,
    bottom: 0,
  },
});

使用 View 封装 Modal

上面两种 Modal 的封装已经能够满足绝大部分业务场景的需求,但是如果在 Modal 中需要打开新的界面(不创建新的 ViewController 和 Acticity ),并且 Modal 不进行隐藏的话,比如使用 react-navigation 跳转页面,那么上面实现的 Modal 层级会太高了。所以这里通过 View 去实现类似的一个 Modal 控件。它的实现代码类似于上面的 RootSiblings 的实现。在 Render 中进行展示就好了,动画也可以使用上面的实现

 render() {
    return this._renderView()
 }

_renderView = () => {
    if (this.state.visible) {
      return (
        <Animated.View style={[styles.root,
          {opacity: this.state.animationFade},
          {
            transform: [{
              translateY: this.state.animationSlide.interpolate({
                inputRange: [0, 1],
                outputRange: [height, 0]
              }),
            }]
          }]}>
          {this.props.children}
        </Animated.View>
      );
    } else {
      return null
    }
}

整体 Modal 控件的封装

相对于 RN Modal ,上面新增了三种 Modal 的实现,为了整合整体的使用,所以需要对它们进行一个整体的封装使用,通过制定 modalType 的方式,来指定我们需要它内部的实现,先上代码

/**
 * @Author: linhe
 * @Date: 2019-05-12 10:11
 *
 * 因为ios端同时只能存在一个Modal,并且Modal多次显示隐藏会有很奇怪的bug
 *
 * 为了兼容ios的使用,这里需要封装一个ModalView
 *
 * Android 依旧使用 React Native Modal 来进行实现
 * ios 的话采用 RootSiblings 配合进行使用
 *
 * 这个是因为有的modal里面还需要跳转到其他界面
 * 这个时候主要要将该View放到最外边的层级才可以
 *
 * modalType:1 //表示使用Modal进行实现
 *           2 //表示使用RootSiblings进行实现
 *           3 //表示使用View进行实现
 * 注意:默认情况下 Android 使用的是1,ios使用的是2
 *
 * 同时采用与 React Native Modal 相同的API
 */
'use strict';
import React, {Component} from "react";
import {Animated, BackHandler, Platform, Easing, StyleSheet, Dimensions, Modal} from "react-native";
import PropTypes from 'prop-types'
import RootSiblings from 'react-native-root-siblings';
import FullScreenModal from './FullScreenModal/FullScreenModal'
const {height} = Dimensions.get('window')
const animationShortTime = 250 //动画时长为250ms
const DEVICE_BACK_EVENT = 'hardwareBackPress';
export default class ModalView extends Component {
  static propTypes = {
    isDarkMode: PropTypes.bool, // false 表示白底黑字,true 表示黑底白字
    autoKeyboard: PropTypes.bool, // 未知原因的坑,modal中的edittext自动弹起键盘要设置这个参数为true
    useReactModal: PropTypes.bool, // 是否使用 RN Modal 进行实现
    modalType: PropTypes.number // modalType 类型,默认 android 为 1,ios 为 2
  };
  static defaultProps = {
    isDarkMode: false,
    autoKeyboard: false,
    useReactModal: false,
    modalType: (Platform.OS === 'android' ? 1 : 2) // 默认 android 为1,ios 为2
  };
  constructor(props) {
    super(props);
    this.state = {
      visible: false,
      animationSlide: new Animated.Value(0),
      animationFade: new Animated.Value(0)
    };
  }
  render() {
    const {modalType} = this.props
    if (modalType === 1) { //modal实现
      return this._renderModal()
    } else if (modalType === 2) { //RootSiblings实现
      this.RootSiblings && this.RootSiblings.update(this._renderRootSiblings())
      return null
    } else { //View的实现
      return this._renderView()
    }
  }
  _renderModal = () => {
    const ModalView = this.props.useReactModal ? Modal : FullScreenModal
    return (
      <ModalView
        transparent={true}
        {...this.props}
        visible={this.state.visible}
        onRequestClose={() => {
          if (this.props.onRequestClose) {
            this.props.onRequestClose()
          } else {
            this.disMiss()
          }
        }}>
        {this.props.children}
      </ModalView>
    )
  }
  _renderRootSiblings = () => {
    return (
      <Animated.View style={[styles.root,
        {opacity: this.state.animationFade},
        {
          transform: [{
            translateY: this.state.animationSlide.interpolate({
              inputRange: [0, 1],
              outputRange: [height, 0]
            }),
          }]
        }]}>
        {this.props.children}
      </Animated.View>
    );
  }
  _renderView = () => {
    if (this.state.visible) {
      return (
        <Animated.View style={[styles.root,
          {opacity: this.state.animationFade},
          {
            transform: [{
              translateY: this.state.animationSlide.interpolate({
                inputRange: [0, 1],
                outputRange: [height, 0]
              }),
            }]
          }]}>
          {this.props.children}
        </Animated.View>
      );
    } else {
      return null
    }
  }
  show = (callback) => {
    if (this.isShow()) {
      return
    }
    const {modalType, animationType} = this.props
    if (modalType === 1) { //modal
      this.setState({visible: true}, () => callback && callback())
    } else if (modalType === 2) { //RootSiblings
      this.RootSiblings = new RootSiblings(this._renderRootSiblings(), () => {
        if (animationType === 'fade') {
          this._animationFadeIn(callback)
        } else if (animationType === 'slide') {
          this._animationSlideIn(callback)
        } else {
          this._animationNoneIn(callback)
        }
      });
      // 这里需要监听 back 键
      this._addHandleBack()
    } else { //view
      if (animationType === 'fade') {
        this.setState({visible: true}, () => this._animationFadeIn(callback))
      } else if (animationType === 'slide') {
        this.setState({visible: true}, () => this._animationSlideIn(callback))
      } else {
        this.setState({visible: true}, () => this._animationNoneIn(callback))
      }
      // 这里需要监听 back 键
      this._addHandleBack()
    }
  }
  disMiss = (callback) => {
    if (!this.isShow()) {
      return
    }
    const {modalType, animationType} = this.props
    if (modalType === 1) { //modal
      this.setState({visible: false}, () => callback && callback())
    } else { //RootSiblings和View
      if (animationType === 'fade') {
        this._animationFadeOut(callback)
      } else if (animationType === 'slide') {
        this._animationSlideOut(callback)
      } else {
        this._animationNoneOut(callback)
      }
      // 移除 back 键的监听
      this._removeHandleBack()
    }
  }
  isShow = () => {
    const {modalType} = this.props
    if (modalType === 1 || modalType === 3) { //modal和view
      return this.state.visible
    } else { //RootSiblings
      return !!this.RootSiblings
    }
  }
  _addHandleBack = () => {
    if (Platform.OS === 'ios') {
      return
    }
    // 监听back键
    this.handleBack = BackHandler.addEventListener(DEVICE_BACK_EVENT, () => {
      const {onRequestClose} = this.props
      if (onRequestClose) {
        onRequestClose()
      } else {
        this.disMiss()
      }
      return true
    });
  }
  _removeHandleBack = () => {
    if (Platform.OS === 'ios') {
      return
    }
    this.handleBack && this.handleBack.remove()
  }
  _animationNoneIn = (callback) => {
    this.state.animationSlide.setValue(1)
    this.state.animationFade.setValue(1)
    callback && callback()
  }
  _animationNoneOut = (callback) => {
    this._animationCallback(callback);
  }
  _animationSlideIn = (callback) => {
    this.state.animationSlide.setValue(0)
    this.state.animationFade.setValue(1)
    Animated.timing(this.state.animationSlide, {
      easing: Easing.in(),
      duration: animationShortTime,
      toValue: 1,
    }).start(() => callback && callback());
  }
  _animationSlideOut = (callback) => {
    this.state.animationSlide.setValue(1)
    this.state.animationFade.setValue(1)
    Animated.timing(this.state.animationSlide, {
      easing: Easing.in(),
      duration: animationShortTime,
      toValue: 0,
    }).start(() => this._animationCallback(callback));
  }
  _animationFadeIn = (callback) => {
    this.state.animationSlide.setValue(1)
    this.state.animationFade.setValue(0)
    Animated.timing(this.state.animationFade, {
      easing: Easing.in(),
      duration: animationShortTime,
      toValue: 1,
    }).start(() => callback && callback());
  }
  _animationFadeOut = (callback) => {
    this.state.animationSlide.setValue(1)
    this.state.animationFade.setValue(1)
    Animated.timing(this.state.animationFade, {
      easing: Easing.in(),
      duration: animationShortTime,
      toValue: 0,
    }).start(() => this._animationCallback(callback));
  }
  _animationCallback = (callback) => {
    if (this.props.modalType === 2) {//RootSiblings
      this.RootSiblings && this.RootSiblings.destroy(() => {
        callback && callback()
        this.RootSiblings = undefined
      })
    } else { //view
      this.setState({visible: false}, () => callback && callback())
    }
  }
}
const styles = StyleSheet.create({
  root: {
    position: 'absolute',
    left: 0,
    top: 0,
    right: 0,
    bottom: 0,
  }
});

这里主要通过 useReactModal 和 modalType 两个属性来控制我们所需要的实现

  • modalType 表示 modal 内部实现方式,1 表示使用层级较高的实现,2 表示使用 RootSiblings 进行实现,3 表示使用 View 进行实现,当不进行指定的时候,默认 Android 为 1,ios 为 2
  • useReactModal 主要针对 Android 端并且 modalType 为 1 的时候使用,true 表示使用 RN Modal,false 表示使用 FullScreenModal ,默认为 false
  • 对外提供 show,disMiss,isShow 方法分别表示显示弹窗,隐藏弹窗和判断当前弹窗的状态,同时在 show 和 disMiss 方法调用的时候还添加了 callback 回调

其他

Android Back 键的注意

当 Modal 组件使用 RootSiblings 或者 View 实现的时候,它并没有处理一个 Android 的返回键,所以对于这两种实现的时候,要额外处理一个 Back 键的操作,这里借助了 RN BackHandler 这个了,如果 modalType 不为 1 的话,需要通过 BackHandler 去实现一个返回键的监听,然后通过 onRequestClose 属性进行返回

const DEVICE_BACK_EVENT = 'hardwareBackPress';
_addHandleBack = () => {
    if (Platform.OS === 'ios') {
      return
    }
    // 监听back键
    this.handleBack = BackHandler.addEventListener(DEVICE_BACK_EVENT, () => {
      const {onRequestClose} = this.props
      if (onRequestClose) {
        onRequestClose()
      } else {
        this.disMiss()
      }
      return true
    });
}
_removeHandleBack = () => {
    if (Platform.OS === 'ios') {
      return
    }
    this.handleBack && this.handleBack.remove()
}

View 封装 Modal 时候的注意

如果是 View 实现的 Modal 控件,那必须要注意它的一个层级,必须满足它所处于整个界面布局的最外层,否则它可能会被其他组件所挡住,同时它的最大的显示范围取决于它的父 View 的显示范围。

最后

当然在最后还是要附上实现效果图

再一次附上 Demo 地址:https://github.com/hzl123456/ModalViewDemo

到此这篇关于React Native Modal 的封装与使用 的文章就介绍到这了,更多相关React Native Modal 使用内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • React Native使用Modal自定义分享界面的示例代码

    在很多App中都会涉及到分享,React Native提供了Modal组件用来实现一些模态弹窗,例如加载进度框,分享弹框等.使用Modal搭建分析的效果如下: 自定义的分析界面代码如下: ShareAlertDialog.js /** * https://github.com/facebook/react-native * @flow 分享弹窗 */ import React, {Component} from 'react'; import {View, TouchableOpacity, A

  • React Native学习教程之Modal控件自定义弹出View详解

    前言 最近在学习RN,好多知识都懒得写,趁今天有空,来一发吧,Modal控件的一个小demo:下面话不多说了,来一起看看详细的介绍吧. 参考文章地址:http://reactnative.cn/docs/0.27/modal.html#content Modal组件可以用来覆盖包含React Native根视图的原生视图(如UIViewController,Activity). 在嵌入React Native的混合应用中可以使用Modal.Modal可以使你应用中RN编写的那部分内容覆盖在原生视

  • React-Native 组件之 Modal的使用详解

    Modal组件可以用来覆盖包含React Native根视图的原生视图(如UIViewController,Activity),用它可以实现遮罩的效果. 属性 Modal提供的属性有: animationType(动画类型) PropTypes.oneOf(['none', 'slide', 'fade'] none:没有动画 slide:从底部滑入 fade:淡入视野 onRequestClose(被销毁时会调用此函数) 在 'Android' 平台,必需调用此函数 onShow(模态显示的时

  • React Native Modal 的封装与使用实例详解

    目录 背景 Android FullScreenModal 的封装使用 Android 原生实现全屏 Dialog 封装给 RN 进行相关的调用 Android 原生部分实现 JS 部分实现 使用 RootSiblings 封装 Modal 实现界面 Render 相关 实现 Modal 展示动画相关 使用 View 封装 Modal 整体 Modal 控件的封装 其他 Android Back 键的注意 View 封装 Modal 时候的注意 最后 背景 在使用 React Native(以下

  • 公共Hooks封装文件下载useDownloadFile实例详解

    目录 引言 项目环境 封装前提:各方法对比 封装分解:下载核心代码 封装分解:用户体验设计 useDownloadFile.js完整代码 引言 对于经常需要开发企业管理后台的前端开发来说,必不可少的需要使用表格对于数据进行操作,在对于现有项目进行代码优化时,封装一些公共的Hooks. 本篇文章为useDownloadFile.js 基于个人项目环境进行封装的Hooks,仅以本文介绍封装Hooks思想心得,故相关代码可能不适用他人 项目环境 Vue3.x + Ant Design Vue3.x +

  • AngularJS封装$http.post()实例详解

      AngularJS封装$http.post()实例详解 用了不是很长的时间跟了一个移动APP项目,用的是ionic + AngularJS + cordova框架,其间遇到过挺多问题,其中一个就是对Ajax的封装问题. 其实针对封装问题一直以来就没停止过谈论,不同的项目也有不同的需求,举个典型的例子,我在做这个项目的时候因为一开始没有考虑封装问题所以被批评了,而我的一个朋友却因为封装了受到了批评--很尴尬不是么. 那么对于是否要封装这个问题,究竟该怎么界定?其实这不是一个很复杂的问题,归根到

  • springbatch的封装与使用实例详解

    Spring Batch官网介绍: A lightweight, comprehensive batch framework designed to enable the development of robust batch applications vital for the daily operations of enterprise systems.(一款轻量的.全面的批处理框架,用于开发强大的日常运营的企业级批处理应用程序.) springbatch 主要实现批量数据的处理,我对bat

  • React Native采用Hermes热更新打包方案详解

    目录 1, 背景 2,热更新传统方案 3,使用Hermes打包 1, 背景 如果我们打开RN的Android源码,在build.gradle中回看到这样一段代码. if (enableHermes) { def hermesPath = "../../node_modules/hermes-engine/android/"; debugImplementation files(hermesPath + "hermes-debug.aar") releaseImple

  • React Native 中实现确认码组件示例详解

    目录 正文 实现原理 开源方案 正文 确认码控件也是一个较为常见的组件了,乍一看,貌似较难实现,但实则主要是障眼法. 实现原理 上图 CodeInput 组件的 UI 结构如下: <View style={[styles.container]}> <TextInput autoFocus={true} /> <View style={[styles.cover, StyleSheet.absoluteFillObject]} pointerEvents="none&

  • Flutter学习LogUtil封装与实现实例详解

    目录 一. 为什么要封装打印类 二. 需要哪些类 三. 打印输出的抽象类 四. 格式化日志内容 格式化堆栈 堆栈裁切工具类 格式化堆栈信息 格式化JSON 五. 需要用到的常量 六. 为了控制多个打印器的设置做了一个配置类 七. Log的管理类 九. 调用LogUtil 十. 定义一个Flutter 控制台打印输出的方法 十一. 现在使用前初始化log打印器一次 使用 一. 为什么要封装打印类 虽然 flutter/原生给我们提供了日志打印的功能,但是超出一定长度以后会被截断 Json打印挤在一

  • React 悬浮框内容懒加载实例详解

    目录 界面隐藏 懒加载 React实现 原始代码 放入新的DIV 状态设置 样式设置 事件设置 事件优化 延迟显示悬浮框 悬浮框内容懒加载 完整代码 界面隐藏 一个容器放置视频,默认情况下 display: none; z-index: 0; transform: transform3d(10000px, true_y, true_z); y轴和z轴左边都是真实的(腾讯视频使用绝对定位,因此是计算得到的),只是将其移到右边很远的距离. 懒加载 React监听鼠标移入(获取坐标) 添加事件监听 o

  • React Native中NavigatorIOS组件的简单使用详解

    一.NavigatorIOS组件介绍 1,组件说明 使用 NavigatorIOS 我们可以实现应用的导航(路由)功能,即实现视图之间的切换和前进.后退.并且在页面上方会有个导航栏(可以隐藏). NavigatorIOS 组件本质上是对 UIKit navigation 的包装.使用 NavigatorIOS 进行路由切换,实际上就是调用 UIKit 的 navigation. NavigatorIOS 组件只支持 iOS 系统.React Native 还提供了一个 iOS 和 Android

  • 微信小程序 封装http请求实例详解

    微信小程序 封装http请求 最近看了一下微信小程序,大致翻了一下,发现跟angular很相似的,但是比angular简单的很多具体可参考官方文档 https://mp.weixin.qq.com/debug/wxadoc/dev/framework/app-service/page.html?t=2017112 下面将封装http请求服务部分的服务以及引用部分 // 本服务用于封装请求 // 返回的是一个promisepromise var sendRrquest = function (ur

随机推荐