diff --git a/docs/components/TopAppBar.md b/docs/components/TopAppBar.md
index bc6f3638d41..7a0a208c048 100644
--- a/docs/components/TopAppBar.md
+++ b/docs/components/TopAppBar.md
@@ -373,6 +373,7 @@ Element | Attribute
**`CollapsingToolbarLayout` scrim animation duration** | `app:scrimAnimationDuration` | `setScrimAnimationDuration`
`getScrimAnimationDuration` | `600`
**`CollapsingToolbarLayout` collapsing animation interpolator** | `app:titlePositionInterpolator` | `setTitlePositionInterpolator` | `@null`
**`AppBarLayout` lift on scroll** | `app:liftOnScroll` | `setLiftOnScroll`
`isLiftOnScroll` | `true`
+**`AppBarLayout` lift on scroll color** | `app:liftOnScrollColor` | N/A | `@null` (defaults to elevation overlay color)
**`AppBarLayout` lift on scroll target view** | `app:liftOnScrollTargetViewId` | `setLiftOnScrollTargetViewId`
`getLiftOnScrollTargetViewId` | `@null`
#### `AppBarLayout` styles
diff --git a/lib/java/com/google/android/material/appbar/AppBarLayout.java b/lib/java/com/google/android/material/appbar/AppBarLayout.java
index 9b251000a32..2c95e68badb 100644
--- a/lib/java/com/google/android/material/appbar/AppBarLayout.java
+++ b/lib/java/com/google/android/material/appbar/AppBarLayout.java
@@ -74,6 +74,7 @@
import com.google.android.material.animation.AnimationUtils;
import com.google.android.material.appbar.AppBarLayout.BaseBehavior.SavedState;
import com.google.android.material.internal.ThemeEnforcement;
+import com.google.android.material.resources.MaterialResources;
import com.google.android.material.shape.MaterialShapeDrawable;
import com.google.android.material.shape.MaterialShapeUtils;
import java.lang.annotation.Retention;
@@ -202,13 +203,17 @@ public interface LiftOnScrollListener {
private boolean liftOnScroll;
@IdRes private int liftOnScrollTargetViewId;
@Nullable private WeakReference liftOnScrollTargetView;
- @Nullable private ValueAnimator elevationOverlayAnimator;
+ @Nullable private final ColorStateList liftOnScrollColor;
+ @Nullable private ValueAnimator liftOnScrollColorAnimator;
+ @Nullable private AnimatorUpdateListener liftOnScrollColorUpdateListener;
private final List liftOnScrollListeners = new ArrayList<>();
private int[] tmpStatesArray;
@Nullable private Drawable statusBarForeground;
+ private final float appBarElevation;
+
private Behavior behavior;
public AppBarLayout(@NonNull Context context) {
@@ -243,11 +248,21 @@ public AppBarLayout(@NonNull Context context, @Nullable AttributeSet attrs, int
ViewCompat.setBackground(this, a.getDrawable(R.styleable.AppBarLayout_android_background));
+ liftOnScrollColor =
+ MaterialResources.getColorStateList(
+ context, a, R.styleable.AppBarLayout_liftOnScrollColor);
+
if (getBackground() instanceof ColorDrawable) {
ColorDrawable background = (ColorDrawable) getBackground();
MaterialShapeDrawable materialShapeDrawable = new MaterialShapeDrawable();
materialShapeDrawable.setFillColor(ColorStateList.valueOf(background.getColor()));
- materialShapeDrawable.initializeElevationOverlay(context);
+ // If there is a lift on scroll color specified, we do not initialize the elevation overlay
+ // and set the alpha to zero manually.
+ if (liftOnScrollColor != null) {
+ initializeLiftOnScrollWithColor(materialShapeDrawable);
+ } else {
+ initializeLiftOnScrollWithElevation(context, materialShapeDrawable);
+ }
ViewCompat.setBackground(this, materialShapeDrawable);
}
@@ -276,6 +291,9 @@ public AppBarLayout(@NonNull Context context, @Nullable AttributeSet attrs, int
}
}
+ // TODO(b/249786834): This should be a customizable attribute.
+ appBarElevation = getResources().getDimension(R.dimen.design_appbar_elevation);
+
liftOnScroll = a.getBoolean(R.styleable.AppBarLayout_liftOnScroll, false);
liftOnScrollTargetViewId =
a.getResourceId(R.styleable.AppBarLayout_liftOnScrollTargetViewId, View.NO_ID);
@@ -293,6 +311,37 @@ public WindowInsetsCompat onApplyWindowInsets(View v, WindowInsetsCompat insets)
});
}
+ private void initializeLiftOnScrollWithColor(MaterialShapeDrawable background) {
+ background.setAlpha(lifted ? 255 : 0);
+ background.setFillColor(liftOnScrollColor);
+ liftOnScrollColorUpdateListener = valueAnimator -> {
+ float alpha = (float) valueAnimator.getAnimatedValue();
+ background.setAlpha((int) alpha);
+
+ for (LiftOnScrollListener liftOnScrollListener : liftOnScrollListeners) {
+ if (background.getFillColor() != null) {
+ liftOnScrollListener.onUpdate(
+ 0, background.getFillColor().withAlpha((int) alpha).getDefaultColor());
+ }
+ }
+ };
+ }
+
+ private void initializeLiftOnScrollWithElevation(
+ Context context, MaterialShapeDrawable background) {
+ background.initializeElevationOverlay(context);
+ liftOnScrollColorUpdateListener = valueAnimator -> {
+ float elevation = (float) valueAnimator.getAnimatedValue();
+ background.setElevation(elevation);
+ if (statusBarForeground instanceof MaterialShapeDrawable) {
+ ((MaterialShapeDrawable) statusBarForeground).setElevation(elevation);
+ }
+ for (LiftOnScrollListener liftOnScrollListener : liftOnScrollListeners) {
+ liftOnScrollListener.onUpdate(elevation, background.getResolvedTintColor());
+ }
+ };
+ }
+
/**
* Add a listener that will be called when the offset of this {@link AppBarLayout} changes.
*
@@ -930,42 +979,33 @@ boolean setLiftedState(boolean lifted, boolean force) {
this.lifted = lifted;
refreshDrawableState();
if (liftOnScroll && getBackground() instanceof MaterialShapeDrawable) {
- startLiftOnScrollElevationOverlayAnimation((MaterialShapeDrawable) getBackground(), lifted);
+ if (liftOnScrollColor != null) {
+ startLiftOnScrollColorAnimation(
+ lifted ? 0 : 255, lifted ? 255 : 0);
+ } else {
+ startLiftOnScrollColorAnimation(
+ lifted ? 0 : appBarElevation, lifted ? appBarElevation : 0);
+ }
}
return true;
}
return false;
}
- private void startLiftOnScrollElevationOverlayAnimation(
- @NonNull final MaterialShapeDrawable background, boolean lifted) {
- float appBarElevation = getResources().getDimension(R.dimen.design_appbar_elevation);
- float fromElevation = lifted ? 0 : appBarElevation;
- float toElevation = lifted ? appBarElevation : 0;
-
- if (elevationOverlayAnimator != null) {
- elevationOverlayAnimator.cancel();
+ private void startLiftOnScrollColorAnimation(
+ float fromValue, float toValue) {
+ if (liftOnScrollColorAnimator != null) {
+ liftOnScrollColorAnimator.cancel();
}
- elevationOverlayAnimator = ValueAnimator.ofFloat(fromElevation, toElevation);
- elevationOverlayAnimator.setDuration(
+ liftOnScrollColorAnimator = ValueAnimator.ofFloat(fromValue, toValue);
+ liftOnScrollColorAnimator.setDuration(
getResources().getInteger(R.integer.app_bar_elevation_anim_duration));
- elevationOverlayAnimator.setInterpolator(AnimationUtils.LINEAR_INTERPOLATOR);
- elevationOverlayAnimator.addUpdateListener(
- new AnimatorUpdateListener() {
- @Override
- public void onAnimationUpdate(@NonNull ValueAnimator valueAnimator) {
- float elevation = (float) valueAnimator.getAnimatedValue();
- background.setElevation(elevation);
- if (statusBarForeground instanceof MaterialShapeDrawable) {
- ((MaterialShapeDrawable) statusBarForeground).setElevation(elevation);
- }
- for (LiftOnScrollListener liftOnScrollListener : liftOnScrollListeners) {
- liftOnScrollListener.onUpdate(elevation, background.getResolvedTintColor());
- }
- }
- });
- elevationOverlayAnimator.start();
+ liftOnScrollColorAnimator.setInterpolator(AnimationUtils.LINEAR_INTERPOLATOR);
+ if (liftOnScrollColorUpdateListener != null) {
+ liftOnScrollColorAnimator.addUpdateListener(liftOnScrollColorUpdateListener);
+ }
+ liftOnScrollColorAnimator.start();
}
/**
diff --git a/lib/java/com/google/android/material/appbar/res-public/values/public.xml b/lib/java/com/google/android/material/appbar/res-public/values/public.xml
index 3731bb2795d..1b357e18594 100644
--- a/lib/java/com/google/android/material/appbar/res-public/values/public.xml
+++ b/lib/java/com/google/android/material/appbar/res-public/values/public.xml
@@ -70,6 +70,7 @@
+
diff --git a/lib/java/com/google/android/material/appbar/res/values/attrs.xml b/lib/java/com/google/android/material/appbar/res/values/attrs.xml
index 5a81d74a6dd..ed295be289b 100644
--- a/lib/java/com/google/android/material/appbar/res/values/attrs.xml
+++ b/lib/java/com/google/android/material/appbar/res/values/attrs.xml
@@ -58,6 +58,9 @@
If this id is not set, the {@link AppBarLayout} will use the target view provided
by nested scrolling to determine whether it should be lifted. -->
+
+
diff --git a/testing/java/com/google/android/material/testapp/res/layout/design_appbar_toolbar_liftonscroll_color.xml b/testing/java/com/google/android/material/testapp/res/layout/design_appbar_toolbar_liftonscroll_color.xml
new file mode 100644
index 00000000000..2b45f976e13
--- /dev/null
+++ b/testing/java/com/google/android/material/testapp/res/layout/design_appbar_toolbar_liftonscroll_color.xml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/javatests/com/google/android/material/appbar/AppBarWithToolbarTest.java b/tests/javatests/com/google/android/material/appbar/AppBarWithToolbarTest.java
index 003a4c4c1d7..e95bb5edfb1 100644
--- a/tests/javatests/com/google/android/material/appbar/AppBarWithToolbarTest.java
+++ b/tests/javatests/com/google/android/material/appbar/AppBarWithToolbarTest.java
@@ -29,6 +29,7 @@
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.MediumTest;
import androidx.test.runner.AndroidJUnit4;
+import com.google.android.material.shape.MaterialShapeDrawable;
import com.google.android.material.testapp.R;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -376,4 +377,35 @@ public void testUpdateAccessibilityActionsWithSetScrollFlags() throws Throwable
assertAccessibilityHasScrollBackwardAction(false);
assertAccessibilityScrollable(false);
}
+
+ /** Tests the lift on scroll color of the app bar layout. */
+ @Test
+ public void testLiftOnScrollColor() throws Throwable {
+ configureContent(
+ R.layout.design_appbar_toolbar_liftonscroll_color,
+ R.string.design_appbar_toolbar_scroll_tabs_pin);
+
+ final int[] appbarOnScreenXY = new int[2];
+ mAppBar.getLocationOnScreen(appbarOnScreenXY);
+
+ final int originalAppbarBottom = appbarOnScreenXY[1] + mAppBar.getHeight();
+ final int centerX = appbarOnScreenXY[0] + mAppBar.getWidth() / 2;
+
+ final int appbarHeight = mAppBar.getHeight();
+ final int longSwipeAmount = 3 * appbarHeight / 2;
+
+ assertEquals(0, ((MaterialShapeDrawable) mAppBar.getBackground()).getAlpha());
+
+ // Perform a swipe-up gesture across the horizontal center of the screen.
+ performVerticalSwipeUpGesture(
+ R.id.coordinator_layout,
+ centerX,
+ originalAppbarBottom + 3 * longSwipeAmount / 2,
+ longSwipeAmount);
+
+ assertEquals(255, ((MaterialShapeDrawable) mAppBar.getBackground()).getAlpha());
+ assertEquals(
+ mAppBar.getResources().getColor(R.color.material_blue_grey_950),
+ ((MaterialShapeDrawable) mAppBar.getBackground()).getFillColor().getDefaultColor());
+ }
}