Snackbar in Support Library doesn't include OnDismissListener()?

邮差的信 提交于 2019-12-18 03:04:43

问题


I'd like to implement the new Snackbar included in the latest Design Support Library, but the way it is offered seems counter-intuitive for my, and I assume many others', use.

When the user does an important action, I want to allow them to undo it via the Snackbar, but there seems to be no way to detect when it is dismissed to do the action. It makes sense to me to do it the following way:

  1. User does action.
  2. Show Snackbar and update UI as if the action has been completed (ie it appears that data is sent to the database, but actually isn't yet).
  3. If user pressed "undo," revert the UI changes. If not, when the Snackbar is dismissed, it will then send the data.

But because I don't see any accessable OnDismissListener, I would therefore have to:

  1. User does action.
  2. Send info to database immediately and update UI.
  3. If user presses "undo," send another call to the database to remove the just-added data and revert the UI changes.

I would really like to avoid having to make the two calls to the database, and just send one when the app knows that it's safe (the user has avoided pressing "undo"). I notice there is some implementation of this in a third-party library via an EventListener, but I'd really like to stick to the Google library.


回答1:


Now it 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();



回答2:


Check out my helper class for snackbars.

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



回答3:


I have the same problem, but I'm providing an 'undo' for deleting data.
This is how I deal with it:

  • Pretend the data is deleted from db (hide from UI, which is a list of items)
  • Wait until the snack bar 'should' have dissapeared
  • Send the delete call to the database
  • IF, the user used the undo action, block the pending DB call

This may not work for you, since you are inserting data and you may (?) need it to be available in the database after the first action, but it will work if 'faking' the data (in the UI) is feasible. My code is not optimal, and I would call it a hack, but its the best I could find while staying within the official libraries.

    // 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.

My actual code is more complicated (dealing with the issue of canRemoveData not being accessible inside sub classes without being final, but this is basically how I managed to achieve what you are talking about.
Hope someone can figure out a better solution.




回答4:


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

I use custom coordinatorlayout. When Snackbar is showed then onMeasure and onMeasureChild of CoordinatorLayout are called. So I overrided these methods.

Please note that you must set ids of children of the custom coordinator layout. Because I find the view of SnackBar by id. The id of SnackBar is -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();

Implement OnSnackBarListener in your activity or fragment. When the snackbar is showed then it will call onShow. And the one is dismissed then it will call onDismiss.




回答5:


To improve Hitch.united answer

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



回答6:


This was just added in v23.

To be notified when a snackbar has been shown or dismissed, you can provide a Snackbar.Callback via setCallback(Callback).




回答7:


Francesco's answer (here) is right, but unfortunately it only works on API > 12. I submitted a feature request to the Android Issue Tracker. You can check it here and star it if you're interested. Thanks.



来源:https://stackoverflow.com/questions/30639470/snackbar-in-support-library-doesnt-include-ondismisslistener

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!