有什么办法可以让 Snackbar 在活动变化中保持不变? [英] Is there any way make Snackbar persist among activity changes?

查看:18
本文介绍了有什么办法可以让 Snackbar 在活动变化中保持不变?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

虽然 Snackbar 很漂亮,但它在改变 Activity 时不会持久化.在我想在完成活动之前确认消息是使用 Snackbar 发送的情况下,这是一个无赖.我曾考虑在退出活动之前暂停代码,但发现这是一种不好的做法.

Although Snackbar is beautiful, it doesn't persist when changing activities. This is a bummer in scenarios where I would like to confirm that a message was sent using a Snackbar, before finishing the activity. I've considered pausing the code before exiting the activity, but have found that to be a bad practice.

如果我所描述的不可行,是否有任何类型的 Material Design Toast 消息?或者一种制作矩形吐司消息的方法;一个半径较小的圆形边缘?

If what I describe isn't possible, is there any type of material design toast message? Or a way to make a rectangular toast message; one with rounded edges of a smaller radius?

推荐答案

使用跨多个 Activity 可见的应用程序上下文创建 Snackbar:

To create a Snackbar with the application context which is visible across multiple activities:

  1. 获取WindowManager作为系统服务
  2. 创建并添加类型为 WindowManager.LayoutParams.TYPE_TOASTWindowManager.LayoutParams.FLAG_NOT_TOUCH_MODALFrameLayout (rootView) 的FrameLayout (rootView)code> 到 WindowManager
  3. 等待 FrameLayout.onAttachedToWindow()FrameLayout (rootView) 中被调用
  4. 使用 View.getWindowToken()
  5. 获取FrameLayout (rootView) 的窗口令牌
  6. 使用应用程序上下文和派生的 @style/Theme.AppCompat
  7. 创建一个 ContextThemeWrapper
  8. 使用新上下文创建额外的FrameLayout (snackbarContainer)
  9. 使用 WindowManager.LayoutParams.TYPE_APPLICATION_PANEL 类型和标记 WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL 添加此 FrameLayout (snackbarContainer)>
  10. 等待 View.onAttachedToWindow()FrameLayout (snackbarContainer) 中被调用
  11. 使用 FrameLayout (snackbarContainer) 像平常一样创建 Snackbar
  12. View.onDismissed() 回调设置为 Snackbar 并移除 FrameLayouts(rootViewsnackbarContainer)
  13. 显示小吃店 Snackbar.show()
  1. Get the WindowManager as system service
  2. Create and add a FrameLayout (rootView) with type WindowManager.LayoutParams.TYPE_TOAST and WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL to the WindowManager
  3. Wait until on FrameLayout.onAttachedToWindow() is called in the FrameLayout (rootView)
  4. Get the window token of the FrameLayout (rootView) with View.getWindowToken()
  5. Create a ContextThemeWrapper with the application context and a derived @style/Theme.AppCompat
  6. Use the new context to create an additional FrameLayout (snackbarContainer)
  7. Add this FrameLayout (snackbarContainer) with type WindowManager.LayoutParams.TYPE_APPLICATION_PANEL and flag WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
  8. Wait until on View.onAttachedToWindow() is called in the FrameLayout (snackbarContainer)
  9. Create the Snackbar like normal with the FrameLayout (snackbarContainer)
  10. Set View.onDismissed() callback to the Snackbar and remove the FrameLayouts (rootView and snackbarContainer)
  11. Show the snackbar Snackbar.show()

这是一个有效的包装器(注意:滑动以关闭是行不通的.也许其他人会找到正确的 WindowManager.LayoutParams 标志来接收触摸事件 由 CoordinatorLayout 修复):

Here a working wrapper (NOTE: Swipe to dismiss is not working. Maybe some one else find the correct WindowManager.LayoutParams flags to receive touch events Fixed by CoordinatorLayout):

public class SnackbarWrapper
{
    private final CharSequence text;
    private final int duration;
    private final WindowManager windowManager;
    private final Context appplicationContext;
    @Nullable
    private Snackbar.Callback externalCallback;
    @Nullable
    private Action action;

    @NonNull
    public static SnackbarWrapper make(@NonNull Context applicationContext, @NonNull CharSequence text, @Snackbar.Duration int duration)
    {
        return new SnackbarWrapper(applicationContext, text, duration);
    }

    private SnackbarWrapper(@NonNull final Context appplicationContext, @NonNull CharSequence text, @Snackbar.Duration int duration)
    {
        this.appplicationContext = appplicationContext;
        this.windowManager = (WindowManager) appplicationContext.getSystemService(Context.WINDOW_SERVICE);
        this.text = text;
        this.duration = duration;
    }

