React Native开发React Native开发经验集React Native实践

React-Native 实现全屏展示的 Modal 弹窗

2019-04-04  本文已影响12人  这真不是玩笑

1. 背景

最近公司的项目 Android 端的 UI 效果也改成了沉浸式状态栏的效果,在使用中突然发现一个问题,之前的弹窗组件(RN 的 Modal)无法是实现全屏展示,类似的效果如下

5FD1B744-8442-47C4-B3A9-115507B140A1.jpeg

实在是太丑了有木有,所以决定自己撸一个能够全屏展示的弹窗控件,就称呼它为 FullScreenModal 吧。

2. Android 原生实现全屏 Dialog

在开发之前,首先查看了 RN Modal 组件在 Android 端的原生实现,发现它是对 Android Dialog 组件的一个封装调用,那么假如我能实现一个全屏展示的 Dialog,那么是不是在 RN 上也就可以实现全屏弹窗了呢。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);
        }
    }

而这里的 setDarkMode 和 setLightMode 是设置状态栏字体样式分别为黑色和白色,AndroidBug5497Workaround.assistView(rootView, getWindow()); 方法是处理一个全屏模式下软键盘弹出的一个 bug。完整的代码会在最后的 Demo 中贴出来的。

3. 封装给 RN 进行相关的调用

3.1 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((dialog) -> mEventEmitter.receiveEvent(view.getId(), Events.ON_REQUEST_CLOSE.toString(), null));
        view.setOnShowListener((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 = "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 的基本一样了。

3.2 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>
        )
    }

}

最后我们看一下实现的效果

Screenshot_20190404_094245_com.fullscreenmodaldem.jpg
附上 Demo 的链接:https://github.com/hzl123456/React-Native-FullScreenModal
上一篇下一篇

猜你喜欢

热点阅读