Есть ли способ заставить Snackbar сохраняться среди изменений активности?

Хотя Snackbar красив, он не сохраняется при изменении действий. Это сценарий в сценариях, где я хотел бы подтвердить, что сообщение было отправлено с помощью Snackbar, прежде чем завершить операцию. Я рассмотрел возможность приостановки кода перед выходом из этой операции, но обнаружил, что это плохая практика.

Если то, что я описываю, невозможно, есть ли какой-либо тип сообщения для тостов с материалами? Или способ сделать прямоугольное тост-сообщение; один с закругленными краями меньшего радиуса?

Ответы

Ответ 1

Чтобы создать Snackbar с контекстом приложения, который отображается в нескольких действиях:

  • Получить WindowManager как системную службу
  • Создайте и добавьте FrameLayout (rootView) с типом WindowManager.LayoutParams.TYPE_TOAST и WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL в WindowManager
  • Подождите, пока в FrameLayout (rootView) не будет вызывается FrameLayout.onAttachedToWindow()
  • Получить маркер окна FrameLayout (rootView) с помощью View.getWindowToken()
  • Создайте ContextThemeWrapper с контекстом приложения и производным @style/Theme.AppCompat
  • Используйте новый контекст для создания дополнительного FrameLayout (snackbarContainer)
  • Добавьте FrameLayout (snackbarContainer) с типом WindowManager.LayoutParams.TYPE_APPLICATION_PANEL и флагом WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
  • Подождите, пока в FrameLayout (snackbarContainer) вызывается View.onAttachedToWindow()
  • Создайте Snackbar, как обычно, с помощью FrameLayout (snackbarContainer)
  • Установите View.onDismissed() обратный вызов в Snackbar и удалите FrameLayouts (rootView и snackbarContainer).
  • Показать закусочную 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>

Ответ 2

Если я правильно понимаю, вы делаете это:

  • Действие Запуск Activity B для отправки сообщения
  • После отправки сообщения вы увидите сообщение с подтверждением
  • Вы возвращаетесь к Activity A

Вы можете использовать SnackBar для этого, используя ActivityResult (здесь - это сообщение StackOverflow о том, как его использовать)

Вот шаги:

  • Действие Запуск Activity B с startActivityForResult
  • Сделайте свой материал в Activity B
  • Задайте свой результат (проверьте ссылку выше, чтобы понять)
  • Завершить работу
  • В действии A получите этот код в OnActivityResult и отобразите SnackBar с соответствующим сообщением

Это позволяет вам отображать Snackar в действии A, соответствующем результату Activity B.

Надеюсь, что это поможет вашей проблеме.

Ответ 3

ОБНОВЛЕНИЕ: см. выбранный ответ.

Лучшим решением для моего вопроса является использование 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);

UPDATE: я обнаружил, что, используя findViewById(android.R.id.content) в качестве представления в Snackbar.make(), Snackbar сохраняется среди изменений фрагмента.

Ответ 4

Чтобы иметь прямоугольник Toast, установите прямоугольный фон для Toast или просто установите другой цвет фона для Toast.

Обратитесь к этому сообщению, где оно было опубликовано как проблема. Но это ваш случай - это возможное решение.

Ответ 5

На всякий случай, кому-то нужно это сделать в 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);
    }
}