From d5856fd0365b4daeb27f07c8864d88618884dfb8 Mon Sep 17 00:00:00 2001 From: conradchen Date: Thu, 10 Feb 2022 19:04:15 +0000 Subject: [PATCH] [Snackbar] Fix the issue that setting margins programmatically does not work We always use the original margins saved when the snackbar is created to update snackbar's actual margins. Therefore the newly set margins will always be overridden. Fixes this by updating original margins when layout params are set. Resolves https://github.com/material-components/material-components-android/issues/1076 PiperOrigin-RevId: 427795853 --- .../snackbar/BaseTransientBottomBar.java | 168 ++++++++---------- 1 file changed, 76 insertions(+), 92 deletions(-) diff --git a/lib/java/com/google/android/material/snackbar/BaseTransientBottomBar.java b/lib/java/com/google/android/material/snackbar/BaseTransientBottomBar.java index 6b44869448f..e64fbe8ce8a 100644 --- a/lib/java/com/google/android/material/snackbar/BaseTransientBottomBar.java +++ b/lib/java/com/google/android/material/snackbar/BaseTransientBottomBar.java @@ -300,35 +300,20 @@ public void run() { } }; - @Nullable private Rect originalMargins; private int extraBottomMarginWindowInset; private int extraLeftMarginWindowInset; private int extraRightMarginWindowInset; private int extraBottomMarginGestureInset; private int extraBottomMarginAnchorView; + private boolean pendingShowingView; + private List> callbacks; private BaseTransientBottomBar.Behavior behavior; @Nullable private final AccessibilityManager accessibilityManager; - /** @hide */ - // TODO(b/76413401): make package private after the widget migration is finished - @RestrictTo(LIBRARY_GROUP) - protected interface OnLayoutChangeListener { - void onLayoutChange(View view, int left, int top, int right, int bottom); - } - - /** @hide */ - // TODO(b/76413401): make package private after the widget migration is finished - @RestrictTo(LIBRARY_GROUP) - protected interface OnAttachStateChangeListener { - void onViewAttachedToWindow(View v); - - void onViewDetachedFromWindow(View v); - } - /** * Constructor for the transient bottom bar. * @@ -371,6 +356,7 @@ protected BaseTransientBottomBar( // in the extending Snackbar class. This is to prevent breakage of apps that have custom // coordinator layout behaviors that depend on that layout. view = (SnackbarBaseLayout) inflater.inflate(getSnackbarBaseLayoutResId(), targetParent, false); + view.setBaseTransientBottomBar(this); if (content instanceof SnackbarContentLayout) { ((SnackbarContentLayout) content) .updateActionTextColorAlphaIfNeeded(view.getActionTextColorAlpha()); @@ -378,17 +364,6 @@ protected BaseTransientBottomBar( } view.addView(content); - LayoutParams layoutParams = view.getLayoutParams(); - if (layoutParams instanceof MarginLayoutParams) { - MarginLayoutParams marginParams = (MarginLayoutParams) layoutParams; - originalMargins = - new Rect( - marginParams.leftMargin, - marginParams.topMargin, - marginParams.rightMargin, - marginParams.bottomMargin); - } - ViewCompat.setAccessibilityLiveRegion(view, ViewCompat.ACCESSIBILITY_LIVE_REGION_POLITE); ViewCompat.setImportantForAccessibility(view, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES); @@ -438,17 +413,23 @@ public boolean performAccessibilityAction(View host, int action, Bundle args) { private void updateMargins() { LayoutParams layoutParams = view.getLayoutParams(); - if (!(layoutParams instanceof MarginLayoutParams) || originalMargins == null) { + if (!(layoutParams instanceof MarginLayoutParams) || view.originalMargins == null) { Log.w(TAG, "Unable to update margins because layout params are not MarginLayoutParams"); return; } + if (view.getParent() == null) { + // Parent will set layout params to view again. Wait for addView() is done to update layout + // params, in case we save the already updated margins as the original margins. + return; + } int extraBottomMargin = getAnchorView() != null ? extraBottomMarginAnchorView : extraBottomMarginWindowInset; MarginLayoutParams marginParams = (MarginLayoutParams) layoutParams; - marginParams.bottomMargin = originalMargins.bottom + extraBottomMargin; - marginParams.leftMargin = originalMargins.left + extraLeftMarginWindowInset; - marginParams.rightMargin = originalMargins.right + extraRightMarginWindowInset; + marginParams.bottomMargin = view.originalMargins.bottom + extraBottomMargin; + marginParams.leftMargin = view.originalMargins.left + extraLeftMarginWindowInset; + marginParams.rightMargin = view.originalMargins.right + extraRightMarginWindowInset; + marginParams.topMargin = view.originalMargins.top; view.requestLayout(); if (VERSION.SDK_INT >= VERSION_CODES.Q && shouldUpdateGestureInset()) { @@ -724,37 +705,6 @@ protected SwipeDismissBehavior getNewBehavior() { } final void showView() { - this.view.setOnAttachStateChangeListener( - new BaseTransientBottomBar.OnAttachStateChangeListener() { - @Override - public void onViewAttachedToWindow(View v) { - if (VERSION.SDK_INT >= VERSION_CODES.Q) { - WindowInsets insets = view.getRootWindowInsets(); - if (insets != null) { - extraBottomMarginGestureInset = insets.getMandatorySystemGestureInsets().bottom; - updateMargins(); - } - } - } - - @Override - public void onViewDetachedFromWindow(View v) { - if (isShownOrQueued()) { - // If we haven't already been dismissed then this event is coming from a - // non-user initiated action. Hence we need to make sure that we callback - // and keep our state up to date. We need to post the call since - // removeView() will call through to onDetachedFromWindow and thus overflow. - handler.post( - new Runnable() { - @Override - public void run() { - onViewHidden(BaseCallback.DISMISS_EVENT_MANUAL); - } - }); - } - } - }); - if (this.view.getParent() == null) { ViewGroup.LayoutParams lp = this.view.getLayoutParams(); @@ -762,9 +712,8 @@ public void run() { setUpBehavior((CoordinatorLayout.LayoutParams) lp); } - recalculateAndUpdateMargins(); - targetParent.addView(this.view); + recalculateAndUpdateMargins(); // Set view to INVISIBLE so it doesn't flash on the screen before the inset adjustment is // handled and the enter animation is started @@ -776,15 +725,41 @@ public void run() { return; } - // Otherwise, add one of our layout change listeners and show it in when laid out - this.view.setOnLayoutChangeListener( - new OnLayoutChangeListener() { - @Override - public void onLayoutChange(View view, int left, int top, int right, int bottom) { - BaseTransientBottomBar.this.view.setOnLayoutChangeListener(null); - BaseTransientBottomBar.this.showViewImpl(); - } - }); + // Otherwise, show it in when laid out + pendingShowingView = true; + } + + void onAttachedToWindow() { + if (VERSION.SDK_INT >= VERSION_CODES.Q) { + WindowInsets insets = view.getRootWindowInsets(); + if (insets != null) { + extraBottomMarginGestureInset = insets.getMandatorySystemGestureInsets().bottom; + updateMargins(); + } + } + } + + void onDetachedFromWindow() { + if (isShownOrQueued()) { + // If we haven't already been dismissed then this event is coming from a + // non-user initiated action. Hence we need to make sure that we callback + // and keep our state up to date. We need to post the call since + // removeView() will call through to onDetachedFromWindow and thus overflow. + handler.post( + new Runnable() { + @Override + public void run() { + onViewHidden(BaseCallback.DISMISS_EVENT_MANUAL); + } + }); + } + } + + void onLayoutChange() { + if (pendingShowingView) { + BaseTransientBottomBar.this.showViewImpl(); + pendingShowingView = false; + } } private void showViewImpl() { @@ -1116,7 +1091,6 @@ boolean shouldAnimate() { /** @hide */ @RestrictTo(LIBRARY_GROUP) protected static class SnackbarBaseLayout extends FrameLayout { - private static final OnTouchListener consumeAllTouchListener = new OnTouchListener() { @SuppressLint("ClickableViewAccessibility") @@ -1127,8 +1101,7 @@ public boolean onTouch(View v, MotionEvent event) { } }; - private BaseTransientBottomBar.OnLayoutChangeListener onLayoutChangeListener; - private BaseTransientBottomBar.OnAttachStateChangeListener onAttachStateChangeListener; + @Nullable private BaseTransientBottomBar baseTransientBottomBar; @AnimationMode private int animationMode; private final float backgroundOverlayColorAlpha; private final float actionTextColorAlpha; @@ -1137,6 +1110,8 @@ public boolean onTouch(View v, MotionEvent event) { private ColorStateList backgroundTint; private PorterDuff.Mode backgroundTintMode; + @Nullable private Rect originalMargins; + protected SnackbarBaseLayout(@NonNull Context context) { this(context, null); } @@ -1233,37 +1208,37 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); - if (onLayoutChangeListener != null) { - onLayoutChangeListener.onLayoutChange(this, l, t, r, b); + if (baseTransientBottomBar != null) { + baseTransientBottomBar.onLayoutChange(); } } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); - if (onAttachStateChangeListener != null) { - onAttachStateChangeListener.onViewAttachedToWindow(this); + if (baseTransientBottomBar != null) { + baseTransientBottomBar.onAttachedToWindow(); } - ViewCompat.requestApplyInsets(this); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); - if (onAttachStateChangeListener != null) { - onAttachStateChangeListener.onViewDetachedFromWindow(this); + if (baseTransientBottomBar != null) { + baseTransientBottomBar.onDetachedFromWindow(); } } - void setOnLayoutChangeListener( - BaseTransientBottomBar.OnLayoutChangeListener onLayoutChangeListener) { - this.onLayoutChangeListener = onLayoutChangeListener; - } - - void setOnAttachStateChangeListener( - BaseTransientBottomBar.OnAttachStateChangeListener listener) { - onAttachStateChangeListener = listener; + @Override + public void setLayoutParams(ViewGroup.LayoutParams params) { + super.setLayoutParams(params); + if (params instanceof MarginLayoutParams) { + updateOriginalMargins((MarginLayoutParams) params); + if (baseTransientBottomBar != null) { + baseTransientBottomBar.updateMargins(); + } + } } @AnimationMode @@ -1291,6 +1266,15 @@ int getMaxInlineActionWidth() { return maxInlineActionWidth; } + private void setBaseTransientBottomBar(BaseTransientBottomBar baseTransientBottomBar) { + this.baseTransientBottomBar = baseTransientBottomBar; + } + + private void updateOriginalMargins(MarginLayoutParams params) { + originalMargins = + new Rect(params.leftMargin, params.topMargin, params.rightMargin, params.bottomMargin); + } + @NonNull private Drawable createThemedBackground() { float cornerRadius =