是否有任何方法使Snackbar在活动变化中持续存在?

时间:2015-10-21 15:58:19

标签: android android-studio

尽管Snackbar很漂亮,但在更改活动时它不会持续存在。在完成活动之前,我想确认使用Snackbar发送消息的情况下,这是一个无聊的事情。我已经考虑在退出活动之前暂停代码,但发现这是一个不好的做法。

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

6 个答案:

答案 0 :(得分:25)

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

  1. 获取WindowManager作为系统服务
  2. 创建{{1>}( rootView )并将FrameLayoutWindowManager.LayoutParams.TYPE_TOAST类型添加到WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
  3. 等到WindowManager rootView
  4. 中调用FrameLayout.onAttachedToWindow()
  5. 使用FrameLayout
  6. 获取FrameLayout rootView )的窗口令牌
  7. 使用应用程序上下文和派生的View.getWindowToken()
  8. 创建ContextThemeWrapper
  9. 使用新上下文创建其他@style/Theme.AppCompat snackbarContainer
  10. 使用FrameLayout类型并标记FrameLayout
  11. 添加此WindowManager.LayoutParams.TYPE_APPLICATION_PANEL snackbarContainer
  12. 等到WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL snackbarContainer
  13. 调用View.onAttachedToWindow()
  14. 使用FrameLayout snackbarContainer )正常创建Snackbar
  15. FrameLayout回调设置为Snackbar并删除FrameLayouts( rootView snackbarContainer
  16. 显示小吃栏View.onDismissed()
  17. 这是一个工作包装注意:滑动以解除不起作用。也许其他人找到正确的Snackbar.show()标志来接收触摸事件修正者CoordinatorLayout):

    WindowManager.LayoutParams

    修改
    定义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

    修改
    对于那些在Android Oreo上获得Bad Token Exception的用户,请将TYPE_TOAST更改为TYPE_APPLICATION_OVERLAY。这是因为Android Oreo实现了绘制应用程序的特殊权限。您可以使用以下方式申请此权限:

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

答案 1 :(得分:5)

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

  1. 活动A发布活动B以发送消息
  2. 发送消息后,您将显示确认消息
  3. 您将返回活动A
  4. 你可以使用SnackBar来使用ActivityResult(here 是一个StackOverflow帖子,如何使用它)

    以下是步骤:

    1. 活动A使用startActivityForResult启动活动B
    2. 在活动B上做你的东西
    3. 设置结果(查看上面的链接以了解)
    4. 完成活动
    5. 在活动A中,在OnActivityResult中获取该代码并显示您的 带有正确信息的SnackBar
    6. 这允许您在活动A中显示与活动B的结果相对应的Snackar。

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

答案 2 :(得分:1)

更新:查看所选答案。

我的问题的最佳解决方案是在显示Timer之后使用Snackbar,然后在计时器的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在片段更改中仍然存在。

答案 3 :(得分:1)

要使用矩形Toast,请为Toast设置矩形背景,或者为Toast设置不同的背景颜色。

请将this post作为问题发布。但在你的情况下,这是一个可能的解决方案。

答案 4 :(得分:0)

如果有人需要在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);
    }
}

答案 5 :(得分:0)

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

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

(不要对我投赞成票,对写答案的“ Myke Dev”投反对票)

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

(也许您可以使用没有背景的活动来启动类似小吃店的对话框,但它不能仅用作window_alert快餐店)