Skip to content

Commit

Permalink
[BottomAppBar] Add new anchor mode attribute for FAB that has an embe…
Browse files Browse the repository at this point in the history
…dded option

PiperOrigin-RevId: 455660518
  • Loading branch information
imhappi authored and raajkumars committed Jun 23, 2022
1 parent 241aa5c commit cab45dd
Show file tree
Hide file tree
Showing 8 changed files with 155 additions and 27 deletions.
Expand Up @@ -152,10 +152,9 @@ private void setUpDemoControls(@NonNull View view) {
}

centerButton.setOnClickListener(
v -> {
bar.setFabAlignmentModeAndReplaceMenu(
BottomAppBar.FAB_ALIGNMENT_MODE_CENTER, R.menu.demo_primary);
});
v ->
bar.setFabAlignmentModeAndReplaceMenu(
BottomAppBar.FAB_ALIGNMENT_MODE_CENTER, R.menu.demo_primary));
endButton.setOnClickListener(
v ->
bar.setFabAlignmentModeAndReplaceMenu(
Expand All @@ -176,6 +175,21 @@ private void setUpDemoControls(@NonNull View view) {
slideButton.setOnClickListener(
v -> bar.setFabAnimationMode(BottomAppBar.FAB_ANIMATION_MODE_SLIDE));

// Set up FAB anchor mode toggle buttons.
MaterialButton embedButton = view.findViewById(R.id.fab_anchor_mode_button_embed);
MaterialButton cradleButton = view.findViewById(R.id.fab_anchor_mode_button_cradle);

if (bar.getFabAnchorMode() == BottomAppBar.FAB_ANCHOR_MODE_EMBED) {
embedButton.setChecked(true);
} else {
cradleButton.setChecked(true);
}

embedButton.setOnClickListener(
v -> bar.setFabAnchorMode(BottomAppBar.FAB_ANCHOR_MODE_EMBED));
cradleButton.setOnClickListener(
v -> bar.setFabAnchorMode(BottomAppBar.FAB_ANCHOR_MODE_CRADLE));

// Set up hide on scroll switch.
MaterialSwitch barScrollSwitch = view.findViewById(R.id.bar_scroll_switch);
barScrollSwitch.setChecked(bar.getHideOnScroll());
Expand Down
Expand Up @@ -108,6 +108,29 @@
android:text="@string/cat_bottomappbar_fab_hide" />
</com.google.android.material.button.MaterialButtonToggleGroup>

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/cat_bottomappbar_fab_anchor_mode"
android:textAppearance="?attr/textAppearanceBody1" />
<com.google.android.material.button.MaterialButtonToggleGroup
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<Button
android:id="@+id/fab_anchor_mode_button_embed"
style="?attr/materialButtonOutlinedStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/cat_bottomappbar_button_embed" />
<Button
android:id="@+id/fab_anchor_mode_button_cradle"
style="?attr/materialButtonOutlinedStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/cat_bottomappbar_button_cradle" />
</com.google.android.material.button.MaterialButtonToggleGroup>

<com.google.android.material.materialswitch.MaterialSwitch
android:id="@+id/bar_scroll_switch"
android:layout_width="wrap_content"
Expand Down
Expand Up @@ -49,4 +49,7 @@ Phasellus in aliquet mi. Pellentesque habitant morbi tristique senectus et netus
<string name="cat_bottomappbar_fab_alignment_mode" description="Name of the FAB Alignment Mode toggle button group [CHAR_LIMIT=NONE]">Fab Alignment Mode</string>
<string name="cat_bottomappbar_fab_animation_mode" description="Name of the FAB Animation Mode toggle button group [CHAR_LIMIT=NONE]">Fab Animation Mode</string>
<string name="cat_bottomappbar_fab_visibility_mode" description="Name of the FAB Visibility Mode toggle button group [CHAR_LIMIT=NONE]">Fab Visibility Mode</string>
<string name="cat_bottomappbar_fab_anchor_mode" description="Name of the FAB Anchor Mode toggle button group [CHAR_LIMIT=NONE]">Fab Anchor Mode</string>
<string name="cat_bottomappbar_button_embed" description="Name of the option to embed the FAB inside the BottomAppBar [CHAR_LIMIT=NONE]">Embed</string>
<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>
</resources>
1 change: 1 addition & 0 deletions docs/components/BottomAppBar.md
Expand Up @@ -234,6 +234,7 @@ Element | Attribute | Related
-------------------------------- | ---------------------------------- | ---------------------------------------------------------------------- | -------------
**Alignment mode** | `app:fabAlignmentMode` | `setFabAlignmentMode`<br>`getFabAlignmentMode` | `center`
**Animation mode** | `app:fabAnimationMode` | `setFabAnimationMode`<br>`getFabAnimationMode` | `slide`
**Anchor mode** | `app:fabAnchorMode` | `setFabAnchorMode` <br> `getFabAnchorMode` | `cradle`
**Cradle margin** | `app:fabCradleMargin` | `setFabCradleMargin`<br>`getFabCradleMargin` | `6dp`
**Cradle rounded corner radius** | `app:fabCradleRoundedCornerRadius` | `setFabCradleRoundedCornerRadius`<br>`getFabCradleRoundedCornerRadius` | `4dp`
**Cradle vertical offset** | `app:fabCradleVerticalOffset` | `setCradleVerticalOffset`<br>`getCradleVerticalOffset` | `12dp`
Expand Down
124 changes: 101 additions & 23 deletions lib/java/com/google/android/material/bottomappbar/BottomAppBar.java
Expand Up @@ -18,6 +18,7 @@

import com.google.android.material.R;

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

Expand Down Expand Up @@ -47,6 +48,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.Px;
import androidx.annotation.RestrictTo;
import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.coordinatorlayout.widget.CoordinatorLayout.AttachedBehavior;
import androidx.core.graphics.drawable.DrawableCompat;
Expand Down Expand Up @@ -104,6 +106,7 @@
*
* @attr ref com.google.android.material.R.styleable#BottomAppBar_backgroundTint
* @attr ref com.google.android.material.R.styleable#BottomAppBar_fabAlignmentMode
* @attr ref com.google.android.material.R.styleable#BottomAppBar_fabAnchorMode
* @attr ref com.google.android.material.R.styleable#BottomAppBar_fabAnimationMode
* @attr ref com.google.android.material.R.styleable#BottomAppBar_fabCradleMargin
* @attr ref com.google.android.material.R.styleable#BottomAppBar_fabCradleRoundedCornerRadius
Expand Down Expand Up @@ -132,6 +135,22 @@ public class BottomAppBar extends Toolbar implements AttachedBehavior {
@Retention(RetentionPolicy.SOURCE)
public @interface FabAlignmentMode {}

/** The FAB is embedded inside the BottomAppBar. */
public static final int FAB_ANCHOR_MODE_EMBED = 0;
/** The FAB is cradled at the top of the BottomAppBar. */
public static final int FAB_ANCHOR_MODE_CRADLE = 1;

/**
* The fabAnchorMode determines the placement of the FAB within the BottomAppBar. The FAB can be
* cradled at the top of the BottomAppBar, or embedded within it.
*
* @hide
*/
@RestrictTo(LIBRARY_GROUP)
@IntDef({FAB_ANCHOR_MODE_EMBED, FAB_ANCHOR_MODE_CRADLE})
@Retention(RetentionPolicy.SOURCE)
public @interface FabAnchorMode {}

public static final int FAB_ANIMATION_MODE_SCALE = 0;
public static final int FAB_ANIMATION_MODE_SLIDE = 1;

Expand All @@ -153,6 +172,7 @@ public class BottomAppBar extends Toolbar implements AttachedBehavior {
@Nullable private Animator menuAnimator;
@FabAlignmentMode private int fabAlignmentMode;
@FabAnimationMode private int fabAnimationMode;
@FabAnchorMode private int fabAnchorMode;
private boolean hideOnScroll;
private final boolean paddingBottomSystemWindowInsets;
private final boolean paddingLeftSystemWindowInsets;
Expand Down Expand Up @@ -220,11 +240,16 @@ public void onAnimationStart(Animator animation) {
@Override
public void onScaleChanged(@NonNull FloatingActionButton fab) {
materialShapeDrawable.setInterpolation(
fab.getVisibility() == View.VISIBLE ? fab.getScaleY() : 0);
fab.getVisibility() == View.VISIBLE && fabAnchorMode == FAB_ANCHOR_MODE_CRADLE
? fab.getScaleY()
: 0);
}

@Override
public void onTranslationChanged(@NonNull FloatingActionButton fab) {
if (fabAnchorMode != FAB_ANCHOR_MODE_CRADLE) {
return;
}
float horizontalOffset = fab.getTranslationX();
if (getTopEdgeTreatment().getHorizontalOffset() != horizontalOffset) {
getTopEdgeTreatment().setHorizontalOffset(horizontalOffset);
Expand Down Expand Up @@ -276,6 +301,7 @@ public BottomAppBar(@NonNull Context context, @Nullable AttributeSet attrs, int
a.getInt(R.styleable.BottomAppBar_fabAlignmentMode, FAB_ALIGNMENT_MODE_CENTER);
fabAnimationMode =
a.getInt(R.styleable.BottomAppBar_fabAnimationMode, FAB_ANIMATION_MODE_SCALE);
fabAnchorMode = a.getInt(R.styleable.BottomAppBar_fabAnchorMode, FAB_ANCHOR_MODE_CRADLE);
hideOnScroll = a.getBoolean(R.styleable.BottomAppBar_hideOnScroll, false);
// Reading out if we are handling bottom padding, so we can apply it to the FAB.
paddingBottomSystemWindowInsets =
Expand Down Expand Up @@ -335,7 +361,7 @@ public WindowInsetsCompat onApplyWindowInsets(
if (leftInsetsChanged || rightInsetsChanged) {
cancelAnimations();

setCutoutState();
setCutoutStateAndTranslateFab();
setActionMenuViewPosition();
}

Expand Down Expand Up @@ -405,17 +431,51 @@ public void setFabAlignmentModeAndReplaceMenu(
}

/**
* Returns the current fabAlignmentMode, either {@link #FAB_ANIMATION_MODE_SCALE} or {@link
* #FAB_ANIMATION_MODE_SLIDE}.
* Returns the current {@code fabAnchorMode}, either {@link #FAB_ANCHOR_MODE_CRADLE} or {@link
* #FAB_ANCHOR_MODE_EMBED}.
*/
@FabAnchorMode
public int getFabAnchorMode() {
return fabAnchorMode;
}

/**
* Sets the current {@code fabAnchorMode}.
*
* @param fabAnchorMode the desired fabAnchorMode, either {@link #FAB_ANCHOR_MODE_CRADLE} or
* {@link #FAB_ANCHOR_MODE_EMBED}.
*/
public void setFabAnchorMode(@FabAnchorMode int fabAnchorMode) {
this.fabAnchorMode = fabAnchorMode;
setCutoutStateAndTranslateFab();
View fab = findDependentView();
if (fab != null) {
updateFabAnchorGravity(this, fab);
fab.requestLayout();
materialShapeDrawable.invalidateSelf();
}
}

private static void updateFabAnchorGravity(BottomAppBar bar, View fab) {
CoordinatorLayout.LayoutParams fabLayoutParams =
(CoordinatorLayout.LayoutParams) fab.getLayoutParams();
fabLayoutParams.anchorGravity = Gravity.CENTER;
if (bar.fabAnchorMode == FAB_ANCHOR_MODE_CRADLE) {
fabLayoutParams.anchorGravity |= Gravity.TOP;
}
}

/**
* Returns the current {@code fabAnimationMode}, either {@link #FAB_ANIMATION_MODE_SCALE} or
* {@link #FAB_ANIMATION_MODE_SLIDE}.
*/
@FabAnimationMode
public int getFabAnimationMode() {
return fabAnimationMode;
}

/**
* Sets the current fabAlignmentMode. Determines which animation will be played when the fab is
* animated from from one {@link FabAlignmentMode} to another.
* Sets the current {@code fabAnimationMode}.
*
* @param fabAnimationMode the desired fabAlignmentMode, either {@link #FAB_ALIGNMENT_MODE_CENTER}
* or {@link #FAB_ALIGNMENT_MODE_END}.
Expand All @@ -441,7 +501,10 @@ public float getFabCradleMargin() {
}

/**
* Sets the cradle margin for the fab cutout. This is the space between the fab and the cutout.
* Sets the cradle margin for the fab cutout.
*
* This is the space between the fab and the cutout. If
* the fab anchor mode is not cradled, this will not be respected.
*/
public void setFabCradleMargin(@Dimension float cradleMargin) {
if (cradleMargin != getFabCradleMargin()) {
Expand All @@ -450,13 +513,19 @@ public void setFabCradleMargin(@Dimension float cradleMargin) {
}
}

/** Returns the rounded corner radius for the cutout. A value of 0 will be a sharp edge. */
/**
* Returns the rounded corner radius for the cutout if it exists. A value of 0 will be a
* sharp edge.
*/
@Dimension
public float getFabCradleRoundedCornerRadius() {
return getTopEdgeTreatment().getFabCradleRoundedCornerRadius();
}

/** Sets the rounded corner radius for the fab cutout. A value of 0 will be a sharp edge. */
/**
* Sets the rounded corner radius for the fab cutout. A value of 0 will be a sharp edge.
* This will not be visible until there is a cradle.
*/
public void setFabCradleRoundedCornerRadius(@Dimension float roundedCornerRadius) {
if (roundedCornerRadius != getFabCradleRoundedCornerRadius()) {
getTopEdgeTreatment().setFabCradleRoundedCornerRadius(roundedCornerRadius);
Expand All @@ -477,12 +546,13 @@ public float getCradleVerticalOffset() {
* Sets the vertical offset, in pixels, of the {@link FloatingActionButton} being cradled. An
* offset of 0 indicates the vertical center of the {@link FloatingActionButton} is positioned on
* the top edge.
* This will not be visible until there is a cradle.
*/
public void setCradleVerticalOffset(@Dimension float verticalOffset) {
if (verticalOffset != getCradleVerticalOffset()) {
getTopEdgeTreatment().setCradleVerticalOffset(verticalOffset);
materialShapeDrawable.invalidateSelf();
setCutoutState();
setCutoutStateAndTranslateFab();
}
}

Expand Down Expand Up @@ -629,7 +699,7 @@ private void dispatchAnimationEnd() {

/**
* Sets the fab diameter. This will be called automatically by the {@link BottomAppBar.Behavior}
* if the fab is anchored to this {@link BottomAppBar}.
* if the fab is anchored to this {@link BottomAppBar}..
*/
boolean setFabDiameter(@Px int diameter) {
if (diameter != getTopEdgeTreatment().getFabDiameter()) {
Expand Down Expand Up @@ -871,7 +941,10 @@ public void onAnimationEnd(Animator animation) {
}

private float getFabTranslationY() {
return -getTopEdgeTreatment().getCradleVerticalOffset();
if (fabAnchorMode == FAB_ANCHOR_MODE_CRADLE) {
return -getTopEdgeTreatment().getCradleVerticalOffset();
}
return 0;
}

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

setCutoutState();
setCutoutStateAndTranslateFab();
}

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

private void setCutoutState() {
private void setCutoutStateAndTranslateFab() {
// Layout all elements related to the positioning of the fab.
getTopEdgeTreatment().setHorizontalOffset(getFabTranslationX());
materialShapeDrawable.setInterpolation(
fabAttached && isFabVisibleOrWillBeShown() && fabAnchorMode == FAB_ANCHOR_MODE_CRADLE
? 1
: 0);
View fab = findDependentView();
materialShapeDrawable.setInterpolation(fabAttached && isFabVisibleOrWillBeShown() ? 1 : 0);
if (fab != null) {
fab.setTranslationY(getFabTranslationY());
fab.setTranslationX(getFabTranslationX());
Expand Down Expand Up @@ -1164,10 +1240,12 @@ public void onLayoutChange(
// Extra padding is added for the fake shadow on API < 21. Ensure we don't add too
// much space by removing that extra padding.
int bottomShadowPadding = (fab.getMeasuredHeight() - height) / 2;
int bottomMargin =
child
.getResources()
.getDimensionPixelOffset(R.dimen.mtrl_bottomappbar_fab_bottom_margin);
int bottomMargin = 0;
if (child.fabAnchorMode == FAB_ANCHOR_MODE_CRADLE) {
bottomMargin = child
.getResources()
.getDimensionPixelOffset(R.dimen.mtrl_bottomappbar_fab_bottom_margin);
}
// Should be moved above the bottom insets with space ignoring any shadow padding.
int minBottomMargin = bottomMargin - bottomShadowPadding;
fabLayoutParams.bottomMargin = child.getBottomInset() + minBottomMargin;
Expand Down Expand Up @@ -1201,12 +1279,12 @@ public boolean onLayoutChild(
if (dependentView != null && !ViewCompat.isLaidOut(dependentView)) {
// Set the initial position of the FloatingActionButton with the BottomAppBar vertical
// offset.
CoordinatorLayout.LayoutParams fabLayoutParams =
(CoordinatorLayout.LayoutParams) dependentView.getLayoutParams();
fabLayoutParams.anchorGravity = Gravity.CENTER | Gravity.TOP;
updateFabAnchorGravity(child, dependentView);

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

if (dependentView instanceof FloatingActionButton) {
Expand All @@ -1230,7 +1308,7 @@ public boolean onLayoutChild(
}

// Move the fab to the correct position
child.setCutoutState();
child.setCutoutStateAndTranslateFab();
}

// Now let the CoordinatorLayout lay out the BAB
Expand Down
Expand Up @@ -18,6 +18,7 @@
<public name="bottomAppBarStyle" type="attr"/>
<public name="fabAlignmentMode" type="attr"/>
<public name="fabAnimationMode" type="attr"/>
<public name="fabAnchorMode" type="attr"/>
<public name="fabCradleMargin" type="attr"/>
<public name="fabCradleRoundedCornerRadius" type="attr"/>
<public name="fabCradleVerticalOffset" type="attr"/>
Expand Down
Expand Up @@ -30,6 +30,13 @@
<!-- Mode that aligns the fab to the end. -->
<enum name="end" value="1"/>
</attr>
<!-- The anchor mode of the fab relative to the BottomAppBar. -->
<attr name="fabAnchorMode">
<!-- Mode that anchors the fab embedded inside the BottomAppBar. -->
<enum name="embed" value="0"/>
<!-- Mode that anchors the fab to be cradled within the top edge of the BottomAppBar. -->
<enum name="cradle" value="1"/>
</attr>
<!-- The animation mode that should be used when the fab animates between alignment modes. -->
<attr name="fabAnimationMode">
<!-- Mode that scales the fab down to a point, moves it, then scales the fab back to its normal size. -->
Expand Down
Expand Up @@ -53,6 +53,7 @@

<style name="Widget.Material3.BottomAppBar" parent="Widget.MaterialComponents.BottomAppBar">
<item name="fabAnimationMode">slide</item>
<item name="fabAnchorMode">cradle</item>
<item name="fabCradleMargin">@dimen/m3_bottomappbar_fab_cradle_margin</item>
<item name="fabCradleRoundedCornerRadius">
@dimen/m3_bottomappbar_fab_cradle_rounded_corner_radius
Expand Down

0 comments on commit cab45dd

Please sign in to comment.