一行代码搞定 Android平台React Native Modal 无法延伸到状态栏的问题

栏目: 服务器 · 发布时间: 4年前

内容简介:最近在做最开始的想法是自定义一个组件来代替原生的Q: 为什么

最近在做 react-native 应用Android端沉浸式状态栏时,发现通过 Statusbar.setTrranslucent(ture) 设置界面拉通状态栏之后,使用 Modal 组件的地方界面无法延伸到状态栏,导致使用 Modal 实现的弹窗背景蒙层顶部会有一个白条,看起来很不爽,在经过一番搜索之后,发现 react-native github 上有人提这个问题,但是没有解决。因此就只有找其他方案来解决。

最开始的想法是自定义一个组件来代替原生的 Modal 组件,但是项目里面使用 Modal 的地方很多,替换起来也很麻烦。比较致命的一点是 Modal 组件的一些属性是不好被替代的。比如: onRequestClose ,在弹出 Modal 时,点击物理返回键,会回调这个方法,基本上所有使用 Modal 的地方都会用它来做关闭弹窗,新的组件需要报保留这些属性和功能。在网上搜到一篇文章 [React Native] 还我靓靓 modal 弹窗 ,借鉴它的思路,最后完美解决。

解决方案和思路

Q: 为什么 react native 提供的 Modal 组件Android平台不能延伸到状态栏?

A:因为 Modal Android 原生用 Dialog 实现, Dialog 本身就不能衍生到 statusbar

因此我们改一下 Modal 原生的实现就好了。

**解决方案: 就是更改 Modal 组件的原生代码实现。重新提供一个 Modal (就叫: TranslucentModal )组件给 react native **端。

注意的问题:

1、新的 Modal 组件和原来的modal 组件所暴露的属性和方法要完全一样,这样替换就很方便。

2、在 react-native 做统一封装,IOS平台继续使用 react-native 提供的 Modal 组件,Android平台使用 TranslucentModal

最终我们只需要在使用 Modal 的页面更改一下引用的就ok,真正的只需要修改一行代码。

import { Modal } from "react-native";
复制代码

改为:

import Modal from 'react-native-translucent-modal';

复制代码

效果图

对比图 使用RN原生的Modal 使用Translucent Modal
splash
一行代码搞定 Android平台React Native Modal 无法延伸到状态栏的问题
一行代码搞定 Android平台React Native Modal 无法延伸到状态栏的问题
pop
一行代码搞定 Android平台React Native Modal 无法延伸到状态栏的问题
一行代码搞定 Android平台React Native Modal 无法延伸到状态栏的问题

具体实现

1、原生端代码更改

Modal 组件Android端的实现类为 com.facebook.react.views.modal.ReactModalHostView.java ,这个类是 public 的,因此我们就可以在我们自己的项目下创建一个新类 TranslucentModalHostView 继承自 ReactModalHostView ,修改部分实现就好了,如下:

/**
 * React Native Modal(Android) 延伸到状态栏
 * 由于React Native 提供的 Modal 组件不能延伸到状态栏,因此,只有对原生{@link ReactModalHostView}实现修改。
 */
public class TranslucentModalHostView extends ReactModalHostView {

    public TranslucentModalHostView(Context context) {
        super(context);
    }

    @Override
    protected void setOnShowListener(DialogInterface.OnShowListener listener) {
        super.setOnShowListener(listener);
    }

    @Override
    protected void setOnRequestCloseListener(OnRequestCloseListener listener) {
        super.setOnRequestCloseListener(listener);
    }

    @Override
    protected void setTransparent(boolean transparent) {
        super.setTransparent(transparent);
    }

    @Override
    protected void setHardwareAccelerated(boolean hardwareAccelerated) {
        super.setHardwareAccelerated(hardwareAccelerated);
    }

    @Override
    protected void setAnimationType(String animationType) {
        super.setAnimationType(animationType);
    }

