Android: Multiple snackbars in separate Fragments

2019-02-21 16:04发布

问题:

I have a viewpager, with a few fragments of course. Each of these fragments have CoordinatorLayout as parent.

I'm showing a snackbar for something. The problem is, if Fragment A shows a snackbar it's fine, but if it's adjacent fragment B also shows a snackbar the snackbar in fragment A automatically hides.

Since viewpager adjacent fragments are preloaded, it's a visible issue. Any workaround? Or am I doing it wrong?

回答1:

There is a few problems with snackbar in multiple Fragments in ViewPager

1) If snackbar is shown on Fragment A (visible) and Fragment B (not visible), both snackbar is not visible.

2) If I use a combination of @Override setUserVisibleHint and getUserVisibleHint() to smartly show and hide snackbar depending on fragment visibility, it only work the first time. After that, calling snackbar.show() fail to show the snackbar anymore (unless I recreate the snackbar).

The following is my proposed solution (tested with v23.1.1):

public class SnackbarManager {
    private static final String TAG = SnackbarManager.class.getName();

    private Snackbar snackbar;
    private Create instance;
    // private boolean isMultiSnackbar;

    public interface Create {
        Snackbar create();
    }

    public SnackbarManager(Create instance) {
        // why not pass in snackbar? coz snackbar.show will fail after 1st show (it multiple snackbar), thus need to recreate it
        snackbar = instance.create();
        this.instance = instance;
    }

    public void show(Fragment fragment) {
        if (fragment.getUserVisibleHint()) {
            snackbar.show();
        }
    }

    public void onSetUserVisibleHint(boolean isVisible) {
        if (isVisible) {
            if (snackbar == null) {
                snackbar = instance.create();
            }
            snackbar.show();
            Log.d(TAG, "showSnackbar="+snackbar.isShown());
            // if snackbar.isShown()=false, if means multiple snackbar exist (might or might not be in same fragment)
            /*
            boolean isMultiSnackbar = !snackbar.isShown();
            // the following is inaccurate when I manually dismiss one of the snackbar
            // even when isShown()=true, the snackbar is not shown
            if (isMultiSnackbar) {
                snackbar = null;
                snackbar = instance.create();
                snackbar.show();
            }
             */
            }
            else {
                Log.d(TAG, "dismissSnackbar");
                snackbar.dismiss();
                // subsequent show will fail, make sure to recreate next
                snackbar = null;
            }
        }
    }
}

public class TestFragment extends Fragment {
    private SnackbarManager snackbarManager;

    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        if (snackbarManager != null)
            snackbarManager.onSetUserVisibleHint(isVisibleToUser);
    }

    public void showSnackbar() {
        snackbarManager = new SnackbarManager(new SnackbarManager.Create() {
            @Override
            public Snackbar create() {
                Snackbar snackbar = Snackbar.make(snackbarLayout, "Create your first moment ;)", Snackbar.LENGTH_INDEFINITE);
                snackbar.setAction("Create", new View.OnClickListener() {
                    @Override
                    public void onClick(View view) {
                        snackbarManager = null;
                    }
                });
                // enable following if not Snackbar.LENGTH_INDEFINITE
                /*
                snackbar.setCallback(new Snackbar.Callback() {
                    @Override
                    public void onDismissed(Snackbar snackbar, int event) {
                        super.onDismissed(snackbar, event);
                        snackbarManager = null;
                    }
                })
                */
                return snackbar;
            }
        });
        snackbarManager.show(this);
    }
}


回答2:

You can use setUserVisibleHint(boolean isVisible) to update fragment visibility (for instance on your ViewPager onPageSelected) and then getUserVisibleHint() on your fragment to show the Snackbar only if the fragment is visible.

Lets say you have Fragments A, B & C

Fragment A is visible and so is the Snackbar If Fragment B or C try to open a Snackbar, getUserVisibleHint() will return false and they wont mess up with Fragment A Snackbar.

Obs. Be careful when doing this with FragmentStatePagerAdapter because you can get NPE keeping references to Fragments and calling setUserVisibleHint() on this references.

Edit: You don't need setUserVisibleHint() because its already called by the system.