背景

在应用 React Native(以下简称 RN ) 开发挪动App时,会碰到很多弹窗的场景,尽管 RN自带了一个 Modal 组件能够实现这一成果,然而因为Android和iOS平台的差异性,使得应用同一个组件开发进去的成果会略有差别。比方,Modal组件在iOS平台,弹框是全屏的,然而在Android平台却不是,会有状态栏,如下成果。

之所以这样,是因为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>  )

附,相干源码