【问题标题】:Is there any way make Snackbar persist among activity changes?有没有办法让 Snackbar 在活动更改中持续存在?
【发布时间】:2015-10-21 15:58:19
【问题描述】:

Snackbar虽然很漂亮,但是换活动的时候就不坚持了。在我想在完成活动之前确认使用Snackbar 发送消息的情况下,这真是令人沮丧。我曾考虑在退出活动之前暂停代码,但发现这是一种不好的做法。

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

【问题讨论】:

  • 我假设您想显示一个显示 Message Sent Successfully 的小吃栏,但该事件发生在消息发送后立即完成的活动上?您可以向其后显示的活动发送消息,然后显示该快餐栏。
  • 怎么样?此外,这并不那么重要,在某些情况下我 startActivityForResult() 并使用 Intent.ACTION_GET_CONTENT 在这种情况下我可能处于我没有创建的活动中。
  • 也许 Toast 是更好的选择?
  • 重读我的问题。 Toast 消息不完全是材料设计。
  • 对不起!看看这个,你可以自定义它stackoverflow.com/a/16909532/4130107

标签: android android-studio


【解决方案1】:

使用在多个活动中可见的应用程序上下文创建一个 Snackbar:

  1. 获取WindowManager 作为系统服务
  2. 创建FrameLayout (rootView) 类型为WindowManager.LayoutParams.TYPE_TOASTWindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL 并将其添加到WindowManager
  3. 等到FrameLayout.onAttachedToWindow()FrameLayout 中被调用(rootView
  4. 使用View.getWindowToken() 获取FrameLayout (rootView) 的窗口令牌
  5. 使用应用程序上下文和派生的@style/Theme.AppCompat 创建一个ContextThemeWrapper
  6. 使用新上下文创建一个额外的FrameLayout (snackbarContainer)
  7. 添加此FrameLayout (snackbarContainer) 类型为WindowManager.LayoutParams.TYPE_APPLICATION_PANEL 和标志WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
  8. 等到View.onAttachedToWindow()FrameLayout 中被调用(snackbarContainer
  9. 使用FrameLayout (snackbarContainer) 像平常一样创建 Snackbar
  10. 设置View.onDismissed()回调到Snackbar并移除FrameLayouts(rootViewsnackbarContainer
  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);
    }

【讨论】:

  • onDismissed() 中,您是否使用snackBarContainer 作为container
  • 这可行,但在显示Snackbar 时,用户界面不接受输入。
  • 您说得对,API 级别 16 快餐栏操作不起作用。我编辑了解决方案。操作应该适用于 API 级别 16。(注意:滑动关闭仍然不起作用)
  • 我收到“android.view.WindowManager$BadTokenException: Unable to add window -- token null 无效;您的活动正在运行吗?”它在 Android Oreo 中使用 windowManager.addView 的 show 方法,有人知道如何解决这个问题吗?
  • 我赞成您的回答,因为它很好,但对我不利。我不想添加另一个打开设置的权限请求......这对我来说真的很糟糕:/无论如何都是好的答案
【解决方案2】:

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

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

您可以通过 ActivityResult 使用 SnackBar 来做到这一点(here 是一个 StackOverflow 帖子,其中包含如何使用它)

步骤如下:

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

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

希望它可以帮助您解决问题

【讨论】:

  • 恕我直言,这应该是公认的答案。其他答案也可能有效,但要复杂得多,并且违反了几项编程原则。
  • 谢谢。这似乎是它的预期使用方式。
【解决方案3】:

以防万一有人需要在 Xamarin 中执行此操作,我改编了 the accepted answer,我发现这真的很有帮助。

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);
    }
}

【讨论】:

    【解决方案4】:

    要拥有一个矩形 Toast,为 Toast 设置一个矩形背景,或者为 Toast 设置一个不同的背景颜色。

    请参阅this post,将其作为问题发布。但根据您的情况,这是一个可能的解决方案。

    【讨论】:

      【解决方案5】:

      更新:查看选定的答案。

      我的问题的最佳解决方案是在呈现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 在fragment 更改中持续存在。

      【讨论】:

      • 我尝试使用 Snackbar.make(findViewById(android.R.id.content)...);但小吃店没有显示。
      • 是的,小吃店没有出现。当按钮单击我当前的活动时,我目前正在尝试使快餐栏显示到上一个活动。
      • 这在活动更改之间不适用吗?
      • @brettbrdls 不,它只在片段更改之间对我有用。如果你想出另一种方法,请告诉我。也许还有另一个系统视图可以使用。
      【解决方案6】:

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

      https://stackoverflow.com/a/34640942/9993413

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

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

      (也许您可以使用没有背景的活动来启动类似小吃栏的对话框,但它不会仅充当 window_alert 小吃栏)

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-06-28
        • 2021-09-17
        • 1970-01-01
        • 2016-09-27
        • 2012-08-01
        相关资源
        最近更新 更多