支持库中的 Snackbar 不包含 OnDismissListener()?
Posted
技术标签:
【中文标题】支持库中的 Snackbar 不包含 OnDismissListener()?【英文标题】:Snackbar in Support Library doesn't include OnDismissListener()? 【发布时间】:2015-08-18 19:21:45 【问题描述】:我想实现包含在最新设计支持库中的新 Snackbar,但它的提供方式对我来说似乎是违反直觉的,而且我认为还有很多其他人会使用它。
当用户执行一项重要操作时,我想允许他们通过 Snackbar 撤消它,但似乎无法检测何时解除执行该操作。对我来说,这样做是有意义的:
-
用户执行操作。
显示 Snackbar 并更新 UI,就像操作已完成一样(即,数据似乎已发送到数据库,但实际上尚未发送)。
如果用户按下“撤消”,则恢复 UI 更改。如果没有,当 Snackbar 被关闭时,它将发送数据。
但由于我没有看到任何可访问的 OnDismissListener,因此我必须:
-
用户执行操作。
立即将信息发送到数据库并更新 UI。
如果用户按下“撤消”,则向数据库发送另一个调用以删除刚刚添加的数据并恢复 UI 更改。
我真的很想避免对数据库进行两次调用,并在应用程序知道它是安全的时发送一个(用户避免按“撤消”)。我注意到在第三方库中通过 EventListener 对此进行了一些实现,但我真的很想坚持使用 Google 库。
【问题讨论】:
它非常非常基础。它甚至没有“isShowing()”。 【参考方案1】:这是刚刚在 v23 中添加的。
要在显示或关闭小吃店时收到通知,您可以通过setCallback(Callback)
提供 Snackbar.Callback。
【讨论】:
【参考方案2】:现在是does
Snackbar.make(getView(), "Hi there!", Snackbar.LENGTH_LONG).setCallback( new Snackbar.Callback()
@Override
public void onDismissed(Snackbar snackbar, int event)
switch(event)
case Snackbar.Callback.DISMISS_EVENT_ACTION:
Toast.makeText(getActivity(), "Clicked the action", Toast.LENGTH_LONG).show();
break;
case Snackbar.Callback.DISMISS_EVENT_TIMEOUT:
Toast.makeText(getActivity(), "Time out", Toast.LENGTH_LONG).show();
break;
@Override
public void onShown(Snackbar snackbar)
Toast.makeText(getActivity(), "This is my annoying step-brother", Toast.LENGTH_LONG).show();
).setAction("Go away!", new View.OnClickListener()
@Override
public void onClick(View v)
).show();
【讨论】:
【参考方案3】:Francesco 的回答 (here) 是正确的,但不幸的是它只适用于 API > 12。我向 android 问题跟踪器提交了一个功能请求。如果你有兴趣,可以查看here 并加注星标。谢谢。
【讨论】:
【参考方案4】:改进 Hitch.united 答案
boolean mAllowedToRemove = true;
Snackbar snack = Snackbar.make(mView, mSnackTitle, Snackbar.LENGTH_LONG);
snack.setAction(getString(R.string.snackbar_undo), new OnClickListener()
@Override
public void onClick(View v)
mAllowedToRemove = false;
// undo
...
);
snack.getView().addOnAttachStateChangeListener(new View.OnAttachStateChangeListener()
@Override
public void onViewAttachedToWindow(View v)
@Override
public void onViewDetachedFromWindow(View v)
if(!mAllowedToRemove)
// handle actions like http requests
...
);
snack.show();
【讨论】:
【参考方案5】:查看我的帮助类以了解小吃店。
public class SnackbarUtils
private static final String LOG_TAG = SnackbarUtils.class.getSimpleName();
private SnackbarUtils()
public interface SnackbarDismissListener
void onSnackbarDismissed();
public static void showSnackbar(View rootView, String message, String actionMessage, View.OnClickListener callbacks, final SnackbarDismissListener dismissListener)
Snackbar snackbar = Snackbar.make(rootView,message,Snackbar.LENGTH_LONG);
snackbar.setAction(actionMessage,callbacks);
if (dismissListener != null)
snackbar.getView().addOnAttachStateChangeListener(new View.OnAttachStateChangeListener()
@Override
public void onViewAttachedToWindow(View v)
@Override
public void onViewDetachedFromWindow(View v)
dismissListener.onSnackbarDismissed();
);
snackbar.show();
【讨论】:
好主意。但是有没有办法在 Pre API 12 设备上执行此操作?【参考方案6】:public class CustomCoordinatorLayout extends CoordinatorLayout
private boolean mIsSnackBar = false;
private View mSnakBarView = null;
private OnSnackBarListener mOnSnackBarListener = null;
public CustomCoordinatorLayout(Context context)
super(context);
public CustomCoordinatorLayout(Context context, AttributeSet attrs)
super(context, attrs);
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if(mIsSnackBar)
// Check whether the snackbar is existed.
// If it is not existed then index of the snackbar is -1.
if(indexOfChild(mSnakBarView) == -1)
mSnakBarView = null;
mIsSnackBar = false;
if(mOnSnackBarListener != null)
mOnSnackBarListener.onDismiss();
Log.d("NEOSARCHIZO","SnackBar is dismissed!");
@Override
public void onMeasureChild(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed)
super.onMeasureChild(child, parentWidthMeasureSpec, widthUsed, parentHeightMeasureSpec, heightUsed);
// onMeaureChild is called before onMeasure.
// The view of SnackBar doesn't have an id.
if(child.getId() == -1)
mIsSnackBar = true;
// Store the view of SnackBar.
mSnakBarView = child;
if(mOnSnackBarListener != null)
mOnSnackBarListener.onShow();
Log.d("NEOSARCHIZO","SnackBar is showed!");
public void setOnSnackBarListener(OnSnackBarListener onSnackBarListener)
mOnSnackBarListener = onSnackBarListener;
public interface OnSnackBarListener
public void onShow();
public void onDismiss();
我使用自定义协调器布局。当显示 Snackbar 时,将调用 CoordinatorLayout 的 onMeasure 和 onMeasureChild。所以我重写了这些方法。
请注意,您必须设置自定义协调器布局的子级 ID。因为我通过id找到了SnackBar的视图。 SnackBar的id是-1。
CustomCoordinatorLayout layout = (CustomCoordinatorLayout)findViewById(R.id.main_content);
layout.setOnSnackBarListener(this);
Snackbar.make(layout, "Hello!", Snackbar.LENGTH_LONG).setAction("UNDO", new View.OnClickListener()
@Override
public void onClick(View v)
//TODO something
).show();
在您的活动或片段中实现 OnSnackBarListener。当显示快餐栏时,它将调用 onShow。然后一个被解雇然后它会调用 onDismiss。
【讨论】:
【参考方案7】:我有同样的问题,但我提供了一个“撤消”来删除数据。 我是这样处理的:
假装数据已从数据库中删除(从 UI 中隐藏,即项目列表) 等到小吃店“应该”消失 向数据库发送删除调用 如果用户使用了撤消操作,则阻止待处理的 DB 调用这可能对您不起作用,因为您正在插入数据并且您可能(?)需要它在第一次操作后在数据库中可用,但如果“伪造”数据(在 UI 中)是可行的。我的代码不是最佳的,我会称之为 hack,但它是我在官方库中能找到的最好的代码。
// Control objects
boolean canRemoveData = true;
Object removedData = getData(id);
UI.remove(id);
// Snackbar code
Snackbar snackbar = Snackbar.make(view, "Data removed", Snackbar.LENGTH_LONG);
snackbar.setAction("Undo", new View.OnClickListener()
@Override
public void onClick(View v)
canRemoveData = false;
DB.remove(id);
);
// Handler to time the dismissal of the snackbar
new Handler(getActivity().getMainLooper()).postDelayed(new Runnable()
@Override
public void run()
if(canRemoveData)
DB.remove(id);
, (int)(snackbar.getDuration() * 1.05f));
// Here I am using a slightly longer delay before sending the db delete call,
// just because I don't trust the accuracy of the Handler timing and I want
// to be on the safe side. Either way this is dirty code, but the best I could do.
我的实际代码更复杂(处理 canRemoveData 在子类中无法访问而不是最终的问题,但这基本上是我设法实现您所说的内容的方法。 希望有人能找到更好的解决方案。
【讨论】:
同意这很 hacky,但在 Google 提供官方方法来处理它之前,我会继续这样做。它甚至不那么阴暗,因为它当前的实现方式,snackbar.getDuration() 返回 0(Snackbar.LENGTH_LONG 由于某种原因也等于 0),所以你的snackbar.getDuration() * 1.05f 返回 0。我尽可能地计时到大约 3500 毫秒,所以我现在就这样做。 哼,这很奇怪。它对我有用......但developer.android.com/reference/android/support/design/widget/… 确实将其指定为 0(并且 LENGTH_SHORT 为 -1)。我不明白为什么(连同 TOAST.LENGTH_SHORT/LONG)不直接使用毫秒值,这给了我们更多的控制权。毫无疑问,Google 会在不久的将来改进所有这些新的 API。目前它们看起来有点生硬。 哈哈,是的,不知道为什么这对你有用,因为 Snackbar 的整个区域有点乱:***.com/questions/30550792/…。 另外,关于我的解决方案的注释,如果用户在 Handler 触发 DB 事件之前导航到新活动,可能会出现问题。虽然没有调试过这个理论 :) Cnackbar 是 Toast 的简单替代品,仅此而已!经典的 Google“不关心开发者”政策以上是关于支持库中的 Snackbar 不包含 OnDismissListener()?的主要内容,如果未能解决你的问题,请参考以下文章