【问题标题】:How to disable snackbar's swipe-to-dismiss behavior如何禁用小吃店的滑动关闭行为
【发布时间】:2016-03-06 00:42:15
【问题描述】:

有没有办法防止用户通过在小吃栏上滑动而将其关闭?

我有一个在网络登录时显示小吃店的应用,我想避免它被关闭。

根据 Nikola Despotoski 的建议,我尝试了两种解决方案:

private void startSnack(){

    loadingSnack = Snackbar.make(findViewById(R.id.email_login_form), getString(R.string.logging_in), Snackbar.LENGTH_INDEFINITE)
            .setAction("CANCEL", new OnClickListener() {
                @Override
                public void onClick(View view) {
                    getOps().cancelLogin();
                    enableControls();
                }
            });

    loadingSnack.getView().setOnTouchListener(new View.OnTouchListener() {
        public long mInitialTime;
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            if (v instanceof Button) return false; //Action view was touched, proceed normally.
            else {
                switch (MotionEventCompat.getActionMasked(event)) {
                    case MotionEvent.ACTION_DOWN: {
                        Log.i(TAG, "ACTION_DOWN");
                        mInitialTime = System.currentTimeMillis();
                        break;
                    }
                    case MotionEvent.ACTION_UP: {
                        Log.i(TAG, "ACTION_UP");
                        long clickDuration = System.currentTimeMillis() - mInitialTime;
                        if (clickDuration <= ViewConfiguration.getTapTimeout()) {
                            return false;// click event, proceed normally
                        }
                    }
                    case MotionEvent.ACTION_MOVE: {
                        Log.i(TAG, "ACTION_MOVE");
                        return true;
                    }
                }
                return true;
            }
        }
    });

    ViewGroup.LayoutParams lp = loadingSnack.getView().getLayoutParams();
    if (lp != null && lp instanceof CoordinatorLayout.LayoutParams) {
        ((CoordinatorLayout.LayoutParams)lp).setBehavior(new DummyBehavior());
        loadingSnack.getView().setLayoutParams(lp);
        Log.i(TAG, "Dummy behavior assigned to " + lp.toString());

    }

    loadingSnack.show();

}

这是 DummyBehavior 类:

public class DummyBehavior extends CoordinatorLayout.Behavior<View>{

    /**
     * Debugging tag used by the Android logger.
     */
    protected final static String TAG =
            DummyBehavior.class.getSimpleName();



    public DummyBehavior() {
        Log.i(TAG, "Dummy behavior created");
        StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
        Log.i(TAG, "Method " + stackTrace[2].getMethodName() );

    }

    public DummyBehavior(Context context, AttributeSet attrs) {
        super(context, attrs);
        Log.i(TAG, "Dummy behavior created");

    }

    @Override
    public boolean onInterceptTouchEvent(CoordinatorLayout parent, View child, MotionEvent ev) {
        Log.i(TAG, "Method " + Thread.currentThread().getStackTrace()[2].getMethodName() );
        return false;
    }

    @Override
    public boolean onTouchEvent(CoordinatorLayout parent, View child, MotionEvent ev) {
        Log.i(TAG, "Method " + Thread.currentThread().getStackTrace()[2].getMethodName() );
        return false;
    }

    @Override
    public boolean blocksInteractionBelow(CoordinatorLayout parent, View child) {
        Log.i(TAG, "Method " + Thread.currentThread().getStackTrace()[2].getMethodName() );
        return false;
    }