    public void show()
    {
        WindowManager.LayoutParams layoutParams = createDefaultLayoutParams(WindowManager.LayoutParams.TYPE_TOAST, null);
        windowManager.addView(new FrameLayout(appplicationContext)
        {
            @Override
            protected void onAttachedToWindow()
            {
                super.onAttachedToWindow();
                onRootViewAvailable(this);
            }

        }, layoutParams);
    }

    private void onRootViewAvailable(final FrameLayout rootView)
    {
        final CoordinatorLayout snackbarContainer = new CoordinatorLayout(new ContextThemeWrapper(appplicationContext, R.style.FOL_Theme_SnackbarWrapper))
        {
            @Override
            public void onAttachedToWindow()
            {
                super.onAttachedToWindow();
                onSnackbarContainerAttached(rootView, this);
            }
        };
        windowManager.addView(snackbarContainer, createDefaultLayoutParams(WindowManager.LayoutParams.TYPE_APPLICATION_PANEL, rootView.getWindowToken()));
    }

    private void onSnackbarContainerAttached(final View rootView, final CoordinatorLayout snackbarContainer)
    {
        Snackbar snackbar = Snackbar.make(snackbarContainer, text, duration);
        snackbar.setCallback(new Snackbar.Callback()
        {
            @Override
            public void onDismissed(Snackbar snackbar, int event)
            {
                super.onDismissed(snackbar, event);
                // Clean up (NOTE! This callback can be called multiple times)
                if (snackbarContainer.getParent() != null && rootView.getParent() != null)
                {
                    windowManager.removeView(snackbarContainer);
                    windowManager.removeView(rootView);
                }
                if (externalCallback != null)
                {
                    externalCallback.onDismissed(snackbar, event);
                }
            }

            @Override
            public void onShown(Snackbar snackbar)
            {
                super.onShown(snackbar);
                if (externalCallback != null)
                {
                    externalCallback.onShown(snackbar);
                }
            }
        });
        if (action != null)
        {
            snackbar.setAction(action.text, action.listener);
        }
        snackbar.show();
    }

    private WindowManager.LayoutParams createDefaultLayoutParams(int type, @Nullable IBinder windowToken)
    {
        WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();
        layoutParams.format = PixelFormat.TRANSLUCENT;
        layoutParams.width = WindowManager.LayoutParams.MATCH_PARENT;
        layoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
        layoutParams.gravity = GravityCompat.getAbsoluteGravity(Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM, ViewCompat.LAYOUT_DIRECTION_LTR);
        layoutParams.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
        layoutParams.type = type;
        layoutParams.token = windowToken;
        return layoutParams;
    }

    @NonNull
    public SnackbarWrapper setCallback(@Nullable Snackbar.Callback callback)
    {
        this.externalCallback = callback;
        return this;
    }

    @NonNull
    public SnackbarWrapper setAction(CharSequence text, final View.OnClickListener listener)
    {
        action = new Action(text, listener);
        return this;
    }

    private static class Action
    {
        private final CharSequence text;
        private final View.OnClickListener listener;

        public Action(CharSequence text, View.OnClickListener listener)
        {
            this.text = text;
            this.listener = listener;
        }
    }
}

编辑
一旦 SnackbarWrapper 被定义,你可以像这样使用它:

EDIT
Once SnackbarWrapper is defined you can use it like this:

final SnackbarWrapper snackbarWrapper = SnackbarWrapper.make(getApplicationContext(),
            "Test snackbarWrapper", Snackbar.LENGTH_LONG);

snackbarWrapper.setAction(R.string.snackbar_text,
            new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Toast.makeText(getApplicationContext(), "Action",
                            Toast.LENGTH_SHORT).show();
                }
            });

snackbarWrapper.show();

如果你没有主题,你可以在styles.xml中快速定义一个:

If you don't have a theme, you can quickly define one in styles.xml:

<style name="FOL_Theme_SnackbarWrapper" parent="@style/Theme.AppCompat">
    <!--Insert customization here-->
</style>

编辑
对于 Android Oreo 上出现 Bad Token Exception 的用户,请将 TYPE_TOAST 更改为 TYPE_APPLICATION_OVERLAY.这是由于 Android Oreo 实施了特殊权限来绘制应用程序.您可以使用以下方式请求此权限:

EDIT
For those on Android Oreo getting Bad Token Exception, change TYPE_TOAST to TYPE_APPLICATION_OVERLAY. This is due to Android Oreo implementing special permissions to draw over applications. You can ask for this permissions using:

    if(!Settings.canDrawOverlays(Activity.this){
        Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, URI.parse("package:" + getPackageName()));
        startActivityForResult(intent, REQ_CODE);
    }

这篇关于有什么办法可以让 Snackbar 在活动变化中保持不变?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