    @Override
    protected void showOrUpdate() {
        super.showOrUpdate();
        Dialog dialog = getDialog();
        if (dialog != null) {
            setStatusBarTranslucent(dialog.getWindow(), true);
            setStatusBarColor(dialog.getWindow(), Color.TRANSPARENT);
            setStatusBarStyle(dialog.getWindow(), isDark());
        }
    }

    @TargetApi(23)
    private boolean isDark() {
        Activity activity = ((ReactContext) getContext()).getCurrentActivity();
        // fix activity NPE
        if (activity == null) {
            return true;
        }
        return (activity.getWindow().getDecorView().getSystemUiVisibility() & View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR) != 0;
    }

    public static void setStatusBarTranslucent(Window window, boolean translucent) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            View decorView = window.getDecorView();
            if (translucent) {
                decorView.setOnApplyWindowInsetsListener(new View.OnApplyWindowInsetsListener() {
                    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
                    @Override
                    public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) {
                        WindowInsets defaultInsets = v.onApplyWindowInsets(insets);
                        return defaultInsets.replaceSystemWindowInsets(
                                defaultInsets.getSystemWindowInsetLeft(),
                                0,
                                defaultInsets.getSystemWindowInsetRight(),
                                defaultInsets.getSystemWindowInsetBottom());
                    }
                });
            } else {
                decorView.setOnApplyWindowInsetsListener(null);
            }
            ViewCompat.requestApplyInsets(decorView);
        }
    }

    public static void setStatusBarColor(final Window window, int color) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
            window.setStatusBarColor(color);
        }
    }

    public static void setStatusBarStyle(Window window, boolean dark) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            View decorView = window.getDecorView();
            decorView.setSystemUiVisibility(
                    dark ? View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR : 0);
        }
    }
}
复制代码

就这样,功能就实现了,现在我们需要把它以组件的形式提供给 react native 端,可以看一下 com.facebook.react.views.modal 用到了如下几个类:

一行代码搞定 Android平台React Native Modal 无法延伸到状态栏的问题

它们的可见性都是包内访问的,因此在我们自己的包下访问不了,因此,需要把这几个类拷贝一份出来:

一行代码搞定 Android平台React Native Modal 无法延伸到状态栏的问题

TranslucentReactModalHostManager 中换一下对应的名字就ok 了。

2、 react native 端统一封装

因为我们提供的属性要和原来的 Modal 组件保持一致,因此,我们把原来的 Modal.js 文件拷贝一份出来改一下,把 ios 端的属性和相关方法剔除掉,剩下Android 平台的属性相关就好了。最终如下,取名为 MFTranslucentModal.android.js :

const AppContainer = require('AppContainer');
const I18nManager = require('I18nManager');
const Platform = require('Platform');
const React = require('React');
const PropTypes = require('prop-types');
const StyleSheet = require('StyleSheet');
const View = require('View');

const requireNativeComponent = require('requireNativeComponent');

const RCTModalHostView = requireNativeComponent('RCTTranslucentModalHostView', null);


/**
 * The Modal component is a simple way to present content above an enclosing view.
 *
 * See https://facebook.github.io/react-native/docs/modal.html
 */