    @Override
    public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
        Log.i(TAG, "Method " + Thread.currentThread().getStackTrace()[2].getMethodName() );
        return false;
    }

    @Override
    public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
        Log.i(TAG, "Method " + Thread.currentThread().getStackTrace()[2].getMethodName() );
        return false;
    }

    @Override
    public void onDependentViewRemoved(CoordinatorLayout parent, View child, View dependency) {
        Log.i(TAG, "Method " + Thread.currentThread().getStackTrace()[2].getMethodName() );
    }

    @Override
    public boolean isDirty(CoordinatorLayout parent, View child) {
        Log.i(TAG, "Method " + Thread.currentThread().getStackTrace()[2].getMethodName() );
        return false;
    }

    @Override
    public boolean onMeasureChild(CoordinatorLayout parent, View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) {
        Log.i(TAG, "Method " + Thread.currentThread().getStackTrace()[2].getMethodName() );
        return false;
    }

    @Override
    public boolean onLayoutChild(CoordinatorLayout parent, View child, int layoutDirection) {
        Log.i(TAG, "Method " + Thread.currentThread().getStackTrace()[2].getMethodName() );
        return false;
    }

    @Override
    public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) {
        Log.i(TAG, "Method " + Thread.currentThread().getStackTrace()[2].getMethodName() );
        return false;
    }

    @Override
    public void onNestedScrollAccepted(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) {
        Log.i(TAG, "Method " + Thread.currentThread().getStackTrace()[2].getMethodName() );
    }

    @Override
    public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target) {
        Log.i(TAG, "Method " + Thread.currentThread().getStackTrace()[2].getMethodName() );
    }

    @Override
    public void onNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
        Log.i(TAG, "Method " + Thread.currentThread().getStackTrace()[2].getMethodName() );
    }

    @Override
    public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dx, int dy, int[] consumed) {
        Log.i(TAG, "Method " + Thread.currentThread().getStackTrace()[2].getMethodName() );
    }

    @Override
    public boolean onNestedFling(CoordinatorLayout coordinatorLayout, View child, View target, float velocityX, float velocityY, boolean consumed) {
        Log.i(TAG, "Method " + Thread.currentThread().getStackTrace()[2].getMethodName() );
        return false;
    }

    @Override
    public boolean onNestedPreFling(CoordinatorLayout coordinatorLayout, View child, View target, float velocityX, float velocityY) {
        Log.i(TAG, "Method " + Thread.currentThread().getStackTrace()[2].getMethodName() );
        return false;
    }

    @Override
    public WindowInsetsCompat onApplyWindowInsets(CoordinatorLayout coordinatorLayout, View child, WindowInsetsCompat insets) {
        Log.i(TAG, "Method " + Thread.currentThread().getStackTrace()[2].getMethodName() );
        return null;
    }

    @Override
    public void onRestoreInstanceState(CoordinatorLayout parent, View child, Parcelable state) {
        Log.i(TAG, "Method " + Thread.currentThread().getStackTrace()[2].getMethodName() );
    }

    @Override
    public Parcelable onSaveInstanceState(CoordinatorLayout parent, View child) {
        Log.i(TAG, "Method " + Thread.currentThread().getStackTrace()[2].getMethodName() );
        return null;
    }
}

但是我的snackbar在滑动时仍然消失,这是一个典型的日志:

12-02 22:26:43.864 19598-19598/ I/DummyBehavior: Dummy behavior created
12-02 22:26:43.866 19598-19598/ I/DummyBehavior: Method <init>
12-02 22:26:43.866 19598-19598/ I/LifecycleLoggingActivity: Dummy behavior assigned to android.support.design.widget.CoordinatorLayout$LayoutParams@808c0e9
12-02 22:26:44.755 19598-19598/ I/LifecycleLoggingActivity: ACTION_DOWN
12-02 22:26:44.798 19598-19598/ I/LifecycleLoggingActivity: ACTION_MOVE
12-02 22:26:44.815 19598-19598/ I/LifecycleLoggingActivity: ACTION_MOVE
12-02 22:26:44.832 19598-19598/ I/LifecycleLoggingActivity: ACTION_MOVE
12-02 22:26:44.849 19598-19598/ I/LifecycleLoggingActivity: ACTION_MOVE
12-02 22:26:44.866 19598-19598/ I/LifecycleLoggingActivity: ACTION_MOVE
12-02 22:26:44.883 19598-19598/ I/LifecycleLoggingActivity: ACTION_MOVE

