与DialogX一起实现全屏WebView对话框和沉浸式适配

882 阅读5分钟

与DialogX一起实现全屏WebView对话框和沉浸式适配

开发中我们往往会遇到一种需求,需要临时弹出一个 Web 层展示 H5 页面,iOS 端提供了一种原界面下沉的全屏展开 WebView 可以很方便的实现,用户也可以通过下滑操作关闭 Web 页面的浏览返回原界面。

要在 Android 平台实现类似的效果,可以尝试使用 DialogX 的 FullScreenDialog 搭配 WebView 实现沉浸式的全屏浏览器呈现,同样的,也支持滑动到 Web 页面顶端时继续向下滑动关闭对话框的操作,另外也支持沉浸式哦,跟随我一起来看看我是如何实现这样炫酷的功能吧!

image-20230408010247558.png

首先介绍一下 DialogX

DialogX 是一款简单易用的对话框组件,相比原生对话框使用体验更佳,可自定义程度更高,扩展性更强,轻松实现各种对话框、菜单和提示效果,更有iOS、MIUI、Material You等主题扩展可选。

可以移步之前的文章查看具体介绍:使用 DialogX 快速构建 Android App 对话框 - 掘金 (juejin.cn)

先来一起创建一个全屏 WebView 对话框吧!

如上图所示,是一个从屏幕下方展开的全屏对话框,它基于 DialogX 的 FullScreenDialog 实现,只需要自定义一个 WebView 布局即可,要实现滑动继承,需要自定义 WebView 并实现一个 DialogX 的接口,范例代码如下所示:

public class CustomWebView extends WebView implements ScrollController {
    public CustomWebView(@NonNull Context context) {
        super(context);
    }
    
    public CustomWebView(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }
    
    public CustomWebView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
    
    @Override
    @Deprecated
    public boolean isLockScroll() {
        return lockScroll;
    }
    
    boolean lockScroll;
    
    @Override
    public void lockScroll(boolean lockScroll) {
        this.lockScroll = lockScroll;
    }
    
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (lockScroll) return false;
        return super.onTouchEvent(event);
    }
    
    @Override
    public int getScrollDistance() {
        return getScrollY();
    }
    
    boolean canScroll = true;
    
    public CustomWebView setCanScroll(boolean canScroll) {
        this.canScroll = canScroll;
        return this;
    }
    
    @Override
    public boolean isCanScroll() {
        return canScroll;
    }
}

按照上述固定写法即可。

将 CustomWebView 添加一份布局,放上关闭按钮即可完成界面布局的搭建工作。为方便后续操作,暂时将布局命名为 layout_dialog_webview 参考布局代码如下,请注意设置 tag 为 ScrollController 以方便 FullScreenDialog 绑定滑动继承组件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
​
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:orientation="horizontal">
​
        <Space
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_weight="1" />
​
        <TextView
            android:id="@+id/btn_close"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:gravity="center"
            android:paddingLeft="15dp"
            android:paddingRight="15dp"
            android:text="关闭"
            android:textColor="@color/dialogxIOSBlue"
            android:textSize="18dp" />
​
    </LinearLayout>
​
    <com.kongzue.dialogxdemo.CustomWebView
        android:id="@+id/webView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:tag="ScrollController" />
</LinearLayout>

接下来编写 FullScreenDialog 的实现代码

FullScreenDialog.show(new OnBindView<FullScreenDialog>(R.layout.layout_dialog_webview) {
            private TextView btnClose;
            private CustomWebView webView;
            @Override
            public void onBind(FullScreenDialog dialog, View v) {
                btnClose = v.findViewById(R.id.btn_close);
                webView = v.findViewById(R.id.webView);
                //Set WebView Settings...
                webView.loadUrl("https://www.example.com");
            }
        });

即可启动一个全屏展示的 WebView 对话框并加载一个页面。

那么该如何实现沉浸式适配呢?FullScreenDialog 默认情况下已经做好了一些沉浸式工作,例如对话框的内容一定会在沉浸式的“安全区”范围内,对话框顶部只会上移到手机状态栏以下,而对话框内容底部也会在导航栏以上,但这距离我们想要的页面内容“下沉”到导航栏以下的需求不符,此时只需要一个开关即可实现:

.setBottomNonSafetyAreaBySelf(true)

此设置是 FullScreenDialog 的设置,如果没有请更新至最新测试版本的 DialogX,开启后内容布局将被允许下沉到底部导航栏以后显示,此时 Web 页面将能够沉浸的显示在导航栏后了,那么接下来的问题就是如何为页面内容设置一个 paddingBottom,使其内容不分可以在用户可操作的屏幕安全区内,沉浸,但不影响正常使用。

设置页面内容到安全范围内

常见的方案大致是对内容直接设置一个导航栏高度的 paddingBottom,使其背景不受隐藏但内容在安全范围内,

我之前 有一篇文章 有说过沉浸式的基本逻辑在于正确的对安全区和非安全区的处理,原则即:将背景下沉,将操作区域和主要内容区域放在安全区内:

安全区

要实现这点,我们需要获取底部导航栏的高度,再对 WebView 的页面内容进行 paddingBottom 的设置,前者其实 DialogX 已经帮你处理好了,只需要一句代码即可获取:

int bottomUnsafeAreaHeight = dialog.getDialogImpl().boxRoot.getUnsafePlace().bottom;

但实际上,不建议这样粗暴的获取,因为非安全区位置可能因为横竖屏切换,以及不分设备上可以隐藏导航栏导致高度发生变化,更建议的方案是动态回调的方式获取,这样更加安全可靠:

dialog.getDialogImpl().boxRoot.setOnSafeInsetsChangeListener(new OnSafeInsetsChangeListener() {
    @Override
    public void onChange(Rect unsafeRect) {
        int bottomUnsafeAreaHeight = unsafeRect.bottom;
    }
});

bottomUnsafeAreaHeight 的值就是底部非安全区的高度啦,单位是像素。

但此时你又会遇到一个严重问题,那就是 WebView 不吃 setPadding 这套,对 WebView 设置 padding 是无效的,我们需要使页面内容 paddingBottom = bottomUnsafeAreaHeight 的距离以保证当 WebView 滑动到底部后为导航栏空出一段距离。

那么此时就得用到 WebView 的 Client,在其中 onPageFinished 方法设置一段 js 来为页面的 body 设置一段底部 padding 来达成需要的效果,不废话直接上代码:

webView.setWebViewClient(new WebViewClient() {
    @Override
    public void onPageFinished(WebView view, String url) {
        if (unsafeRect != null) {
            webView.loadUrl("javascript:document.body.style.paddingBottom="" + px2dip(bottomUnsafeAreaHeight) + "px"; void 0");
        }
        super.onPageFinished(view, url);
    }
}

其中,px2dip 是一个像素对 dp 的转换工具,因为适配移动端的 H5 在 WebView 呈现时默认会适应屏幕的像素密度,因此对于网页内容中的 px 实际上等同于 dp 的值,因此此处需要将底部非安全区的高度转换为 dp 当做 px 设置给页面。

上述代码为页面的 body 添加了 paddingBottom 的设置,但这需要 H5 页面支持,如果你需要显示的 H5 页面不生效,请根据实际需要检查内部元素适当的调整上述 js 以适配实际需求。

至此,基于 DialogX 的 FullScreenDialog 实现全屏 WebView 和沉浸式适配工作也就基本完成了,让我们在 App 全部沉浸式的道路上更进一步吧!