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 sen
To have a rectangular Toast
, set a rectangular background for the Toast or just set a different background color for the Toast.
Refer this post where it was posted as a problem. But it your case it is a possible solution.
To create a Snackbar with the application context which is visible across multiple activities:
WindowManager
as system serviceFrameLayout
(rootView) with type WindowManager.LayoutParams.TYPE_TOAST
and WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
to the WindowManager
FrameLayout.onAttachedToWindow()
is called in the FrameLayout
(rootView)FrameLayout
(rootView) with View.getWindowToken()
ContextThemeWrapper
with the application context and a derived @style/Theme.AppCompat
FrameLayout
(snackbarContainer)FrameLayout
(snackbarContainer) with type WindowManager.LayoutParams.TYPE_APPLICATION_PANEL
and flag WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
View.onAttachedToWindow()
is called in the FrameLayout
(snackbarContainer)FrameLayout
(snackbarContainer)View.onDismissed()
callback to the Snackbar and remove the FrameLayouts (rootView and snackbarContainer)Snackbar.show()
Here a working wrapper (NOTE: Swipe to dismiss is not working. Maybe some one else find the correct Fixed by CoordinatorLayout):WindowManager.LayoutParams
flags to receive touch events
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;
}
}
}
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();
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>
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);
}
Just in case somebody needs to do this in Xamarin I have adapted the accepted answer which I found really helpful.
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);
}
}
UPDATE: See selected answer.
The best solution to my question is using a Timer
after the presenting the Snackbar
and then in the run()
method of the timer, starting the activity.
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: I found that by using findViewById(android.R.id.content)
as the view in Snackbar.make()
the Snackbar persists among fragment changes.
If I understand correctly, you do this:
You can use SnackBar to do that by using an ActivityResult (here is a StackOverflow post with how to use it)
Here are the steps:
This allow you do display a Snackar in Activity A corresponding to result of Activity B.
Hopes it can helps your problem
Actually I just need to display a message and I don't need a onClickListener on the message. If you just need to show a message look at "Myke Dev" answer in this thread which is the one I needed:
https://stackoverflow.com/a/34640942/9993413
(Don't give upvote to me, give upvote to "Myke Dev" which wrote the answer)
In user1185087 answer you must request permissions from user by opening settings and this, for me, isn't a good thing for user interactions, but I thinks is the only way if you want to show a snackbar with an onClickListener.
(Maybe you can use an activity with no background which launch a snackbar-like dialog, but it won't act just as a window_alert snackbar)