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

作者: 这真不是玩笑 | 来源:发表于2019-04-04 15:46 被阅读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 已经存在了 onShow 和 onRequestClose 回调,这里不能再使用这两个命名,所以这里改成了 onFullScreenShow 和 onFullScreenRequstClose
    • 增加了 isDarkMode 属性,对应上面的状态栏字体的颜色

    其他的也就跟 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

    相关文章

      网友评论

        本文标题:React-Native 实现全屏展示的 Modal 弹窗

        本文链接:https://www.haomeiwen.com/subject/hccnbqtx.html