|
83 | 83 | import com.google.android.material.resources.MaterialResources; |
84 | 84 | import java.lang.annotation.Retention; |
85 | 85 | import java.lang.annotation.RetentionPolicy; |
| 86 | +import java.lang.ref.WeakReference; |
86 | 87 | import java.util.ArrayList; |
87 | 88 | import java.util.List; |
88 | 89 |
|
@@ -262,19 +263,11 @@ public boolean handleMessage(@NonNull Message message) { |
262 | 263 |
|
263 | 264 | private int duration; |
264 | 265 | private boolean gestureInsetBottomIgnored; |
265 | | - @Nullable private View anchorView; |
| 266 | + |
| 267 | + @Nullable |
| 268 | + private Anchor anchor; |
| 269 | + |
266 | 270 | private boolean anchorViewLayoutListenerEnabled = false; |
267 | | - private final OnGlobalLayoutListener anchorViewLayoutListener = |
268 | | - new OnGlobalLayoutListener() { |
269 | | - @Override |
270 | | - public void onGlobalLayout() { |
271 | | - if (!anchorViewLayoutListenerEnabled) { |
272 | | - return; |
273 | | - } |
274 | | - extraBottomMarginAnchorView = calculateBottomMarginForAnchorView(); |
275 | | - updateMargins(); |
276 | | - } |
277 | | - }; |
278 | 271 |
|
279 | 272 | @RequiresApi(VERSION_CODES.Q) |
280 | 273 | private final Runnable bottomMarginGestureInsetRunnable = |
@@ -451,7 +444,7 @@ private void updateMargins() { |
451 | 444 | } |
452 | 445 |
|
453 | 446 | int extraBottomMargin = |
454 | | - anchorView != null ? extraBottomMarginAnchorView : extraBottomMarginWindowInset; |
| 447 | + getAnchorView() != null ? extraBottomMarginAnchorView : extraBottomMarginWindowInset; |
455 | 448 | MarginLayoutParams marginParams = (MarginLayoutParams) layoutParams; |
456 | 449 | marginParams.bottomMargin = originalMargins.bottom + extraBottomMargin; |
457 | 450 | marginParams.leftMargin = originalMargins.left + extraLeftMarginWindowInset; |
@@ -566,15 +559,16 @@ public B setAnimationMode(@AnimationMode int animationMode) { |
566 | 559 | */ |
567 | 560 | @Nullable |
568 | 561 | public View getAnchorView() { |
569 | | - return anchorView; |
| 562 | + return anchor == null ? null : anchor.getAnchorView(); |
570 | 563 | } |
571 | 564 |
|
572 | 565 | /** Sets the view the {@link BaseTransientBottomBar} should be anchored above. */ |
573 | 566 | @NonNull |
574 | 567 | public B setAnchorView(@Nullable View anchorView) { |
575 | | - ViewUtils.removeOnGlobalLayoutListener(this.anchorView, anchorViewLayoutListener); |
576 | | - this.anchorView = anchorView; |
577 | | - ViewUtils.addOnGlobalLayoutListener(this.anchorView, anchorViewLayoutListener); |
| 568 | + if (this.anchor != null) { |
| 569 | + this.anchor.unanchor(); |
| 570 | + } |
| 571 | + this.anchor = anchorView == null ? null : Anchor.anchor(this, anchorView); |
578 | 572 | return (B) this; |
579 | 573 | } |
580 | 574 |
|
@@ -768,8 +762,7 @@ public void run() { |
768 | 762 | setUpBehavior((CoordinatorLayout.LayoutParams) lp); |
769 | 763 | } |
770 | 764 |
|
771 | | - extraBottomMarginAnchorView = calculateBottomMarginForAnchorView(); |
772 | | - updateMargins(); |
| 765 | + recalculateAndUpdateMargins(); |
773 | 766 |
|
774 | 767 | // Set view to INVISIBLE so it doesn't flash on the screen before the inset adjustment is |
775 | 768 | // handled and the enter animation is started |
@@ -861,18 +854,23 @@ public void onDragStateChanged(int state) { |
861 | 854 | clp.setBehavior(behavior); |
862 | 855 | // Also set the inset edge so that views can dodge the bar correctly, but only if there is |
863 | 856 | // no anchor view. |
864 | | - if (anchorView == null) { |
| 857 | + if (getAnchorView() == null) { |
865 | 858 | clp.insetEdge = Gravity.BOTTOM; |
866 | 859 | } |
867 | 860 | } |
868 | 861 |
|
| 862 | + private void recalculateAndUpdateMargins() { |
| 863 | + extraBottomMarginAnchorView = calculateBottomMarginForAnchorView(); |
| 864 | + updateMargins(); |
| 865 | + } |
| 866 | + |
869 | 867 | private int calculateBottomMarginForAnchorView() { |
870 | | - if (anchorView == null) { |
| 868 | + if (getAnchorView() == null) { |
871 | 869 | return 0; |
872 | 870 | } |
873 | 871 |
|
874 | 872 | int[] anchorViewLocation = new int[2]; |
875 | | - anchorView.getLocationOnScreen(anchorViewLocation); |
| 873 | + getAnchorView().getLocationOnScreen(anchorViewLocation); |
876 | 874 | int anchorViewAbsoluteYTop = anchorViewLocation[1]; |
877 | 875 |
|
878 | 876 | int[] targetParentLocation = new int[2]; |
@@ -1096,9 +1094,6 @@ void onViewHidden(int event) { |
1096 | 1094 | } |
1097 | 1095 | } |
1098 | 1096 |
|
1099 | | - // Reset anchor view and onGlobalLayoutListener so they won't be leaked. |
1100 | | - setAnchorView(null); |
1101 | | - |
1102 | 1097 | // Lastly, hide and remove the view from the parent (if attached) |
1103 | 1098 | ViewParent parent = view.getParent(); |
1104 | 1099 | if (parent instanceof ViewGroup) { |
@@ -1362,4 +1357,77 @@ public void onInterceptTouchEvent( |
1362 | 1357 | } |
1363 | 1358 | } |
1364 | 1359 | } |
| 1360 | + |
| 1361 | + @SuppressWarnings("rawtypes") // Generic type of BaseTransientBottomBar doesn't matter here. |
| 1362 | + static class Anchor |
| 1363 | + implements android.view.View.OnAttachStateChangeListener, OnGlobalLayoutListener { |
| 1364 | + @NonNull |
| 1365 | + private final WeakReference<BaseTransientBottomBar> transientBottomBar; |
| 1366 | + |
| 1367 | + @NonNull |
| 1368 | + private final WeakReference<View> anchorView; |
| 1369 | + |
| 1370 | + static Anchor anchor( |
| 1371 | + @NonNull BaseTransientBottomBar transientBottomBar, @NonNull View anchorView) { |
| 1372 | + Anchor anchor = new Anchor(transientBottomBar, anchorView); |
| 1373 | + if (ViewCompat.isAttachedToWindow(anchorView)) { |
| 1374 | + ViewUtils.addOnGlobalLayoutListener(anchorView, anchor); |
| 1375 | + } |
| 1376 | + anchorView.addOnAttachStateChangeListener(anchor); |
| 1377 | + return anchor; |
| 1378 | + } |
| 1379 | + |
| 1380 | + private Anchor( |
| 1381 | + @NonNull BaseTransientBottomBar transientBottomBar, @NonNull View anchorView) { |
| 1382 | + this.transientBottomBar = new WeakReference<>(transientBottomBar); |
| 1383 | + this.anchorView = new WeakReference<>(anchorView); |
| 1384 | + } |
| 1385 | + |
| 1386 | + @Override |
| 1387 | + public void onViewAttachedToWindow(View anchorView) { |
| 1388 | + if (unanchorIfNoTransientBottomBar()) { |
| 1389 | + return; |
| 1390 | + } |
| 1391 | + ViewUtils.addOnGlobalLayoutListener(anchorView, this); |
| 1392 | + } |
| 1393 | + |
| 1394 | + @Override |
| 1395 | + public void onViewDetachedFromWindow(View anchorView) { |
| 1396 | + if (unanchorIfNoTransientBottomBar()) { |
| 1397 | + return; |
| 1398 | + } |
| 1399 | + ViewUtils.removeOnGlobalLayoutListener(anchorView, this); |
| 1400 | + } |
| 1401 | + |
| 1402 | + @Override |
| 1403 | + public void onGlobalLayout() { |
| 1404 | + if (unanchorIfNoTransientBottomBar() |
| 1405 | + || !transientBottomBar.get().anchorViewLayoutListenerEnabled) { |
| 1406 | + return; |
| 1407 | + } |
| 1408 | + transientBottomBar.get().recalculateAndUpdateMargins(); |
| 1409 | + } |
| 1410 | + |
| 1411 | + @Nullable |
| 1412 | + View getAnchorView() { |
| 1413 | + return anchorView.get(); |
| 1414 | + } |
| 1415 | + |
| 1416 | + private boolean unanchorIfNoTransientBottomBar() { |
| 1417 | + if (transientBottomBar.get() == null) { |
| 1418 | + unanchor(); |
| 1419 | + return true; |
| 1420 | + } |
| 1421 | + return false; |
| 1422 | + } |
| 1423 | + |
| 1424 | + void unanchor() { |
| 1425 | + if (anchorView.get() != null) { |
| 1426 | + anchorView.get().removeOnAttachStateChangeListener(this); |
| 1427 | + ViewUtils.removeOnGlobalLayoutListener(anchorView.get(), this); |
| 1428 | + } |
| 1429 | + anchorView.clear(); |
| 1430 | + transientBottomBar.clear(); |
| 1431 | + } |
| 1432 | + } |
1365 | 1433 | } |
0 commit comments