自定义React Native Modal,支持全屏弹框

CodeHorizonX
• 阅读 7927

背景

在使用 React Native(以下简称 RN ) 开发移动App时,会碰到很多弹窗的场景,虽然 RN自带了一个 Modal 组件可以实现这一效果,但是由于Android和iOS平台的差异性,使得使用同一个组件开发出来的效果会略有差异。比如,Modal组件在iOS平台,弹框是全屏的,但是在Android平台却不是,会有状态栏,如下效果。
自定义React Native Modal,支持全屏弹框
之所以这样,是因为Android 端的Modal 控件使用的Dialog,内容无法从状态栏处开始布局。而iOS是基于 Window 的,所以是覆盖在视图上面的。如果要让双端的样式一样,那么需要对Android进行特殊处理。

由于RN的Modal 组件在Android中是使用Dialog实现的,所以如果要实现一个全屏的弹框,那么就需要自定义一个全屏展示的Dialog。

1,自定义Dialog

首先,我们新建一个继承自Dialog的自定义组件FullModal,代码如下:

package com.cgv.cn.movie.modal;

import android.app.Dialog;
import android.content.Context;
import android.view.View;

import androidx.annotation.NonNull;
import androidx.annotation.StyleRes;


public class FullModal extends Dialog {

    private boolean isDarkMode;
    private View rootView;

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

    public FullModal(@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());
        }
        AndroidWorkaround.assistView(rootView, getWindow());
    }
}

在上面的代码中,StatusBarUtil.setTransparent(getWindow()) 方法的主要作用就是将状态栏背景透明,并且让布局内容可以从 Android 状态栏开始。然后我们看一下setTransparent()方法的实现。

@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);
        }
    }

需要说明的是,transparentStatusBar()方法只在 Android 4.4 以上才会有效果,Android 4.4之前要实现沉浸式状态栏需要使用其他的方式(比如反射)。不过现在都已经是9012年了, Android 4.4版本的应该很少了吧。

2, RN封装调用

自定义的原生组件开发完成之后,接下来就是按照RN和原生交互的规则进行RN的封装。首先,新建一个FullModalManager类,该类的主要作用就是RN的Android和Js的通信桥梁,代码如下。

public class FullModalManager extends ViewGroupManager<FullModalView> {
    @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 FullModalView createViewInstance(ThemedReactContext reactContext) {
        final FullModalView view = new FullModalView(reactContext);
        final RCTEventEmitter mEventEmitter = reactContext.getJSModule(RCTEventEmitter.class);
        view.setOnRequestCloseListener(new FullModalView.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(FullModalView view) {
        super.onDropViewInstance(view);
        view.onDropInstance();
    }

    @ReactProp(name = "autoKeyboard")
    public void setAutoKeyboard(FullModalView view, boolean autoKeyboard) {
        view.setAutoKeyboard(autoKeyboard);
    }

    @ReactProp(name = "isDarkMode")
    public void setDarkMode(FullModalView view, boolean isDarkMode) {
        view.setDarkMode(isDarkMode);
    }

    @ReactProp(name = "animationType")
    public void setAnimationType(FullModalView view, String animationType) {
        view.setAnimationType(animationType);
    }

    @ReactProp(name = "transparent")
    public void setTransparent(FullModalView view, boolean transparent) {
        view.setTransparent(transparent);
    }

    @ReactProp(name = "hardwareAccelerated")
    public void setHardwareAccelerated(FullModalView view, boolean hardwareAccelerated) {
        view.setHardwareAccelerated(hardwareAccelerated);
    }

    @Override
    protected void onAfterUpdateTransaction(FullModalView view) {
        super.onAfterUpdateTransaction(view);
        view.showOrUpdate();
    }
}

FullModalManager类最重要的createViewInstance()方法。并且,在事件通信中,RN的Modal 已经存在了onShow()和 onRequestClose()回调,但是这里不能再使用这两个命名,所以这里改成了 onFullScreenShow 和 onFullScreenRequstClose,但是在Js端还是重新命名成 onShow 和 onRequestClose ,所以在使用过程中还是没有任何变化。

在RN的Js部分,我们只需要处理 Android 的实现即可,而对于iOS部分则不需要处理。为了方便在RN代码中进行引用,我们可以参考RN自定义组件的方式新建FullModal.android.js和FullModal.ios.js两个文件,其中FullModal.android.js的源码如下。

const FullScreenModal = requireNativeComponent('RCTFullScreenModalHostView', null);

export default class FullModalViewAndroid extends Component {
  _shouldSetResponder = () => {
    return true;
  }

  static propTypes = {
    isDarkMode: PropTypes.bool, // false 表示白底黑字,true 表示黑底白字
    autoKeyboard: PropTypes.bool, // 未知原因的坑,modal中的edittext自动弹起键盘要设置这个参数为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>
    );
  }
}

接下来,我们就可以在业务代码中进行使用了,如下所示。

const ModalView = tools.isIos ? Modal : FullModal
  return (
    <ModalView transparent={false} visible={targetShow} onRequestClose={() => { }}>
      <View style={{ flex: 1 }}>
        ...//省略其他代码
      </View>
    </ModalView>
  )

附,相关源码

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Jacquelyn38 Jacquelyn38
4年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
LinMeng LinMeng
4年前
element-ui Dialog组件的close-on-click-modal属性
element组件库的Dialog对话框默认可以通过点击modal关闭Dialog,即点击空白处弹框可关闭。单功能设置如下:<eldialog:closeonclickmodal"false"</eldialog全局修改默认配置,点击空白处不能关闭弹窗://在组件注册.js文件中Dialog.props.cl
Easter79 Easter79
3年前
Taro小程序自定义顶部导航栏
微信自带的顶部导航栏是无法支持自定义icon和增加元素的,在开发小程序的时候自带的根本满足不了需求,分享一个封装好的组件,支持自定义icon、扩展dom,适配安卓、ios、h5,全面屏。我用的是京东的Taro多端编译框架写的小程序,原生的也可以适用,用到的微信/taro的api做调整就行,实现效果如下。!在这里插入图片描述(https://i
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
Unity横屏
Android下发现Unity里面的Player设置,并不能完全有效,比如打开了自动旋转,启动的时候还是会横屏,修改XML添加以下代码<applicationandroid:icon"@drawable/ic\_launcher"                    android:label"@string/app\_name"
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
为什么mysql不推荐使用雪花ID作为主键
作者:毛辰飞背景在mysql中设计表的时候,mysql官方推荐不要使用uuid或者不连续不重复的雪花id(long形且唯一),而是推荐连续自增的主键id,官方的推荐是auto_increment,那么为什么不建议采用uuid,使用uuid究
Python进阶者 Python进阶者
1年前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这
美凌格栋栋酱 美凌格栋栋酱
4个月前
Oracle 分组与拼接字符串同时使用
SELECTT.,ROWNUMIDFROM(SELECTT.EMPLID,T.NAME,T.BU,T.REALDEPART,T.FORMATDATE,SUM(T.S0)S0,MAX(UPDATETIME)CREATETIME,LISTAGG(TOCHAR(