diff --git a/docs/api/hippy-react/components.md b/docs/api/hippy-react/components.md index 79b4e75aea9..945be5bca1d 100644 --- a/docs/api/hippy-react/components.md +++ b/docs/api/hippy-react/components.md @@ -179,6 +179,8 @@ import icon from './qb_icon_new.png'; | supportedOrientations | 支持屏幕翻转方向 | `enum (portrait, portrait-upside-down, landscape, landscape-left, landscape-right)[]` | `iOS` | | immersionStatusBar | 是否是沉浸式状态栏。`default: false` | `boolean` | `Android、Voltron` | | darkStatusBarText | 是否是亮色主体文字,默认字体是黑色的,改成 true 后会认为 Modal 背景为暗色调,字体就会改成白色。 | `boolean` | `Android、iOS、Voltron` | +| autoHideStatusBar | 是否在`Modal`显示时自动隐藏状态栏。Android 中仅 api28 以上生效。 `default: false` | `boolean` | `Android` | +| autoHideNavigationBar | 是否在`Modal`显示时自动隐藏导航栏。 `default: false` | `boolean` | `Android` | | onShow | 在`Modal`显示时会执行此回调函数。 | `Function` | `Android、iOS、hippy-react-web、Web-Renderer、Voltron` | | onOrientationChange | 屏幕旋转方向改变时执行会回调 | `Function` | `Android、iOS` | | onRequestClose | 在 `Modal` 请求关闭时会执行此回调函数,一般时在 Android 系统里按下硬件返回按钮时触发,一般要在里面处理关闭弹窗。 | `Function` | `Android、hippy-react-web、Voltron` | diff --git a/docs/api/hippy-vue/external-components.md b/docs/api/hippy-vue/external-components.md index 8c175c52baa..1e47c97ee04 100644 --- a/docs/api/hippy-vue/external-components.md +++ b/docs/api/hippy-vue/external-components.md @@ -162,6 +162,9 @@ export default { | supportedOrientations | 支持屏幕翻转方向 | `enum(portrait, portrait-upside-down, landscape, landscape-left, landscape-right)[]` | `iOS` | | immersionStatusBar | 是否是沉浸式状态栏。`default: true` | `boolean` | `Android、Voltron` | | darkStatusBarText | 是否是亮色主体文字,默认字体是黑色的,改成 true 后会认为 Modal 背景为暗色调,字体就会改成白色。 | `boolean` | `Android、iOS、Voltron` | +| autoHideStatusBar | 是否在`Modal`显示时自动隐藏状态栏。Android 中仅 api28 以上生效。 `default: false` | `boolean` | `Android` | +| autoHideNavigationBar | 是否在`Modal`显示时自动隐藏导航栏。 `default: false` | `boolean` | `Android` | + | transparent | 背景是否是透明的。`default: true` | `boolean` | `Android、iOS、Web-Renderer、Voltron` | ## 事件 diff --git a/driver/js/examples/hippy-react-demo/src/components/Modal/index.jsx b/driver/js/examples/hippy-react-demo/src/components/Modal/index.jsx index e08f6cce27a..5bf6a3fb443 100644 --- a/driver/js/examples/hippy-react-demo/src/components/Modal/index.jsx +++ b/driver/js/examples/hippy-react-demo/src/components/Modal/index.jsx @@ -39,11 +39,13 @@ const styles = StyleSheet.create({ }, selectionText: { fontSize: 20, - color: SKIN_COLOR.mainLight, textAlign: 'center', textAlignVertical: 'center', marginLeft: 10, marginRight: 10, + padding: 5, + borderRadius: 5, + borderWidth: 2, }, }); @@ -55,6 +57,9 @@ export default class ModalExpo extends React.Component { visible: false, press: false, animationType: 'fade', + immerseStatusBar: false, + hideStatusBar: false, + hideNavigationBar: false, }; this.show = this.show.bind(this); this.hide = this.hide.bind(this); @@ -98,24 +103,62 @@ export default class ModalExpo extends React.Component { {this.setState({animationType: 'fade'})}} - style={[styles.selectionText, {backgroundColor: this.state.animationType === 'fade' ? 'rgba(255, 0, 0, 0.5)' : '#FFFFFF'}]} + style={[styles.selectionText, + {borderColor: this.state.animationType === 'fade' ? 'red' : SKIN_COLOR.mainLight}, + {color: this.state.animationType === 'fade' ? 'red' : SKIN_COLOR.mainLight} + ]} >fade {this.setState({animationType: 'slide'})}} - style={[styles.selectionText, {backgroundColor: this.state.animationType === 'slide' ? 'rgba(255, 0, 0, 0.5)' : '#FFFFFF'}]} + style={[styles.selectionText, + {borderColor: this.state.animationType === 'slide' ? 'red' : SKIN_COLOR.mainLight}, + {color: this.state.animationType === 'slide' ? 'red' : SKIN_COLOR.mainLight} + ]} >slide {this.setState({animationType: 'slide_fade'})}} - style={[styles.selectionText, {backgroundColor: this.state.animationType === 'slide_fade' ? 'rgba(255, 0, 0, 0.5)' : '#FFFFFF'}]} + style={[styles.selectionText, + {borderColor: this.state.animationType === 'slide_fade' ? 'red' : SKIN_COLOR.mainLight}, + {color: this.state.animationType === 'slide_fade' ? 'red' : SKIN_COLOR.mainLight} + ]} >slide_fade + + {this.setState({hideStatusBar: !this.state.hideStatusBar})}} + style={[styles.selectionText, + {borderColor: this.state.hideStatusBar ? 'red' : SKIN_COLOR.mainLight}, + {color: this.state.hideStatusBar ? 'red' : SKIN_COLOR.mainLight} + ]} + >autoHideStatusBar + + + {this.setState({immerseStatusBar: !this.state.immerseStatusBar})}} + style={[styles.selectionText, + {borderColor: this.state.immerseStatusBar ? 'red' : SKIN_COLOR.mainLight}, + {color: this.state.immerseStatusBar ? 'red' : SKIN_COLOR.mainLight} + ]} + >immersionStatusBar + + + {this.setState({hideNavigationBar: !this.state.hideNavigationBar})}} + style={[styles.selectionText, + {borderColor: this.state.hideNavigationBar ? 'red' : SKIN_COLOR.mainLight}, + {color: this.state.hideNavigationBar ? 'red' : SKIN_COLOR.mainLight} + ]} + >autoHideNavigationBar + { /* Trigger when hardware back pressed */ }} supportedOrientations={['portrait']} - immersionStatusBar={true} + immersionStatusBar={this.state.immerseStatusBar} + autoHideStatusBar={this.state.hideStatusBar} + autoHideNavigationBar={this.state.hideNavigationBar} > 显示对话框--slide_fade + clickDialogConfig('hideStatusBar')" + > + 隐藏状态栏 + + clickDialogConfig('immerseStatusBar')" + > + 沉浸式状态栏 + + clickDialogConfig('hideNavigationBar')" + > + 隐藏导航栏 + @@ -52,6 +76,9 @@ v-if="dialog2IsVisible" :animationType="dialogAnimationType" :transparent="true" + :immersionStatusBar="immersionStatusBar" + :autoHideStatusBar="autoHideStatusBar" + :autoHideNavigationBar="autoHideNavigationBar" @requestClose="onClose" > 显示对话框--slide_fade + onClickDialogConfig('hideStatusBar')" + > + 隐藏状态栏 + + onClickDialogConfig('immerseStatusBar')" + > + 沉浸式状态栏 + + onClickDialogConfig('hideNavigationBar')" + > + 隐藏导航栏 + @@ -93,11 +117,32 @@ export default defineComponent({ const dialog2IsVisible = ref(false); // dialog 动画效果 const dialogAnimationType = ref('fade'); + // 是否沉浸式状态栏 + const immersionStatusBar = ref(false); + // 是否隐藏状态栏 + const autoHideStatusBar = ref(false); + // 是否隐藏导航栏 + const autoHideNavigationBar = ref(false); const onClickView = (type = '') => { dialogIsVisible.value = !dialogIsVisible.value; dialogAnimationType.value = type; }; + const onClickDialogConfig = (option) => { + switch (option) { + case 'hideStatusBar': + autoHideStatusBar.value = !autoHideStatusBar.value; + break; + case 'immerseStatusBar': + immersionStatusBar.value = !immersionStatusBar.value; + break; + case 'hideNavigationBar': + autoHideNavigationBar.value = !autoHideNavigationBar.value; + break; + default: + break; + } + }; const onClickOpenSecond = (evt) => { evt.stopPropagation(); dialog2IsVisible.value = !dialog2IsVisible.value; @@ -139,11 +184,15 @@ export default defineComponent({ dialogIsVisible, dialog2IsVisible, dialogAnimationType, + immersionStatusBar, + autoHideStatusBar, + autoHideNavigationBar, stopPropagation, onClose, onShow, onClickView, onClickOpenSecond, + onClickDialogConfig, }; }, }); diff --git a/driver/js/packages/hippy-react/src/components/modal.tsx b/driver/js/packages/hippy-react/src/components/modal.tsx index e80acb72ea4..d5776187782 100644 --- a/driver/js/packages/hippy-react/src/components/modal.tsx +++ b/driver/js/packages/hippy-react/src/components/modal.tsx @@ -77,6 +77,13 @@ interface ModalProps { */ autoHideStatusBar?: boolean; + /** + * Hide navigation bar when Modal is showing + * + * Default: false + */ + autoHideNavigationBar?: boolean; + /** * The animation effect when toggle * diff --git a/driver/js/packages/hippy-vue-native-components/src/dialog.ts b/driver/js/packages/hippy-vue-native-components/src/dialog.ts index aa08186e3bf..ba8f4dc35e9 100644 --- a/driver/js/packages/hippy-vue-native-components/src/dialog.ts +++ b/driver/js/packages/hippy-vue-native-components/src/dialog.ts @@ -48,6 +48,14 @@ function registerDialog(Vue: any) { type: Boolean, default: true, }, + autoHideStatusBar: { + type: Boolean, + default: false, + }, + autoHideNavigationBar: { + type: Boolean, + default: false, + }, }, render(h: any) { const firstChild = getFirstComponent(this.$slots.default); @@ -63,7 +71,7 @@ function registerDialog(Vue: any) { }); } } - const { collapsable, transparent, immersionStatusBar } = this; + const { collapsable, transparent, immersionStatusBar, autoHideStatusBar, autoHideNavigationBar } = this; return h( 'hi-dialog', { @@ -72,6 +80,8 @@ function registerDialog(Vue: any) { collapsable, transparent, immersionStatusBar, + autoHideStatusBar, + autoHideNavigationBar, }, }, this.$slots.default, diff --git a/driver/js/packages/hippy-vue-next/src/native-component/dialog.ts b/driver/js/packages/hippy-vue-next/src/native-component/dialog.ts index 7ebdb4f4273..8361b062f6c 100644 --- a/driver/js/packages/hippy-vue-next/src/native-component/dialog.ts +++ b/driver/js/packages/hippy-vue-next/src/native-component/dialog.ts @@ -31,6 +31,8 @@ export function registerDialog(): void { transparent: true, immersionStatusBar: true, collapsable: false, + autoHideStatusBar: false, + autoHideNavigationBar: false, }, defaultNativeStyle: { position: 'absolute', diff --git a/renderer/native/android/src/main/java/com/tencent/mtt/hippy/views/modal/HippyModalHostManager.java b/renderer/native/android/src/main/java/com/tencent/mtt/hippy/views/modal/HippyModalHostManager.java index 1718e90b821..f819745f19f 100644 --- a/renderer/native/android/src/main/java/com/tencent/mtt/hippy/views/modal/HippyModalHostManager.java +++ b/renderer/native/android/src/main/java/com/tencent/mtt/hippy/views/modal/HippyModalHostManager.java @@ -70,6 +70,16 @@ public void setAnimationType(HippyModalHostView view, String animationType) { view.setAnimationType(animationType); } + @HippyControllerProps(name = "autoHideStatusBar", defaultType = HippyControllerProps.BOOLEAN) + public void autoHideStatusBar(HippyModalHostView view, boolean fullScreen) { + view.autoHideStatusBar(fullScreen); + } + + @HippyControllerProps(name = "autoHideNavigationBar", defaultType = HippyControllerProps.BOOLEAN) + public void autoHideNavigationBar(HippyModalHostView view, boolean fullScreen) { + view.autoHideNavigationBar(fullScreen); + } + @HippyControllerProps(name = "immersionStatusBar", defaultType = HippyControllerProps.BOOLEAN) public void setEnterImmersionStatusBar(HippyModalHostView view, boolean fullScreen) { view.setEnterImmersionStatusBar(fullScreen); diff --git a/renderer/native/android/src/main/java/com/tencent/mtt/hippy/views/modal/HippyModalHostView.java b/renderer/native/android/src/main/java/com/tencent/mtt/hippy/views/modal/HippyModalHostView.java index 247184ac990..e7bb9ef9975 100644 --- a/renderer/native/android/src/main/java/com/tencent/mtt/hippy/views/modal/HippyModalHostView.java +++ b/renderer/native/android/src/main/java/com/tencent/mtt/hippy/views/modal/HippyModalHostView.java @@ -23,6 +23,7 @@ import android.content.DialogInterface; import android.graphics.Canvas; import android.graphics.Color; +import android.graphics.drawable.ColorDrawable; import android.os.Build; import android.view.KeyEvent; import android.view.View; @@ -34,6 +35,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.core.view.DisplayCutoutCompat; import com.openhippy.renderer.R; import com.tencent.mtt.hippy.HippyInstanceLifecycleEventListener; @@ -59,6 +61,8 @@ public enum AnimationStyleTheme { private static final String ANIMATION_TYPE_SLIDE_FADE = "slide_fade"; private static final String ANIMATION_PROPERTY_ALPHA = "alpha"; private static final String ANIMATION_PROPERTY_TRANSLATION_Y = "translationY"; + private boolean mAutoHideStatusBar = false; + private boolean mAutoHideNavigationBar = false; private boolean mEnterImmersionStatusBar = false; private boolean mStatusBarTextDarkColor = false; private boolean mTransparent = true; @@ -203,6 +207,14 @@ protected String getAnimationType() { return mAnimationType; } + protected void autoHideStatusBar(boolean hide) { + mAutoHideStatusBar = hide; + } + + protected void autoHideNavigationBar(boolean hide) { + mAutoHideNavigationBar = hide; + } + protected void setEnterImmersionStatusBar(boolean fullScreen) { mEnterImmersionStatusBar = fullScreen; } @@ -225,20 +237,33 @@ protected void setDialogBar(boolean isDarkIcon) { return; } int sysUI = window.getDecorView().getSystemUiVisibility(); - sysUI = sysUI & ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; - sysUI = sysUI & ~View.SYSTEM_UI_FLAG_LAYOUT_STABLE; - int extra; - if (isDarkIcon) { - extra = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; - } else { - extra = View.SYSTEM_UI_FLAG_LAYOUT_STABLE; + + if (mAutoHideStatusBar && Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + WindowManager.LayoutParams lp = window.getAttributes(); + lp.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; + window.setAttributes(lp); + sysUI = sysUI | View.SYSTEM_UI_FLAG_FULLSCREEN + | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY; + } else if (mEnterImmersionStatusBar) { + sysUI = sysUI & ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; + sysUI = sysUI & ~View.SYSTEM_UI_FLAG_LAYOUT_STABLE; + int extra; + if (isDarkIcon) { + extra = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; + } else { + extra = View.SYSTEM_UI_FLAG_LAYOUT_STABLE; + } + sysUI = sysUI | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | extra; + window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); + window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); + window.setStatusBarColor(Color.TRANSPARENT); } - window.getDecorView() - .setSystemUiVisibility(sysUI | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN - | extra); - window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); - window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); - window.setStatusBarColor(Color.TRANSPARENT); + + if (mAutoHideNavigationBar) { + sysUI = sysUI | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION; + } + + window.getDecorView().setSystemUiVisibility(sysUI); } catch (Throwable e) { e.printStackTrace(); } @@ -256,7 +281,7 @@ private void initDialog() { if (mDialog == null) { return; } - if (mDialog.getWindow() != null && mEnterImmersionStatusBar) { + if (mDialog.getWindow() != null) { setDialogBar(mStatusBarTextDarkColor); } switch (mAnimationStyleTheme) {