class Modal extends React.Component {
  static propTypes = {
    /**
     * The `animationType` prop controls how the modal animates.
     *
     * See https://facebook.github.io/react-native/docs/modal.html#animationtype
     */
    animationType: PropTypes.oneOf(['none', 'slide', 'fade']),
    /**
     * The `transparent` prop determines whether your modal will fill the
     * entire view.
     *
     * See https://facebook.github.io/react-native/docs/modal.html#transparent
     */
    transparent: PropTypes.bool,
    /**
     * The `hardwareAccelerated` prop controls whether to force hardware
     * acceleration for the underlying window.
     *
     * See https://facebook.github.io/react-native/docs/modal.html#hardwareaccelerated
     */
    hardwareAccelerated: PropTypes.bool,
    /**
     * The `visible` prop determines whether your modal is visible.
     *
     * See https://facebook.github.io/react-native/docs/modal.html#visible
     */
    visible: PropTypes.bool,
    /**
     * The `onRequestClose` callback is called when the user taps the hardware
     * back button on Android or the menu button on Apple TV.
     *
     * See https://facebook.github.io/react-native/docs/modal.html#onrequestclose
     */
    onRequestClose: (Platform.isTVOS || Platform.OS === 'android') ? PropTypes.func.isRequired : PropTypes.func,
    /**
     * The `onShow` prop allows passing a function that will be called once the
     * modal has been shown.
     *
     * See https://facebook.github.io/react-native/docs/modal.html#onshow
     */
    onShow: PropTypes.func,
  };

  static defaultProps = {
    visible: true,
    hardwareAccelerated: false,
  };

  static contextTypes = {
    rootTag: PropTypes.number,
  };


  render() {
    if (this.props.visible === false) {
      return null;
    }

    const containerStyles = {
      backgroundColor: this.props.transparent ? 'transparent' : 'white',
    };

    let animationType = this.props.animationType;
    if (!animationType) {
      // manually setting default prop here to keep support for the deprecated 'animated' prop
      animationType = 'none';
    }

    const innerChildren = __DEV__ ?
      (<AppContainer rootTag={this.context.rootTag}>
        {this.props.children}
      </AppContainer>) :
      this.props.children;

    return (
      <RCTModalHostView
        animationType={animationType}
        transparent={this.props.transparent}
        hardwareAccelerated={this.props.hardwareAccelerated}
        onRequestClose={this.props.onRequestClose}
        onShow={this.props.onShow}
        style={styles.modal}
        onStartShouldSetResponder={this._shouldSetResponder}
      >
        <View style={[styles.container, containerStyles]}>
          {innerChildren}
        </View>
      </RCTModalHostView>
    );
  }

  // We don't want any responder events bubbling out of the modal.
  _shouldSetResponder = () => true
}

const side = I18nManager.isRTL ? 'right' : 'left';
const styles = StyleSheet.create({
  modal: {
    position: 'absolute',
  },
  container: {
    position: 'absolute',
    [side]: 0,
    top: 0,
  },
});

module.exports = Modal;
复制代码

ios 使用原来的 Modal 组件,添加一个 MFTranslucentModal.ios.js 文件,实现很简单,引用 react native 的 Modal 就ok , 如下:

import { Modal } from 'react-native';

export default Modal;
复制代码

最后,通过, index.js 文件统一导出:

import MFTranslucentModal from './MFTranslucentModal';

export default MFTranslucentModal;
复制代码

好了,整个封装过程就完成了。

新的 Modal 和原来的 Modal 使用完全一样,只需要更改一行代码那就是 import 的地方

import { Modal } from "react-native";
复制代码

改为

import Modal from '@components/Modal';
复制代码

为了方便使用,我已经这个组件开源的Github,地址为:

github.com/23mf/react-…

已经发布到npm仓库,引用到项目中直接使用就好具体请看Github 文档,最后,别忘了star 一下哟。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

C专家编程

C专家编程

Peter Van Der Linden / 徐波 / 人民邮电出版社 / 2008-2 / 45.00元

《C专家编程》展示了最优秀的C程序员所使用的编码技巧,并专门开辟了一章对C++的基础知识进行了介绍。 书中C的历史、语言特性、声明、数组、指针、链接、运行时、内存以及如何进一步学习C++等问题进行了细致的讲解和深入的分析。全书撷取几十个实例进行讲解,对C程序员具有非常高的实用价值。 本书可以帮助有一定经验的C程序员成为C编程方面的专家,对于具备相当的C语言基础的程序员,本书可以帮助他们......一起来看看 《C专家编程》 这本书的介绍吧!

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具