当前位置: 代码迷 >> Android >> 有什么办法可以让 Snackbar 在活动变化中保持不变?
  详细解决方案

有什么办法可以让 Snackbar 在活动变化中保持不变?

热度:57   发布时间:2023-08-04 10:44:00.0

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

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

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

  1. 获取WindowManager作为系统服务
  2. 创建一个WindowManager.LayoutParams.TYPE_TOASTWindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL类型的FrameLayout ( rootView ) 并添加到WindowManager
  3. 等到FrameLayout.onAttachedToWindow()FrameLayout ( rootView ) 中被调用
  4. 使用View.getWindowToken()获取FrameLayout ( rootView ) 的窗口令牌
  5. 创建ContextThemeWrapper与应用程序上下文和派生@style/Theme.AppCompat
  6. 使用新的上下文创建一个额外的FrameLayoutsnackbarContainer
  7. 添加类型为WindowManager.LayoutParams.TYPE_APPLICATION_PANEL和标志WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL FrameLayoutsnackbarContainer
  8. 等到View.onAttachedToWindow()FrameLayout被调用( snackbarContainer
  9. 使用FrameLayout像往常一样创建 Snackbar( snackbarContainer
  10. View.onDismissed()回调设置为 Snackbar 并删除 FrameLayouts( rootViewsnapbarContainer
  11. 显示小吃店Snackbar.show()

这里有一个工作包装器 注意:滑动关闭不起作用。也许其他人找到了正确的 WindowManager.LayoutParams标志来接收 由 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 ,您可以像这样使用它:

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快速定义一个:

<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 实施了特殊权限来绘制应用程序。 您可以使用以下方法请求此权限:

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

如果我理解正确,你可以这样做:

  1. Activity A 启动 Activity B 发送消息
  2. 发送消息后,您会显示一条确认消息
  3. 你回到活动A

您可以通过使用 ActivityResult 来使用 SnackBar 来做到这一点( 是一篇关于如何使用它的 StackOverflow 帖子)

以下是步骤:

  1. 活动 A 使用 startActivityForResult 启动活动 B
  2. 在活动 B 上做你的事
  3. 设置您的结果(查看上面的链接以了解)
  4. 完成活动
  5. 在活动 A 中,在 OnActivityResult 中获取该代码并显示带有正确消息的 SnackBar

这允许您在活动 A 中显示与活动 B 的结果相对应的 Snackar。

希望它可以帮助你的问题

以防万一有人需要在 Xamarin 中执行此操作,我已经修改我发现非常有用的已。

using Android.Content;
using Android.Graphics;
using Android.OS;
using Android.Runtime;
using Android.Support.Design.Widget;
using Android.Views;
using Android.Widget;
using System;

public class SnackbarWrapper
{
    private readonly string text;
    private readonly int duration;
    private readonly IWindowManager windowManager;
    private readonly Context appplicationContext;
    private Snackbar.Callback externalCallback;
    private SnackbarAction action { get; set; }

    public static SnackbarWrapper make(Context applicationContext, string text, int duration)
    {
        return new SnackbarWrapper(applicationContext, text, duration);
    }

    private SnackbarWrapper(Context appplicationContext, string text, int duration)
    {
        this.appplicationContext = appplicationContext;
        var wm = appplicationContext.GetSystemService(Context.WindowService);
        // We have to use JavaCast instead of a normal cast
        this.windowManager = wm.JavaCast<IWindowManager>();
        this.text = text;
        this.duration = duration;
    }

    public void Show()
    {
        WindowManagerLayoutParams layoutParams = createDefaultLayoutParams(WindowManagerTypes.Toast, null);
        var frameLayout = new FrameLayout(appplicationContext);
        frameLayout.ViewAttachedToWindow += delegate
        {
            //this.onAttachedToWindow();
            onRootViewAvailable(frameLayout);
        };

        windowManager.AddView(frameLayout, layoutParams);
    }

    private void onRootViewAvailable(FrameLayout rootView)
    {
        var ctw = new ContextThemeWrapper(appplicationContext, Resource.Style.Base_Theme_AppCompat);
        CoordinatorLayout snackbarContainer = new CoordinatorLayout(ctw);
        snackbarContainer.ViewAttachedToWindow += delegate
        {
            onSnackbarContainerAttached(rootView, snackbarContainer);
        };

        windowManager.AddView(snackbarContainer, createDefaultLayoutParams(WindowManagerTypes.ApplicationPanel, rootView.WindowToken));
    }

    private void onSnackbarContainerAttached(View rootView, CoordinatorLayout snackbarContainer)
    {
        Snackbar snackbar = Snackbar.Make(snackbarContainer, text, duration);

        snackbar.SetCallback(new SnackbarCallbackImpl(rootView, snackbarContainer, windowManager));

        if (action != null)
        {
            snackbar.SetAction(action.Text, action.Listener);
        }
        snackbar.Show();
    }

    private WindowManagerLayoutParams createDefaultLayoutParams(WindowManagerTypes type, IBinder windowToken)
    {
        WindowManagerLayoutParams layoutParams = new WindowManagerLayoutParams();
        layoutParams.Format = Format.Translucent;
        layoutParams.Width = ViewGroup.LayoutParams.MatchParent;
        /* Si ponemos aqui WrapContent en alguna ocasion en la que haya un action largo y el texto tambien, el snackbar puede volverse como loco
         * asi que usamos MatchParent. Aun asi sucede que a veces se puede mostrar en una linea o en dos el mismo texto, pero al menos no hace el temblor loco que de la otra forma*/
        layoutParams.Height = ViewGroup.LayoutParams.MatchParent;
        layoutParams.Gravity = GravityFlags.CenterHorizontal | GravityFlags.Bottom;
        layoutParams.Flags = WindowManagerFlags.NotTouchModal;
        layoutParams.Type = type;
        layoutParams.Token = windowToken;
        return layoutParams;
    }

    public SnackbarWrapper SetCallback(Snackbar.Callback callback)
    {
        this.externalCallback = callback;
        return this;
    }

    public SnackbarWrapper SetAction(string text, Action<View> listener)
    {
        action = new SnackbarAction(text, listener);
        return this;
    }

}//class

internal class SnackbarAction
{
    public string Text { get; set; }
    public Action<View> Listener { get; set; }

    public SnackbarAction(string text, Action<View> listener)
    {
        Text = text;
        Listener = listener;
    }
}

internal class SnackbarCallbackImpl : Snackbar.Callback
{
    public Snackbar.Callback externalCallback { get; set; }

    View rootView;
    CoordinatorLayout snackbarContainer;
    IWindowManager windowManager;

    public SnackbarCallbackImpl(View rootView, CoordinatorLayout snackbarContainer, IWindowManager windowManager)
    {
        this.rootView = rootView;
        this.snackbarContainer = snackbarContainer;
        this.windowManager = windowManager;
    }

    public override void OnShown(Snackbar snackbar)
    {
        base.OnShown(snackbar);
        externalCallback?.OnShown(snackbar);
    }

    public override void OnDismissed(Snackbar snackbar, int evt)
    {
        base.OnDismissed(snackbar, evt);

        // Clean up (NOTE! This callback can be called multiple times)
        if (snackbarContainer.Parent != null && rootView.Parent != null)
        {
            windowManager.RemoveView(snackbarContainer);
            windowManager.RemoveView(rootView);
        }

        externalCallback?.OnDismissed(snackbar, evt);
    }
}

要获得矩形Toast ,请为Toast设置矩形背景,或者仅为 Toast 设置不同的背景颜色。

请参阅,将其发布为问题。 但对于您的情况,这是一个可能的解决方案。

更新:请参阅选定的答案。

我的问题的最佳解决方案是在呈现Snackbar之后使用Timer ,然后在计时器的run()方法中启动活动。

Snackbar.show(); // Excluded make for brevity.

Timer timer = new Timer();
    timer.schedule(new TimerTask() {
        @Override
        public void run() {
            Intent chooseVideoIntent = new Intent(Intent.ACTION_GET_CONTENT); // Any type of content/file. Song, doc, video...
            chooseVideoIntent.setType("video/*");
            startActivityForResult(chooseVideoIntent, CHOOSE_VIDEO_REQUEST);
        }
    }, 2 * 1000);

更新:我发现通过使用findViewById(android.R.id.content)作为Snackbar.make()的视图, Snackbar.make()片段更改中持续存在。

实际上我只需要显示一条消息,我不需要消息上的 onClickListener 。 如果您只需要显示一条消息,请查看此线程中的“Myke Dev”答案,这是我需要的答案:

(不要给我投票,给写答案的“Myke Dev”投票)

user1185087答案中,您必须通过打开设置来请求用户的权限,这对我来说对于用户交互来说不是一件好事,但我认为如果您想显示带有 onClickListener 的小吃店,这是唯一的方法。

(也许你可以使用一个没有背景的活动来启动一个类似小吃店的对话框,但它不会只是一个 window_alert 小吃店)