【问题讨论】:

    标签: android swipe dismiss android-snackbar snackbar


    【解决方案1】:

    我有一个带有透明元素的小吃店(作为带有自定义视图的 Toast 的替代品,现在已弃用)。有几个挑战需要克服:

    1. 小吃店可以刷掉。
    2. 触摸小吃店的任何部分(包括透明背景部分)都会刷新自动关闭计时器。
    3. 当小吃店拦截触摸事件时,无法与小吃店后面的 UI 元素(包括透明背景部分)进行交互。

    要完全禁用与小吃店的所有交互,我使用以下内容。

    // Counteract SnackbarBaseLayout's consumeAllTouchListener which prevents
    // touches from going through our snack bar, because (a) most of our snack 
    // bar is invisible and (b) we don't have any interaction elements
    snackbar.view.setOnTouchListener { _, _ -> false }
    snackbar.view.isFocusable = false
    
    // Disable swipe-to-dismiss on the snackbar. Also important as if a user 
    // clicks on a snackbar (including the transparent parts of it) then the 
    // auto-dismiss timer is deferred by default.
    snackbar.behavior = object : BaseTransientBottomBar.Behavior() {
        override fun canSwipeDismissView(child: View) = false
        override fun onInterceptTouchEvent(parent: CoordinatorLayout, child: View, event: MotionEvent) = false
    }
    

    【讨论】:

      【解决方案2】:

      只需覆盖 CoordinatorLayout.LayoutParams.getBehaviour()return null 即可禁用滑动。

      例如:

      Snackbar snackBar = Snackbar.make(view, "Enjoy!", Snackbar.LENGTH_INDEFINITE);
      CoordinatorLayout.LayoutParams snackBarLayoutParams = new CoordinatorLayout.LayoutParams(CoordinatorLayout.LayoutParams.MATCH_PARENT, CoordinatorLayout.LayoutParams.WRAP_CONTENT){
                      @Override
                      public CoordinatorLayout.Behavior getBehavior() {
                          return null;
                      }
                  };
      snackBarLayoutParams.gravity = Gravity.BOTTOM;
      snackBar.getView().setLayoutParams(snackBarLayoutParams);
      snackBar.show();
      

      享受吧!

      【讨论】:

        【解决方案3】:

        Snackbar 现在通过使用 setBehavior 方法对此提供了实际支持。这里最棒的是,以前你总是会丢失一些现在保留下来的行为。

        请注意,包已移动,因此您必须导入“新”Snackbar in the snackbar package

        Snackbar.make(view, stringId, Snackbar.LENGTH_LONG)
            .setBehavior(new NoSwipeBehavior())
            .show();
        
        class NoSwipeBehavior extends BaseTransientBottomBar.Behavior {
        
            @Override
            public boolean canSwipeDismissView(View child) {
              return false;
            }
        }
        

        【讨论】:

          【解决方案4】:

          这是一个不需要你搞砸ViewTreeObserver的解决方案。请注意,以下解决方案是基于 SDK 26 用 Kotlin 编写的。

          BaseTransientBottomBar

          final void showView() {
              if (mView.getParent() == null) {
                  final ViewGroup.LayoutParams lp = mView.getLayoutParams();
          
                  if (lp instanceof CoordinatorLayout.LayoutParams) {
                      // If our LayoutParams are from a CoordinatorLayout, we'll setup our Behavior
                      final CoordinatorLayout.LayoutParams clp = (CoordinatorLayout.LayoutParams) lp;
          
                      final Behavior behavior = new Behavior();
                      behavior.setStartAlphaSwipeDistance(0.1f);
                      behavior.setEndAlphaSwipeDistance(0.6f);
                      behavior.setSwipeDirection(SwipeDismissBehavior.SWIPE_DIRECTION_START_TO_END);
                      behavior.setListener(new SwipeDismissBehavior.OnDismissListener() {
                          @Override
                          public void onDismiss(View view) {
                              view.setVisibility(View.GONE);
                              dispatchDismiss(BaseCallback.DISMISS_EVENT_SWIPE);
                          }
          
                          @Override
                          public void onDragStateChanged(int state) {
                              switch (state) {
                                  case SwipeDismissBehavior.STATE_DRAGGING:
                                  case SwipeDismissBehavior.STATE_SETTLING:
                                      // If the view is being dragged or settling, pause the timeout
                                      SnackbarManager.getInstance().pauseTimeout(mManagerCallback);
                                      break;
                                  case SwipeDismissBehavior.STATE_IDLE:
                                      // If the view has been released and is idle, restore the timeout
                                      SnackbarManager.getInstance()
                                              .restoreTimeoutIfPaused(mManagerCallback);
                                      break;
                              }
                          }
                      });
                      clp.setBehavior(behavior);
                      // Also set the inset edge so that views can dodge the bar correctly
                      clp.insetEdge = Gravity.BOTTOM;
                  }
          
                  mTargetParent.addView(mView);
              }
          
              ...
          }
          

          如果您在方法showView 中查看BaseTransientBottomBar 的源代码,它会添加layoutParams 为CoordinatorLayout.LayoutParams 的行为。我们可以通过将行为设置回null 来撤消此操作。

          因为它在视图显示之前添加了行为,所以我们应该在视图显示后撤消它。

          val snackbar = Snackbar.make(coordinatorLayout, "Hello World!", Snackbar.LENGTH_INDEFINITE)
          snackbar.show()
          snackbar.addCallback(object : BaseTransientBottomBar.BaseCallback<Snackbar>() {
              override fun onShown(transientBottomBar: Snackbar) {
                  val layoutParams = transientBottomBar.view.layoutParams as? CoordinatorLayout.LayoutParams
                  layoutParams?.let { it.behavior = null }
              }
          })
          

          【讨论】:

            【解决方案5】:

            这对我有用:

            Snackbar snackbar = Snackbar.make(findViewById(container), R.string.offers_refreshed, Snackbar.LENGTH_LONG);
                final View snackbarView = snackbar.getView();
                snackbar.show();
            
                snackbarView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
                    @Override
                    public boolean onPreDraw() {
                        snackbarView.getViewTreeObserver().removeOnPreDrawListener(this);
                        ((CoordinatorLayout.LayoutParams) snackbarView.getLayoutParams()).setBehavior(null);
                        return true;
                    }
                });
            

            祝你好运! :)

            【讨论】:

              【解决方案6】:

              这对我有用:

                  Snackbar.SnackbarLayout layout = (Snackbar.SnackbarLayout) snackbar.getView();
                  snackbar.setDuration(Snackbar.LENGTH_INDEFINITE);
                  snackbar.show();
                  layout.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
                      @Override
                      public void onGlobalLayout() {
                          ViewGroup.LayoutParams lp = layout.getLayoutParams();
                          if (lp instanceof CoordinatorLayout.LayoutParams) {
                              ((CoordinatorLayout.LayoutParams) lp).setBehavior(new DisableSwipeBehavior());
                              layout.setLayoutParams(lp);
                          }
                          if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
                              layout.getViewTreeObserver().removeOnGlobalLayoutListener(this);
                          } else {
                              //noinspection deprecation
                              layout.getViewTreeObserver().removeGlobalOnLayoutListener(this);
                          }
                      }
                  });
              

              DisableSwipeBehavior 在哪里:

              public class DisableSwipeBehavior extends SwipeDismissBehavior<Snackbar.SnackbarLayout> {
                  @Override
                  public boolean canSwipeDismissView(@NonNull View view) {
                      return false;
                  }
              }
              

              【讨论】:

              • 这是一个可以接受的答案。即使我们不应该使用snackbar,如果我们想禁用滑动手势。
              【解决方案7】:

              这里有更好的解决方案.... 不要在小吃栏中提供CoordinatorLayout 或其任何子项作为视图。

              Snackbar.make(ROOT_LAYOUT , "No internet connection", Snackbar.LENGTH_INDEFINITE).show();
              

              其中,ROOT_LAYOUT 应该是除 coordinatorlayout 或其子级之外的任何布局。

              【讨论】:

              • 您的解决方案很好,但使用它需要您自担风险。来自Android docs“在你的视图层次结构中有一个 CoordinatorLayout 允许 Snackbar 启用某些功能,例如滑动关闭和自动移动像 FloatingActionButton 这样的小部件。”
              【解决方案8】:

              您可以禁用流式触摸事件,而不是点击Snackbar 视图。

              mSnackBar.getView().setOnTouchListener(new View.OnTouchListener() {
                          public long mInitialTime;
                          @Override
                          public boolean onTouch(View v, MotionEvent event) {
                              if (v instanceof Button) return false; //Action view was touched, proceed normally.
                              else {
                                  switch (MotionEventCompat.getActionMasked(event)) {
                                      case MotionEvent.ACTION_DOWN: {
                                          mInitialTime = System.currentTimeMillis();
                                          break;
                                      }
                                      case MotionEvent.ACTION_UP: {
                                          long clickDuration = System.currentTimeMillis() - mInitialTime;
                                          if (clickDuration <= ViewConfiguration.getTapTimeout()) {
                                              return false;// click event, proceed normally
                                          }
                                      }
                                  }
                                  return true;
                              }
                          });
              

              或者您可以将Snackbar 行为替换为一些空的CoordinatorLayout.Behavior

              public CouchPotatoBehavior extends CoordinatorLayout.Behavior<View>{
              
                  //override all methods and don't call super methods. 
              
              }
              

              这是空的行为,什么都不做。默认SwipeToDismissBehavior 使用ViewDragHelper 处理触摸事件,触发解除。

               ViewGroup.LayoutParams lp = mSnackbar.getView().getLayoutParams();
               if (lp instanceof CoordinatorLayout.LayoutParams) {
                   ((CoordinatorLayout.LayoutParams)lp).setBehavior(new CouchPotatoBehavior());
                     mSnackbar.getView().setLayoutParams(lp);              
              }
              

              【讨论】:

              • 或者你可以用一些空的 CoordinatorLayout.Behavior 替换 Snackbar 行为 我有点喜欢这个想法......你能扩展吗?喜欢使用ViewConfiguration.getTapTimeout()
              • @petey 好了。只要确保 getView() 不为空。 :)
              • 感谢您的建议,但我无法让它们正常工作。我修改了我的问题,添加了根据您提出的解决方案开发的代码。
              猜你喜欢
              • 1970-01-01
              • 2016-06-22
              • 2018-07-11
              • 2019-01-04
              • 2023-01-05
              • 1970-01-01
              • 2021-06-29
              • 2021-08-03
              • 2018-11-29
              相关资源
              最近更新 更多