How to disable snackbar's swipe-to-dismiss behavior

后端 未结 6 1648
执念已碎
执念已碎 2020-12-05 18:52

Is there a way to prevent the user from dismissing a snackbar by swiping on it?

I have an app that shows a snack bar during network login, I want to avoid it to be d

相关标签:
6条回答
  • 2020-12-05 19:15

    You can disable streaming touch events rather than clicks to the Snackbar view.

    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;
                    }
                });
    

    Or you could just replace the Snackbar behavior with some empty CoordinatorLayout.Behavior:

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

    This is the empty behavior, that does nothing. Default SwipeToDismissBehavior uses ViewDragHelper to process touch events, upon which triggers the dismissal.

     ViewGroup.LayoutParams lp = mSnackbar.getView().getLayoutParams();
     if (lp instanceof CoordinatorLayout.LayoutParams) {
         ((CoordinatorLayout.LayoutParams)lp).setBehavior(new CouchPotatoBehavior());
           mSnackbar.getView().setLayoutParams(lp);              
    }
    
    0 讨论(0)
  • 2020-12-05 19:16

    Here is a solution that does not require you to mess with ViewTreeObserver. Note that the following solution is written in Kotlin based on SDK 26.

    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);
        }
    
        ...
    }
    

    If you look in to the source code of BaseTransientBottomBar in method showView, it add the behavior if the layoutParams is CoordinatorLayout.LayoutParams. We can undo this by setting the behavior back to null.

    As it add the behavior just before the view is shown, we should undo it after the view is shown.

    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 }
        }
    })
    
    0 讨论(0)
  • 2020-12-05 19:28

    This worked for me:

        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);
                }
            }
        });
    

    Where DisableSwipeBehavior is:

    public class DisableSwipeBehavior extends SwipeDismissBehavior<Snackbar.SnackbarLayout> {
        @Override
        public boolean canSwipeDismissView(@NonNull View view) {
            return false;
        }
    }
    
    0 讨论(0)
  • 2020-12-05 19:29

    Snackbar now has actual support for this by using the setBehavior method. The great thing here is that before you would always lose some behaviors which are now preserved.

    Note that the package moved so you have to import the "new" 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;
        }
    }
    
    0 讨论(0)
  • 2020-12-05 19:39

    Better solution here.... Don't provide CoordinatorLayout or any of its child as view in snackbar.

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

    Where, the ROOT_LAYOUT should be any layout except coordinatorlayout or its child.

    0 讨论(0)
  • 2020-12-05 19:42

    This worked for me :

    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;
            }
        });
    

    Good luck! :)

    0 讨论(0)
提交回复
热议问题