【问题标题】:Snackbar in Support Library doesn't include OnDismissListener()?支持库中的 Snackbar 不包含 OnDismissListener()?
【发布时间】:2015-08-18 19:21:45
【问题描述】:

我想实现包含在最新设计支持库中的新 Snackbar,但它的提供方式对我来说似乎是违反直觉的,而且我认为还有很多其他人会使用它。

当用户执行一项重要操作时,我想允许他们通过 Snackbar 撤消它,但似乎无法检测何时解除执行该操作。对我来说,这样做是有意义的:

  1. 用户执行操作。
  2. 显示 Snackbar 并更新 UI,就像操作已完成一样(即,数据似乎已发送到数据库,但实际上尚未发送)。
  3. 如果用户按下“撤消”,则恢复 UI 更改。如果没有,当 Snackbar 被关闭时,它将发送数据。

但由于我没有看到任何可访问的 OnDismissListener,因此我必须:

  1. 用户执行操作。
  2. 立即将信息发送到数据库并更新 UI。
  3. 如果用户按下“撤消”,则向数据库发送另一个调用以删除刚刚添加的数据并恢复 UI 更改。

我真的很想避免对数据库进行两次调用,并在应用程序知道它是安全的时发送一个(用户避免按“撤消”)。我注意到在第三方库中通过 EventListener 对此进行了一些实现,但我真的很想坚持使用 Google 库。

【问题讨论】:

  • 它非常非常基础。它甚至没有“isShowing()”。

标签: android android-support-library android-design-library android-snackbar


【解决方案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 的整个区域有点乱:stackoverflow.com/questions/30550792/…
            • 另外,关于我的解决方案的注释,如果用户在 Handler 触发 DB 事件之前导航到新活动,可能会出现问题。虽然没有调试过这个理论 :)
            • Cnackbar 是 Toast 的简单替代品,仅此而已!经典的 Google“不关心开发者”政策
            猜你喜欢
            • 2016-05-09
            • 2015-09-14
            • 2015-10-19
            • 2016-12-30
            • 1970-01-01
            • 1970-01-01
            • 2020-12-14
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多