diff --git a/api/current.txt b/api/current.txt index f533f75cd..94e0f9c02 100644 --- a/api/current.txt +++ b/api/current.txt @@ -182,8 +182,8 @@ package android.support.design.widget { method public void dismiss(); method public int getDuration(); method public android.view.View getView(); - method public static android.support.design.widget.Snackbar make(android.view.ViewGroup, java.lang.CharSequence, int); - method public static android.support.design.widget.Snackbar make(android.view.ViewGroup, int, int); + method public static android.support.design.widget.Snackbar make(android.view.View, java.lang.CharSequence, int); + method public static android.support.design.widget.Snackbar make(android.view.View, int, int); method public android.support.design.widget.Snackbar setAction(int, android.view.View.OnClickListener); method public android.support.design.widget.Snackbar setAction(java.lang.CharSequence, android.view.View.OnClickListener); method public android.support.design.widget.Snackbar setDuration(int); diff --git a/src/android/support/design/widget/Snackbar.java b/src/android/support/design/widget/Snackbar.java index 5d9dc1b1c..ac284c404 100644 --- a/src/android/support/design/widget/Snackbar.java +++ b/src/android/support/design/widget/Snackbar.java @@ -23,6 +23,7 @@ import android.os.Handler; import android.os.Looper; import android.os.Message; import android.support.annotation.IntDef; +import android.support.annotation.Nullable; import android.support.annotation.StringRes; import android.support.design.R; import android.support.v4.view.ViewCompat; @@ -33,8 +34,10 @@ import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; +import android.view.ViewParent; import android.view.animation.Animation; import android.view.animation.AnimationUtils; +import android.widget.FrameLayout; import android.widget.LinearLayout; import android.widget.TextView; @@ -116,15 +119,24 @@ public class Snackbar { } /** - * Make a Snackbar to display a message. + * Make a Snackbar to display a message * - * @param parent The parent to add Snackbars to. + *

Snackbar will try and find a parent view to hold Snackbar's view from the value given + * to {@code view}. Snackbar will walk up the view tree trying to find a suitable parent, + * which is defined as a {@link CoordinatorLayout} or the window decor's content view, + * whichever comes first. + * + *

Having a {@link CoordinatorLayout} in your view hierarchy allows Snackbar to enable + * certain features, such as swipe-to-dismiss and automatically moving of widgets like + * {@link FloatingActionButton}. + * + * @param view The view to find a parent from. * @param text The text to show. Can be formatted text. * @param duration How long to display the message. Either {@link #LENGTH_SHORT} or {@link * #LENGTH_LONG} */ - public static Snackbar make(ViewGroup parent, CharSequence text, @Duration int duration) { - Snackbar snackbar = new Snackbar(parent); + public static Snackbar make(View view, CharSequence text, @Duration int duration) { + Snackbar snackbar = new Snackbar(findSuitableParent(view)); snackbar.setText(text); snackbar.setDuration(duration); return snackbar; @@ -133,13 +145,51 @@ public class Snackbar { /** * Make a Snackbar to display a message. * - * @param parent The parent to add Snackbars to. - * @param resId The resource id of the string resource to use. Can be formatted text. + *

Snackbar will try and find a parent view to hold Snackbar's view from the value given + * to {@code view}. Snackbar will walk up the view tree trying to find a suitable parent, + * which is defined as a {@link CoordinatorLayout} or the window decor's content view, + * whichever comes first. + * + *

Having a {@link CoordinatorLayout} in your view hierarchy allows Snackbar to enable + * certain features, such as swipe-to-dismiss and automatically moving of widgets like + * {@link FloatingActionButton}. + * + * @param view The view to find a parent from. + * @param resId The resource id of the string resource to use. Can be formatted text. * @param duration How long to display the message. Either {@link #LENGTH_SHORT} or {@link * #LENGTH_LONG} */ - public static Snackbar make(ViewGroup parent, int resId, @Duration int duration) { - return make(parent, parent.getResources().getText(resId), duration); + public static Snackbar make(View view, int resId, @Duration int duration) { + return make(view, view.getResources().getText(resId), duration); + } + + @Nullable + private static ViewGroup findSuitableParent(View view) { + ViewGroup fallback = null; + do { + if (view instanceof CoordinatorLayout) { + // We've found a CoordinatorLayout, use it + return (ViewGroup) view; + } else if (view instanceof FrameLayout) { + if (view.getId() == android.R.id.content) { + // If we've hit the decor content view, then we didn't find a CoL in the + // hierarchy, so use it. + return (ViewGroup) view; + } else { + // It's not the content view but we'll use it as our fallback + fallback = (ViewGroup) view; + } + } + + if (view != null) { + // Else, we will loop and crawl up the view hierarchy and try to find a parent + final ViewParent parent = view.getParent(); + view = parent instanceof View ? (View) parent : null; + } + } while (view != null); + + // If we reach here then we didn't find a CoL or a suitable content view so we'll fallback + return fallback; } /** diff --git a/src/android/support/design/widget/SnackbarManager.java b/src/android/support/design/widget/SnackbarManager.java index 3b09f179e..44974ec69 100644 --- a/src/android/support/design/widget/SnackbarManager.java +++ b/src/android/support/design/widget/SnackbarManager.java @@ -20,6 +20,8 @@ import android.os.Handler; import android.os.Looper; import android.os.Message; +import java.lang.ref.WeakReference; + /** * Manages {@link Snackbar}s. */ @@ -84,11 +86,12 @@ class SnackbarManager { mNextSnackbar = new SnackbarRecord(duration, callback); } - if (mCurrentSnackbar != null) { - // If the new Snackbar isn't in position 0, we'll cancel the current one and wait - // in line - cancelSnackbarLocked(mCurrentSnackbar); + if (mCurrentSnackbar != null && cancelSnackbarLocked(mCurrentSnackbar)) { + // If we currently have a Snackbar, try and cancel it and wait in line + return; } else { + // Clear out the current snackbar + mCurrentSnackbar = null; // Otherwise, just show it now showNextSnackbarLocked(); } @@ -151,33 +154,49 @@ class SnackbarManager { } private static class SnackbarRecord { - private Callback callback; + private final WeakReference callback; private int duration; SnackbarRecord(int duration, Callback callback) { - this.callback = callback; + this.callback = new WeakReference<>(callback); this.duration = duration; } + + boolean isSnackbar(Callback callback) { + return callback != null && this.callback.get() == callback; + } } private void showNextSnackbarLocked() { if (mNextSnackbar != null) { mCurrentSnackbar = mNextSnackbar; - mCurrentSnackbar.callback.show(); mNextSnackbar = null; + + final Callback callback = mCurrentSnackbar.callback.get(); + if (callback != null) { + callback.show(); + } else { + // The callback doesn't exist any more, clear out the Snackbar + mCurrentSnackbar = null; + } } } - private void cancelSnackbarLocked(SnackbarRecord record) { - record.callback.dismiss(); + private boolean cancelSnackbarLocked(SnackbarRecord record) { + final Callback callback = record.callback.get(); + if (callback != null) { + callback.dismiss(); + return true; + } + return false; } private boolean isCurrentSnackbar(Callback callback) { - return mCurrentSnackbar != null && mCurrentSnackbar.callback == callback; + return mCurrentSnackbar != null && mCurrentSnackbar.isSnackbar(callback); } private boolean isNextSnackbar(Callback callback) { - return mNextSnackbar != null && mNextSnackbar.callback == callback; + return mNextSnackbar != null && mNextSnackbar.isSnackbar(callback); } private void scheduleTimeoutLocked(SnackbarRecord r) {