Skip to content

Commit cab45dd

Browse files
imhappiraajkumars
authored andcommittedJun 23, 2022
[BottomAppBar] Add new anchor mode attribute for FAB that has an embedded option
PiperOrigin-RevId: 455660518
1 parent 241aa5c commit cab45dd

File tree

8 files changed

+155
-27
lines changed

8 files changed

+155
-27
lines changed
 

‎catalog/java/io/material/catalog/bottomappbar/BottomAppBarMainDemoFragment.java

+18-4
Original file line numberDiff line numberDiff line change
@@ -152,10 +152,9 @@ private void setUpDemoControls(@NonNull View view) {
152152
}
153153

154154
centerButton.setOnClickListener(
155-
v -> {
156-
bar.setFabAlignmentModeAndReplaceMenu(
157-
BottomAppBar.FAB_ALIGNMENT_MODE_CENTER, R.menu.demo_primary);
158-
});
155+
v ->
156+
bar.setFabAlignmentModeAndReplaceMenu(
157+
BottomAppBar.FAB_ALIGNMENT_MODE_CENTER, R.menu.demo_primary));
159158
endButton.setOnClickListener(
160159
v ->
161160
bar.setFabAlignmentModeAndReplaceMenu(
@@ -176,6 +175,21 @@ private void setUpDemoControls(@NonNull View view) {
176175
slideButton.setOnClickListener(
177176
v -> bar.setFabAnimationMode(BottomAppBar.FAB_ANIMATION_MODE_SLIDE));
178177

178+
// Set up FAB anchor mode toggle buttons.
179+
MaterialButton embedButton = view.findViewById(R.id.fab_anchor_mode_button_embed);
180+
MaterialButton cradleButton = view.findViewById(R.id.fab_anchor_mode_button_cradle);
181+
182+
if (bar.getFabAnchorMode() == BottomAppBar.FAB_ANCHOR_MODE_EMBED) {
183+
embedButton.setChecked(true);
184+
} else {
185+
cradleButton.setChecked(true);
186+
}
187+
188+
embedButton.setOnClickListener(
189+
v -> bar.setFabAnchorMode(BottomAppBar.FAB_ANCHOR_MODE_EMBED));
190+
cradleButton.setOnClickListener(
191+
v -> bar.setFabAnchorMode(BottomAppBar.FAB_ANCHOR_MODE_CRADLE));
192+
179193
// Set up hide on scroll switch.
180194
MaterialSwitch barScrollSwitch = view.findViewById(R.id.bar_scroll_switch);
181195
barScrollSwitch.setChecked(bar.getHideOnScroll());

‎catalog/java/io/material/catalog/bottomappbar/res/layout/cat_bottomappbar_content.xml

+23
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,29 @@
108108
android:text="@string/cat_bottomappbar_fab_hide" />
109109
</com.google.android.material.button.MaterialButtonToggleGroup>
110110

111+
<TextView
112+
android:layout_width="wrap_content"
113+
android:layout_height="wrap_content"
114+
android:layout_marginTop="8dp"
115+
android:text="@string/cat_bottomappbar_fab_anchor_mode"
116+
android:textAppearance="?attr/textAppearanceBody1" />
117+
<com.google.android.material.button.MaterialButtonToggleGroup
118+
android:layout_width="wrap_content"
119+
android:layout_height="wrap_content">
120+
<Button
121+
android:id="@+id/fab_anchor_mode_button_embed"
122+
style="?attr/materialButtonOutlinedStyle"
123+
android:layout_width="wrap_content"
124+
android:layout_height="wrap_content"
125+
android:text="@string/cat_bottomappbar_button_embed" />
126+
<Button
127+
android:id="@+id/fab_anchor_mode_button_cradle"
128+
style="?attr/materialButtonOutlinedStyle"
129+
android:layout_width="wrap_content"
130+
android:layout_height="wrap_content"
131+
android:text="@string/cat_bottomappbar_button_cradle" />
132+
</com.google.android.material.button.MaterialButtonToggleGroup>
133+
111134
<com.google.android.material.materialswitch.MaterialSwitch
112135
android:id="@+id/bar_scroll_switch"
113136
android:layout_width="wrap_content"

‎catalog/java/io/material/catalog/bottomappbar/res/values/strings.xml

+3
Original file line numberDiff line numberDiff line change
@@ -49,4 +49,7 @@ Phasellus in aliquet mi. Pellentesque habitant morbi tristique senectus et netus
4949
<string name="cat_bottomappbar_fab_alignment_mode" description="Name of the FAB Alignment Mode toggle button group [CHAR_LIMIT=NONE]">Fab Alignment Mode</string>
5050
<string name="cat_bottomappbar_fab_animation_mode" description="Name of the FAB Animation Mode toggle button group [CHAR_LIMIT=NONE]">Fab Animation Mode</string>
5151
<string name="cat_bottomappbar_fab_visibility_mode" description="Name of the FAB Visibility Mode toggle button group [CHAR_LIMIT=NONE]">Fab Visibility Mode</string>
52+
<string name="cat_bottomappbar_fab_anchor_mode" description="Name of the FAB Anchor Mode toggle button group [CHAR_LIMIT=NONE]">Fab Anchor Mode</string>
53+
<string name="cat_bottomappbar_button_embed" description="Name of the option to embed the FAB inside the BottomAppBar [CHAR_LIMIT=NONE]">Embed</string>
54+
<string name="cat_bottomappbar_button_cradle" description="Name of the option to cradle the FAB on top of the BottomAppBar [CHAR_LIMIT=NONE]">Cradle</string>
5255
</resources>

‎docs/components/BottomAppBar.md

+1
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,7 @@ Element | Attribute | Related
234234
-------------------------------- | ---------------------------------- | ---------------------------------------------------------------------- | -------------
235235
**Alignment mode** | `app:fabAlignmentMode` | `setFabAlignmentMode`<br>`getFabAlignmentMode` | `center`
236236
**Animation mode** | `app:fabAnimationMode` | `setFabAnimationMode`<br>`getFabAnimationMode` | `slide`
237+
**Anchor mode** | `app:fabAnchorMode` | `setFabAnchorMode` <br> `getFabAnchorMode` | `cradle`
237238
**Cradle margin** | `app:fabCradleMargin` | `setFabCradleMargin`<br>`getFabCradleMargin` | `6dp`
238239
**Cradle rounded corner radius** | `app:fabCradleRoundedCornerRadius` | `setFabCradleRoundedCornerRadius`<br>`getFabCradleRoundedCornerRadius` | `4dp`
239240
**Cradle vertical offset** | `app:fabCradleVerticalOffset` | `setCradleVerticalOffset`<br>`getCradleVerticalOffset` | `12dp`

‎lib/java/com/google/android/material/bottomappbar/BottomAppBar.java

+101-23
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import com.google.android.material.R;
2020

21+
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
2122
import static com.google.android.material.shape.MaterialShapeDrawable.SHADOW_COMPAT_MODE_ALWAYS;
2223
import static com.google.android.material.theme.overlay.MaterialThemeOverlay.wrap;
2324

@@ -47,6 +48,7 @@
4748
import androidx.annotation.NonNull;
4849
import androidx.annotation.Nullable;
4950
import androidx.annotation.Px;
51+
import androidx.annotation.RestrictTo;
5052
import androidx.coordinatorlayout.widget.CoordinatorLayout;
5153
import androidx.coordinatorlayout.widget.CoordinatorLayout.AttachedBehavior;
5254
import androidx.core.graphics.drawable.DrawableCompat;
@@ -104,6 +106,7 @@
104106
*
105107
* @attr ref com.google.android.material.R.styleable#BottomAppBar_backgroundTint
106108
* @attr ref com.google.android.material.R.styleable#BottomAppBar_fabAlignmentMode
109+
* @attr ref com.google.android.material.R.styleable#BottomAppBar_fabAnchorMode
107110
* @attr ref com.google.android.material.R.styleable#BottomAppBar_fabAnimationMode
108111
* @attr ref com.google.android.material.R.styleable#BottomAppBar_fabCradleMargin
109112
* @attr ref com.google.android.material.R.styleable#BottomAppBar_fabCradleRoundedCornerRadius
@@ -132,6 +135,22 @@ public class BottomAppBar extends Toolbar implements AttachedBehavior {
132135
@Retention(RetentionPolicy.SOURCE)
133136
public @interface FabAlignmentMode {}
134137

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+
135154
public static final int FAB_ANIMATION_MODE_SCALE = 0;
136155
public static final int FAB_ANIMATION_MODE_SLIDE = 1;
137156

@@ -153,6 +172,7 @@ public class BottomAppBar extends Toolbar implements AttachedBehavior {
153172
@Nullable private Animator menuAnimator;
154173
@FabAlignmentMode private int fabAlignmentMode;
155174
@FabAnimationMode private int fabAnimationMode;
175+
@FabAnchorMode private int fabAnchorMode;
156176
private boolean hideOnScroll;
157177
private final boolean paddingBottomSystemWindowInsets;
158178
private final boolean paddingLeftSystemWindowInsets;
@@ -220,11 +240,16 @@ public void onAnimationStart(Animator animation) {
220240
@Override
221241
public void onScaleChanged(@NonNull FloatingActionButton fab) {
222242
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);
224246
}
225247

226248
@Override
227249
public void onTranslationChanged(@NonNull FloatingActionButton fab) {
250+
if (fabAnchorMode != FAB_ANCHOR_MODE_CRADLE) {
251+
return;
252+
}
228253
float horizontalOffset = fab.getTranslationX();
229254
if (getTopEdgeTreatment().getHorizontalOffset() != horizontalOffset) {
230255
getTopEdgeTreatment().setHorizontalOffset(horizontalOffset);
@@ -276,6 +301,7 @@ public BottomAppBar(@NonNull Context context, @Nullable AttributeSet attrs, int
276301
a.getInt(R.styleable.BottomAppBar_fabAlignmentMode, FAB_ALIGNMENT_MODE_CENTER);
277302
fabAnimationMode =
278303
a.getInt(R.styleable.BottomAppBar_fabAnimationMode, FAB_ANIMATION_MODE_SCALE);
304+
fabAnchorMode = a.getInt(R.styleable.BottomAppBar_fabAnchorMode, FAB_ANCHOR_MODE_CRADLE);
279305
hideOnScroll = a.getBoolean(R.styleable.BottomAppBar_hideOnScroll, false);
280306
// Reading out if we are handling bottom padding, so we can apply it to the FAB.
281307
paddingBottomSystemWindowInsets =
@@ -335,7 +361,7 @@ public WindowInsetsCompat onApplyWindowInsets(
335361
if (leftInsetsChanged || rightInsetsChanged) {
336362
cancelAnimations();
337363

338-
setCutoutState();
364+
setCutoutStateAndTranslateFab();
339365
setActionMenuViewPosition();
340366
}
341367

@@ -405,17 +431,51 @@ public void setFabAlignmentModeAndReplaceMenu(
405431
}
406432

407433
/**
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}.
410471
*/
411472
@FabAnimationMode
412473
public int getFabAnimationMode() {
413474
return fabAnimationMode;
414475
}
415476

416477
/**
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}.
419479
*
420480
* @param fabAnimationMode the desired fabAlignmentMode, either {@link #FAB_ALIGNMENT_MODE_CENTER}
421481
* or {@link #FAB_ALIGNMENT_MODE_END}.
@@ -441,7 +501,10 @@ public float getFabCradleMargin() {
441501
}
442502

443503
/**
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.
445508
*/
446509
public void setFabCradleMargin(@Dimension float cradleMargin) {
447510
if (cradleMargin != getFabCradleMargin()) {
@@ -450,13 +513,19 @@ public void setFabCradleMargin(@Dimension float cradleMargin) {
450513
}
451514
}
452515

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+
*/
454520
@Dimension
455521
public float getFabCradleRoundedCornerRadius() {
456522
return getTopEdgeTreatment().getFabCradleRoundedCornerRadius();
457523
}
458524

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+
*/
460529
public void setFabCradleRoundedCornerRadius(@Dimension float roundedCornerRadius) {
461530
if (roundedCornerRadius != getFabCradleRoundedCornerRadius()) {
462531
getTopEdgeTreatment().setFabCradleRoundedCornerRadius(roundedCornerRadius);
@@ -477,12 +546,13 @@ public float getCradleVerticalOffset() {
477546
* Sets the vertical offset, in pixels, of the {@link FloatingActionButton} being cradled. An
478547
* offset of 0 indicates the vertical center of the {@link FloatingActionButton} is positioned on
479548
* the top edge.
549+
* This will not be visible until there is a cradle.
480550
*/
481551
public void setCradleVerticalOffset(@Dimension float verticalOffset) {
482552
if (verticalOffset != getCradleVerticalOffset()) {
483553
getTopEdgeTreatment().setCradleVerticalOffset(verticalOffset);
484554
materialShapeDrawable.invalidateSelf();
485-
setCutoutState();
555+
setCutoutStateAndTranslateFab();
486556
}
487557
}
488558

@@ -629,7 +699,7 @@ private void dispatchAnimationEnd() {
629699

630700
/**
631701
* 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}..
633703
*/
634704
boolean setFabDiameter(@Px int diameter) {
635705
if (diameter != getTopEdgeTreatment().getFabDiameter()) {
@@ -871,7 +941,10 @@ public void onAnimationEnd(Animator animation) {
871941
}
872942

873943
private float getFabTranslationY() {
874-
return -getTopEdgeTreatment().getCradleVerticalOffset();
944+
if (fabAnchorMode == FAB_ANCHOR_MODE_CRADLE) {
945+
return -getTopEdgeTreatment().getCradleVerticalOffset();
946+
}
947+
return 0;
875948
}
876949

877950
private float getFabTranslationX(@FabAlignmentMode int fabAlignmentMode) {
@@ -1000,7 +1073,7 @@ protected void onLayout(boolean changed, int l, int t, int r, int b) {
10001073
if (changed) {
10011074
cancelAnimations();
10021075

1003-
setCutoutState();
1076+
setCutoutStateAndTranslateFab();
10041077
}
10051078

10061079
// Always ensure the MenuView is in the correct position after a layout.
@@ -1013,11 +1086,14 @@ private BottomAppBarTopEdgeTreatment getTopEdgeTreatment() {
10131086
materialShapeDrawable.getShapeAppearanceModel().getTopEdge();
10141087
}
10151088

1016-
private void setCutoutState() {
1089+
private void setCutoutStateAndTranslateFab() {
10171090
// Layout all elements related to the positioning of the fab.
10181091
getTopEdgeTreatment().setHorizontalOffset(getFabTranslationX());
1092+
materialShapeDrawable.setInterpolation(
1093+
fabAttached && isFabVisibleOrWillBeShown() && fabAnchorMode == FAB_ANCHOR_MODE_CRADLE
1094+
? 1
1095+
: 0);
10191096
View fab = findDependentView();
1020-
materialShapeDrawable.setInterpolation(fabAttached && isFabVisibleOrWillBeShown() ? 1 : 0);
10211097
if (fab != null) {
10221098
fab.setTranslationY(getFabTranslationY());
10231099
fab.setTranslationX(getFabTranslationX());
@@ -1164,10 +1240,12 @@ public void onLayoutChange(
11641240
// Extra padding is added for the fake shadow on API < 21. Ensure we don't add too
11651241
// much space by removing that extra padding.
11661242
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+
}
11711249
// Should be moved above the bottom insets with space ignoring any shadow padding.
11721250
int minBottomMargin = bottomMargin - bottomShadowPadding;
11731251
fabLayoutParams.bottomMargin = child.getBottomInset() + minBottomMargin;
@@ -1201,12 +1279,12 @@ public boolean onLayoutChild(
12011279
if (dependentView != null && !ViewCompat.isLaidOut(dependentView)) {
12021280
// Set the initial position of the FloatingActionButton with the BottomAppBar vertical
12031281
// offset.
1204-
CoordinatorLayout.LayoutParams fabLayoutParams =
1205-
(CoordinatorLayout.LayoutParams) dependentView.getLayoutParams();
1206-
fabLayoutParams.anchorGravity = Gravity.CENTER | Gravity.TOP;
1282+
updateFabAnchorGravity(child, dependentView);
12071283

12081284
// Keep track of the original bottom margin for the fab. We will manage the margin if
12091285
// nothing was set.
1286+
CoordinatorLayout.LayoutParams fabLayoutParams =
1287+
(CoordinatorLayout.LayoutParams) dependentView.getLayoutParams();
12101288
originalBottomMargin = fabLayoutParams.bottomMargin;
12111289

12121290
if (dependentView instanceof FloatingActionButton) {
@@ -1230,7 +1308,7 @@ public boolean onLayoutChild(
12301308
}
12311309

12321310
// Move the fab to the correct position
1233-
child.setCutoutState();
1311+
child.setCutoutStateAndTranslateFab();
12341312
}
12351313

12361314
// Now let the CoordinatorLayout lay out the BAB

‎lib/java/com/google/android/material/bottomappbar/res-public/values/public.xml

+1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
<public name="bottomAppBarStyle" type="attr"/>
1919
<public name="fabAlignmentMode" type="attr"/>
2020
<public name="fabAnimationMode" type="attr"/>
21+
<public name="fabAnchorMode" type="attr"/>
2122
<public name="fabCradleMargin" type="attr"/>
2223
<public name="fabCradleRoundedCornerRadius" type="attr"/>
2324
<public name="fabCradleVerticalOffset" type="attr"/>

‎lib/java/com/google/android/material/bottomappbar/res/values/attrs.xml

+7
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,13 @@
3030
<!-- Mode that aligns the fab to the end. -->
3131
<enum name="end" value="1"/>
3232
</attr>
33+
<!-- The anchor mode of the fab relative to the BottomAppBar. -->
34+
<attr name="fabAnchorMode">
35+
<!-- Mode that anchors the fab embedded inside the BottomAppBar. -->
36+
<enum name="embed" value="0"/>
37+
<!-- Mode that anchors the fab to be cradled within the top edge of the BottomAppBar. -->
38+
<enum name="cradle" value="1"/>
39+
</attr>
3340
<!-- The animation mode that should be used when the fab animates between alignment modes. -->
3441
<attr name="fabAnimationMode">
3542
<!-- Mode that scales the fab down to a point, moves it, then scales the fab back to its normal size. -->

‎lib/java/com/google/android/material/bottomappbar/res/values/styles.xml

+1
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353

5454
<style name="Widget.Material3.BottomAppBar" parent="Widget.MaterialComponents.BottomAppBar">
5555
<item name="fabAnimationMode">slide</item>
56+
<item name="fabAnchorMode">cradle</item>
5657
<item name="fabCradleMargin">@dimen/m3_bottomappbar_fab_cradle_margin</item>
5758
<item name="fabCradleRoundedCornerRadius">
5859
@dimen/m3_bottomappbar_fab_cradle_rounded_corner_radius

0 commit comments

Comments
 (0)
Please sign in to comment.