Google Inbox 如何显示覆盖键盘的小吃栏?

Posted

技术标签:

【中文标题】Google Inbox 如何显示覆盖键盘的小吃栏?【英文标题】:How does Google Inbox show a snackbar covering the keyboard? 【发布时间】:2018-01-27 09:04:26 【问题描述】:

Google Inbox 如何在键盘上方(覆盖、分层)显示快餐栏?这不是默认行为,我已经使用基本协调器布局、键盘打开和小吃栏进行了测试:默认行为是在键盘后面显示小吃栏。关于这个问题有很多答案:'How do I show thesnackbar above the keyboard',但我还没有找到如何重现 Google 自己的 Inbox 应用程序的snackbar行为。

你能告诉我如何做到这一点吗?

【问题讨论】:

我尝试将小吃店视图的高度(默认为 6)设置为较大的数字,但这无济于事。我尝试了高达 5000 的海拔高度! 我假设他们必须使用覆盖窗口(例如,TYPE_SYSTEM_OVERLAY pre-8.0,TYPE_APPLICATION_OVERLAY on 8.0+)。我不使用 Google Inbox,所以我无法测试这种情况。但是,如果 Inbox 在 8.0+ 上没有这种行为,但在 7.1 及更早版本上却有,那么它很可能是覆盖,因为overlays cannot overlay the IME on 8.0+。 elevations 仅影响您的应用程序布局内部。我认为创建具有不同上下文的 Snakebar 可能有一些技巧。 @CommonsWare 它出现在 android 7.1(有问题的屏幕截图)和 8.0(我当前的手机)上。 “很少有应用程序应该使用此权限;这些 [覆盖] 窗口旨在与用户进行系统级交互。” (developer.android.com/reference/android/…)。我的用例没有使使用此权限合法化:我想在快餐栏打开时向我的用户提供反馈,并且我支持在键盘上方没有空间放置快餐栏的小屏幕。此外,我在 Google Inbox 中看到了这种小吃店行为,所以我认为这对我来说是一个很好的解决方案。还没有运气,看来是这样。 【参考方案1】:

Inbox 不使用标准的 Snackbar,也不使用覆盖窗口,它使用自定义视图。我反编译了 APK 以验证它们是如何实现这种效果的,并尝试重现一个最小的示例。

首先新建一个布局custom_snackbar.xml

<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_
    android:layout_
    android:background="#323232"
    android:paddingLeft="10dp"
    android:paddingRight="10dp">

    <TextView
        android:id="@+id/text"
        android:layout_
        android:layout_
        android:layout_weight="1"
        android:gravity="center_vertical"
        android:text="Marked done"
        android:textColor="@android:color/white"/>

    <Button
        android:id="@+id/button"
        android:layout_
        android:layout_
        android:background="@android:color/transparent"
        android:gravity="center_vertical"
        android:minWidth="48dp"
        android:text="undo"
        android:textAllCaps="true"
        android:textColor="#a1c2fa"/>

</LinearLayout>

然后创建这些方法来显示和关闭自定义 Snackbar:

private View customSnackbar;

private void showCustomSnackbar(final Context context) 
    customSnackbar = LayoutInflater.from(context).inflate(R.layout.custom_snackbar, null, false);
    customSnackbar.findViewById(R.id.button).setOnClickListener(new View.OnClickListener() 
        @Override
        public void onClick(View v) 
            //action implementation
        
    );

    WindowManager.LayoutParams lp = new WindowManager.LayoutParams();
    lp.height = WindowManager.LayoutParams.WRAP_CONTENT;
    lp.width = WindowManager.LayoutParams.MATCH_PARENT;
    lp.format = PixelFormat.TRANSLUCENT;
    lp.gravity = Gravity.BOTTOM;
    lp.flags = WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH |
            WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
            WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
    customSnackbar.setLayoutParams(lp);

    WindowManager windowManager = (WindowManager) context.getSystemService(WINDOW_SERVICE);

    if (windowManager != null) 
        windowManager.addView(customSnackbar, customSnackbar.getLayoutParams());

        Point m = new Point();
        windowManager.getDefaultDisplay().getSize(m);
        int childMeasureSpecWidth = ViewGroup.getChildMeasureSpec(View.MeasureSpec.makeMeasureSpec(m.x, View.MeasureSpec.EXACTLY), 0, lp.width);
        int childMeasureSpecHeight = ViewGroup.getChildMeasureSpec(View.MeasureSpec.makeMeasureSpec(m.y, View.MeasureSpec.EXACTLY), 0, lp.height);
        customSnackbar.measure(childMeasureSpecWidth, childMeasureSpecHeight);

        customSnackbar.setTranslationY(customSnackbar.getMeasuredHeight());
        customSnackbar.animate()
                .setDuration(300)
                .translationX(0.0f)
                .translationY(0.0f);
    


private void dismissCustomSnackbar(final Context context) 
    customSnackbar.animate()
            .setDuration(300)
            .translationX(0.0f)
            .translationY(customSnackbar.getMeasuredHeight())
            .withEndAction(new Runnable() 
                @Override
                public void run() 
                    WindowManager windowManager = (WindowManager) context.getSystemService(WINDOW_SERVICE);
                    if (windowManager != null) 
                        windowManager.removeView(customSnackbar);
                    
                
            );

这是得到的结果:

【讨论】:

我不太喜欢WindowManager...但是为什么它会出现在 IME 上?我认为 Android 8.0 中关于WindowManager-add windows 的更改应该可以防止这种事情发生。无论如何,干得好! Android 8.0 的限制似乎只适用于TYPE_APPLICATION_OVERLAY 类型。在此示例中,视图具有默认类型 TYPE_APPLICATION 谢谢,太棒了。我稍后会在我的应用程序中尝试这个,如果出现任何怪癖或边缘情况,我会在此处报告。我也猜想有可能以常规方式构建一个Snackbar,然后,如果键盘是打开的,而不是定期显示它,使用这种方法将它显示在键盘前面。这样,这种自定义的快餐栏行为可以适应不同的键盘情况。

以上是关于Google Inbox 如何显示覆盖键盘的小吃栏?的主要内容,如果未能解决你的问题,请参考以下文章

如果文本很长,如何使小吃栏操作按钮显示在不同的行中?

如何在 Flutter 中的 navigator.pop(context) 之后显示小吃栏?

在每个屏幕上的 fcm 通知上显示小吃栏

使用 API 访问 Google Inbox 提醒

如何在颤动中显示小吃吧无限持续时间?

如何在颤动中显示小吃店消息