有什么办法可以让 Snackbar 在活动变化中保持不变? [英] Is there any way make Snackbar persist among activity changes?
问题描述
虽然 Snackbar
很漂亮,但它在改变 Activity 时不会持久化.在我想在完成活动之前确认消息是使用 Snackbar
发送的情况下,这是一个无赖.我曾考虑在退出活动之前暂停代码,但发现这是一种不好的做法.
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 sent using a Snackbar
, before finishing the activity. I've considered pausing the code before exiting the activity, but have found that to be a bad practice.
如果我所描述的不可行,是否有任何类型的 Material Design Toast 消息?或者一种制作矩形吐司消息的方法;一个半径较小的圆形边缘?
If what I describe isn't possible, is there any type of material design toast message? Or a way to make a rectangular toast message; one with rounded edges of a smaller radius?
推荐答案
使用跨多个 Activity 可见的应用程序上下文创建 Snackbar:
To create a Snackbar with the application context which is visible across multiple activities:
- 获取
WindowManager
作为系统服务 - 创建并添加类型为
WindowManager.LayoutParams.TYPE_TOAST
和WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
FrameLayout (rootView) 的FrameLayout
(rootView)code> 到WindowManager
- 等待
FrameLayout.onAttachedToWindow()
在FrameLayout
(rootView) 中被调用 - 使用
View.getWindowToken()
获取 - 使用应用程序上下文和派生的
@style/Theme.AppCompat
创建一个 - 使用新上下文创建额外的
FrameLayout
(snackbarContainer) - 使用
WindowManager.LayoutParams.TYPE_APPLICATION_PANEL
类型和标记WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
添加此FrameLayout
(snackbarContainer)> - 等待
View.onAttachedToWindow()
在FrameLayout
(snackbarContainer) 中被调用 - 使用
FrameLayout
(snackbarContainer) 像平常一样创建 Snackbar - 将
View.onDismissed()
回调设置为 Snackbar 并移除 FrameLayouts(rootView 和 snackbarContainer) - 显示小吃店
Snackbar.show()
FrameLayout
(rootView) 的窗口令牌ContextThemeWrapper
- Get the
WindowManager
as system service - Create and add a
FrameLayout
(rootView) with typeWindowManager.LayoutParams.TYPE_TOAST
andWindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
to theWindowManager
- Wait until on
FrameLayout.onAttachedToWindow()
is called in theFrameLayout
(rootView) - Get the window token of the
FrameLayout
(rootView) withView.getWindowToken()
- Create a
ContextThemeWrapper
with the application context and a derived@style/Theme.AppCompat
- Use the new context to create an additional
FrameLayout
(snackbarContainer) - Add this
FrameLayout
(snackbarContainer) with typeWindowManager.LayoutParams.TYPE_APPLICATION_PANEL
and flagWindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
- Wait until on
View.onAttachedToWindow()
is called in theFrameLayout
(snackbarContainer) - Create the Snackbar like normal with the
FrameLayout
(snackbarContainer) - Set
View.onDismissed()
callback to the Snackbar and remove the FrameLayouts (rootView and snackbarContainer) - Show the snackbar
Snackbar.show()
这是一个有效的包装器(注意:滑动以关闭是行不通的.也许其他人会找到正确的 由 CoordinatorLayout 修复):WindowManager.LayoutParams
标志来接收触摸事件
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;
}
}
}
编辑
一旦 SnackbarWrapper
被定义,你可以像这样使用它:
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();
如果你没有主题,你可以在styles.xml
中快速定义一个:
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>
编辑
对于 Android Oreo 上出现 Bad Token Exception 的用户,请将 TYPE_TOAST 更改为 TYPE_APPLICATION_OVERLAY.这是由于 Android Oreo 实施了特殊权限来绘制应用程序.您可以使用以下方式请求此权限:
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);
}
这篇关于有什么办法可以让 Snackbar 在活动变化中保持不变?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!