18
18
19
19
import com .google .android .material .R ;
20
20
21
+ import static androidx .annotation .RestrictTo .Scope .LIBRARY_GROUP ;
21
22
import static com .google .android .material .shape .MaterialShapeDrawable .SHADOW_COMPAT_MODE_ALWAYS ;
22
23
import static com .google .android .material .theme .overlay .MaterialThemeOverlay .wrap ;
23
24
47
48
import androidx .annotation .NonNull ;
48
49
import androidx .annotation .Nullable ;
49
50
import androidx .annotation .Px ;
51
+ import androidx .annotation .RestrictTo ;
50
52
import androidx .coordinatorlayout .widget .CoordinatorLayout ;
51
53
import androidx .coordinatorlayout .widget .CoordinatorLayout .AttachedBehavior ;
52
54
import androidx .core .graphics .drawable .DrawableCompat ;
104
106
*
105
107
* @attr ref com.google.android.material.R.styleable#BottomAppBar_backgroundTint
106
108
* @attr ref com.google.android.material.R.styleable#BottomAppBar_fabAlignmentMode
109
+ * @attr ref com.google.android.material.R.styleable#BottomAppBar_fabAnchorMode
107
110
* @attr ref com.google.android.material.R.styleable#BottomAppBar_fabAnimationMode
108
111
* @attr ref com.google.android.material.R.styleable#BottomAppBar_fabCradleMargin
109
112
* @attr ref com.google.android.material.R.styleable#BottomAppBar_fabCradleRoundedCornerRadius
@@ -132,6 +135,22 @@ public class BottomAppBar extends Toolbar implements AttachedBehavior {
132
135
@ Retention (RetentionPolicy .SOURCE )
133
136
public @interface FabAlignmentMode {}
134
137
138
+ /** The FAB is embedded inside the BottomAppBar. */
139
+ public static final int FAB_ANCHOR_MODE_EMBED = 0 ;
140
+ /** The FAB is cradled at the top of the BottomAppBar. */
141
+ public static final int FAB_ANCHOR_MODE_CRADLE = 1 ;
142
+
143
+ /**
144
+ * The fabAnchorMode determines the placement of the FAB within the BottomAppBar. The FAB can be
145
+ * cradled at the top of the BottomAppBar, or embedded within it.
146
+ *
147
+ * @hide
148
+ */
149
+ @ RestrictTo (LIBRARY_GROUP )
150
+ @ IntDef ({FAB_ANCHOR_MODE_EMBED , FAB_ANCHOR_MODE_CRADLE })
151
+ @ Retention (RetentionPolicy .SOURCE )
152
+ public @interface FabAnchorMode {}
153
+
135
154
public static final int FAB_ANIMATION_MODE_SCALE = 0 ;
136
155
public static final int FAB_ANIMATION_MODE_SLIDE = 1 ;
137
156
@@ -153,6 +172,7 @@ public class BottomAppBar extends Toolbar implements AttachedBehavior {
153
172
@ Nullable private Animator menuAnimator ;
154
173
@ FabAlignmentMode private int fabAlignmentMode ;
155
174
@ FabAnimationMode private int fabAnimationMode ;
175
+ @ FabAnchorMode private int fabAnchorMode ;
156
176
private boolean hideOnScroll ;
157
177
private final boolean paddingBottomSystemWindowInsets ;
158
178
private final boolean paddingLeftSystemWindowInsets ;
@@ -220,11 +240,16 @@ public void onAnimationStart(Animator animation) {
220
240
@ Override
221
241
public void onScaleChanged (@ NonNull FloatingActionButton fab ) {
222
242
materialShapeDrawable .setInterpolation (
223
- fab .getVisibility () == View .VISIBLE ? fab .getScaleY () : 0 );
243
+ fab .getVisibility () == View .VISIBLE && fabAnchorMode == FAB_ANCHOR_MODE_CRADLE
244
+ ? fab .getScaleY ()
245
+ : 0 );
224
246
}
225
247
226
248
@ Override
227
249
public void onTranslationChanged (@ NonNull FloatingActionButton fab ) {
250
+ if (fabAnchorMode != FAB_ANCHOR_MODE_CRADLE ) {
251
+ return ;
252
+ }
228
253
float horizontalOffset = fab .getTranslationX ();
229
254
if (getTopEdgeTreatment ().getHorizontalOffset () != horizontalOffset ) {
230
255
getTopEdgeTreatment ().setHorizontalOffset (horizontalOffset );
@@ -276,6 +301,7 @@ public BottomAppBar(@NonNull Context context, @Nullable AttributeSet attrs, int
276
301
a .getInt (R .styleable .BottomAppBar_fabAlignmentMode , FAB_ALIGNMENT_MODE_CENTER );
277
302
fabAnimationMode =
278
303
a .getInt (R .styleable .BottomAppBar_fabAnimationMode , FAB_ANIMATION_MODE_SCALE );
304
+ fabAnchorMode = a .getInt (R .styleable .BottomAppBar_fabAnchorMode , FAB_ANCHOR_MODE_CRADLE );
279
305
hideOnScroll = a .getBoolean (R .styleable .BottomAppBar_hideOnScroll , false );
280
306
// Reading out if we are handling bottom padding, so we can apply it to the FAB.
281
307
paddingBottomSystemWindowInsets =
@@ -335,7 +361,7 @@ public WindowInsetsCompat onApplyWindowInsets(
335
361
if (leftInsetsChanged || rightInsetsChanged ) {
336
362
cancelAnimations ();
337
363
338
- setCutoutState ();
364
+ setCutoutStateAndTranslateFab ();
339
365
setActionMenuViewPosition ();
340
366
}
341
367
@@ -405,17 +431,51 @@ public void setFabAlignmentModeAndReplaceMenu(
405
431
}
406
432
407
433
/**
408
- * Returns the current fabAlignmentMode, either {@link #FAB_ANIMATION_MODE_SCALE} or {@link
409
- * #FAB_ANIMATION_MODE_SLIDE}.
434
+ * Returns the current {@code fabAnchorMode}, either {@link #FAB_ANCHOR_MODE_CRADLE} or {@link
435
+ * #FAB_ANCHOR_MODE_EMBED}.
436
+ */
437
+ @ FabAnchorMode
438
+ public int getFabAnchorMode () {
439
+ return fabAnchorMode ;
440
+ }
441
+
442
+ /**
443
+ * Sets the current {@code fabAnchorMode}.
444
+ *
445
+ * @param fabAnchorMode the desired fabAnchorMode, either {@link #FAB_ANCHOR_MODE_CRADLE} or
446
+ * {@link #FAB_ANCHOR_MODE_EMBED}.
447
+ */
448
+ public void setFabAnchorMode (@ FabAnchorMode int fabAnchorMode ) {
449
+ this .fabAnchorMode = fabAnchorMode ;
450
+ setCutoutStateAndTranslateFab ();
451
+ View fab = findDependentView ();
452
+ if (fab != null ) {
453
+ updateFabAnchorGravity (this , fab );
454
+ fab .requestLayout ();
455
+ materialShapeDrawable .invalidateSelf ();
456
+ }
457
+ }
458
+
459
+ private static void updateFabAnchorGravity (BottomAppBar bar , View fab ) {
460
+ CoordinatorLayout .LayoutParams fabLayoutParams =
461
+ (CoordinatorLayout .LayoutParams ) fab .getLayoutParams ();
462
+ fabLayoutParams .anchorGravity = Gravity .CENTER ;
463
+ if (bar .fabAnchorMode == FAB_ANCHOR_MODE_CRADLE ) {
464
+ fabLayoutParams .anchorGravity |= Gravity .TOP ;
465
+ }
466
+ }
467
+
468
+ /**
469
+ * Returns the current {@code fabAnimationMode}, either {@link #FAB_ANIMATION_MODE_SCALE} or
470
+ * {@link #FAB_ANIMATION_MODE_SLIDE}.
410
471
*/
411
472
@ FabAnimationMode
412
473
public int getFabAnimationMode () {
413
474
return fabAnimationMode ;
414
475
}
415
476
416
477
/**
417
- * Sets the current fabAlignmentMode. Determines which animation will be played when the fab is
418
- * animated from from one {@link FabAlignmentMode} to another.
478
+ * Sets the current {@code fabAnimationMode}.
419
479
*
420
480
* @param fabAnimationMode the desired fabAlignmentMode, either {@link #FAB_ALIGNMENT_MODE_CENTER}
421
481
* or {@link #FAB_ALIGNMENT_MODE_END}.
@@ -441,7 +501,10 @@ public float getFabCradleMargin() {
441
501
}
442
502
443
503
/**
444
- * Sets the cradle margin for the fab cutout. This is the space between the fab and the cutout.
504
+ * Sets the cradle margin for the fab cutout.
505
+ *
506
+ * This is the space between the fab and the cutout. If
507
+ * the fab anchor mode is not cradled, this will not be respected.
445
508
*/
446
509
public void setFabCradleMargin (@ Dimension float cradleMargin ) {
447
510
if (cradleMargin != getFabCradleMargin ()) {
@@ -450,13 +513,19 @@ public void setFabCradleMargin(@Dimension float cradleMargin) {
450
513
}
451
514
}
452
515
453
- /** Returns the rounded corner radius for the cutout. A value of 0 will be a sharp edge. */
516
+ /**
517
+ * Returns the rounded corner radius for the cutout if it exists. A value of 0 will be a
518
+ * sharp edge.
519
+ */
454
520
@ Dimension
455
521
public float getFabCradleRoundedCornerRadius () {
456
522
return getTopEdgeTreatment ().getFabCradleRoundedCornerRadius ();
457
523
}
458
524
459
- /** Sets the rounded corner radius for the fab cutout. A value of 0 will be a sharp edge. */
525
+ /**
526
+ * Sets the rounded corner radius for the fab cutout. A value of 0 will be a sharp edge.
527
+ * This will not be visible until there is a cradle.
528
+ */
460
529
public void setFabCradleRoundedCornerRadius (@ Dimension float roundedCornerRadius ) {
461
530
if (roundedCornerRadius != getFabCradleRoundedCornerRadius ()) {
462
531
getTopEdgeTreatment ().setFabCradleRoundedCornerRadius (roundedCornerRadius );
@@ -477,12 +546,13 @@ public float getCradleVerticalOffset() {
477
546
* Sets the vertical offset, in pixels, of the {@link FloatingActionButton} being cradled. An
478
547
* offset of 0 indicates the vertical center of the {@link FloatingActionButton} is positioned on
479
548
* the top edge.
549
+ * This will not be visible until there is a cradle.
480
550
*/
481
551
public void setCradleVerticalOffset (@ Dimension float verticalOffset ) {
482
552
if (verticalOffset != getCradleVerticalOffset ()) {
483
553
getTopEdgeTreatment ().setCradleVerticalOffset (verticalOffset );
484
554
materialShapeDrawable .invalidateSelf ();
485
- setCutoutState ();
555
+ setCutoutStateAndTranslateFab ();
486
556
}
487
557
}
488
558
@@ -629,7 +699,7 @@ private void dispatchAnimationEnd() {
629
699
630
700
/**
631
701
* Sets the fab diameter. This will be called automatically by the {@link BottomAppBar.Behavior}
632
- * if the fab is anchored to this {@link BottomAppBar}.
702
+ * if the fab is anchored to this {@link BottomAppBar}..
633
703
*/
634
704
boolean setFabDiameter (@ Px int diameter ) {
635
705
if (diameter != getTopEdgeTreatment ().getFabDiameter ()) {
@@ -871,7 +941,10 @@ public void onAnimationEnd(Animator animation) {
871
941
}
872
942
873
943
private float getFabTranslationY () {
874
- return -getTopEdgeTreatment ().getCradleVerticalOffset ();
944
+ if (fabAnchorMode == FAB_ANCHOR_MODE_CRADLE ) {
945
+ return -getTopEdgeTreatment ().getCradleVerticalOffset ();
946
+ }
947
+ return 0 ;
875
948
}
876
949
877
950
private float getFabTranslationX (@ FabAlignmentMode int fabAlignmentMode ) {
@@ -1000,7 +1073,7 @@ protected void onLayout(boolean changed, int l, int t, int r, int b) {
1000
1073
if (changed ) {
1001
1074
cancelAnimations ();
1002
1075
1003
- setCutoutState ();
1076
+ setCutoutStateAndTranslateFab ();
1004
1077
}
1005
1078
1006
1079
// Always ensure the MenuView is in the correct position after a layout.
@@ -1013,11 +1086,14 @@ private BottomAppBarTopEdgeTreatment getTopEdgeTreatment() {
1013
1086
materialShapeDrawable .getShapeAppearanceModel ().getTopEdge ();
1014
1087
}
1015
1088
1016
- private void setCutoutState () {
1089
+ private void setCutoutStateAndTranslateFab () {
1017
1090
// Layout all elements related to the positioning of the fab.
1018
1091
getTopEdgeTreatment ().setHorizontalOffset (getFabTranslationX ());
1092
+ materialShapeDrawable .setInterpolation (
1093
+ fabAttached && isFabVisibleOrWillBeShown () && fabAnchorMode == FAB_ANCHOR_MODE_CRADLE
1094
+ ? 1
1095
+ : 0 );
1019
1096
View fab = findDependentView ();
1020
- materialShapeDrawable .setInterpolation (fabAttached && isFabVisibleOrWillBeShown () ? 1 : 0 );
1021
1097
if (fab != null ) {
1022
1098
fab .setTranslationY (getFabTranslationY ());
1023
1099
fab .setTranslationX (getFabTranslationX ());
@@ -1164,10 +1240,12 @@ public void onLayoutChange(
1164
1240
// Extra padding is added for the fake shadow on API < 21. Ensure we don't add too
1165
1241
// much space by removing that extra padding.
1166
1242
int bottomShadowPadding = (fab .getMeasuredHeight () - height ) / 2 ;
1167
- int bottomMargin =
1168
- child
1169
- .getResources ()
1170
- .getDimensionPixelOffset (R .dimen .mtrl_bottomappbar_fab_bottom_margin );
1243
+ int bottomMargin = 0 ;
1244
+ if (child .fabAnchorMode == FAB_ANCHOR_MODE_CRADLE ) {
1245
+ bottomMargin = child
1246
+ .getResources ()
1247
+ .getDimensionPixelOffset (R .dimen .mtrl_bottomappbar_fab_bottom_margin );
1248
+ }
1171
1249
// Should be moved above the bottom insets with space ignoring any shadow padding.
1172
1250
int minBottomMargin = bottomMargin - bottomShadowPadding ;
1173
1251
fabLayoutParams .bottomMargin = child .getBottomInset () + minBottomMargin ;
@@ -1201,12 +1279,12 @@ public boolean onLayoutChild(
1201
1279
if (dependentView != null && !ViewCompat .isLaidOut (dependentView )) {
1202
1280
// Set the initial position of the FloatingActionButton with the BottomAppBar vertical
1203
1281
// offset.
1204
- CoordinatorLayout .LayoutParams fabLayoutParams =
1205
- (CoordinatorLayout .LayoutParams ) dependentView .getLayoutParams ();
1206
- fabLayoutParams .anchorGravity = Gravity .CENTER | Gravity .TOP ;
1282
+ updateFabAnchorGravity (child , dependentView );
1207
1283
1208
1284
// Keep track of the original bottom margin for the fab. We will manage the margin if
1209
1285
// nothing was set.
1286
+ CoordinatorLayout .LayoutParams fabLayoutParams =
1287
+ (CoordinatorLayout .LayoutParams ) dependentView .getLayoutParams ();
1210
1288
originalBottomMargin = fabLayoutParams .bottomMargin ;
1211
1289
1212
1290
if (dependentView instanceof FloatingActionButton ) {
@@ -1230,7 +1308,7 @@ public boolean onLayoutChild(
1230
1308
}
1231
1309
1232
1310
// Move the fab to the correct position
1233
- child .setCutoutState ();
1311
+ child .setCutoutStateAndTranslateFab ();
1234
1312
}
1235
1313
1236
1314
// Now let the CoordinatorLayout lay out the BAB
0 commit comments