Skip to content

Commit

Permalink
[AppBarLayout] Adding new attribute liftOnScrollColor that disables e…
Browse files Browse the repository at this point in the history
…levation overlay and allows users to change the app bar color

PiperOrigin-RevId: 477794812
  • Loading branch information
imhappi authored and pekingme committed Oct 3, 2022
1 parent 2f6ebb4 commit c587dd1
Show file tree
Hide file tree
Showing 6 changed files with 149 additions and 29 deletions.
1 change: 1 addition & 0 deletions docs/components/TopAppBar.md
Expand Up @@ -373,6 +373,7 @@ Element | Attribute
**`CollapsingToolbarLayout` scrim animation duration** | `app:scrimAnimationDuration` | `setScrimAnimationDuration`<br>`getScrimAnimationDuration` | `600`
**`CollapsingToolbarLayout` collapsing animation interpolator** | `app:titlePositionInterpolator` | `setTitlePositionInterpolator` | `@null`
**`AppBarLayout` lift on scroll** | `app:liftOnScroll` | `setLiftOnScroll`<br>`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`<br>`getLiftOnScrollTargetViewId` | `@null`

#### `AppBarLayout` styles
Expand Down
98 changes: 69 additions & 29 deletions lib/java/com/google/android/material/appbar/AppBarLayout.java
Expand Up @@ -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;
Expand Down Expand Up @@ -202,13 +203,17 @@ public interface LiftOnScrollListener {
private boolean liftOnScroll;
@IdRes private int liftOnScrollTargetViewId;
@Nullable private WeakReference<View> liftOnScrollTargetView;
@Nullable private ValueAnimator elevationOverlayAnimator;
@Nullable private final ColorStateList liftOnScrollColor;
@Nullable private ValueAnimator liftOnScrollColorAnimator;
@Nullable private AnimatorUpdateListener liftOnScrollColorUpdateListener;
private final List<LiftOnScrollListener> liftOnScrollListeners = new ArrayList<>();

private int[] tmpStatesArray;

@Nullable private Drawable statusBarForeground;

private final float appBarElevation;

private Behavior behavior;

public AppBarLayout(@NonNull Context context) {
Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -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);
Expand All @@ -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.
*
Expand Down Expand Up @@ -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();
}

/**
Expand Down
Expand Up @@ -70,6 +70,7 @@
<public name="appbar_scrolling_view_behavior" type="string"/>
<public name="expanded" type="attr"/>
<public name="liftOnScroll" type="attr"/>
<public name="liftOnScrollColor" type="attr"/>
<public name="liftOnScrollTargetViewId" type="attr"/>
<public name="collapsedTitleGravity" type="attr"/>
<public name="expandedTitleGravity" type="attr"/>
Expand Down
Expand Up @@ -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. -->
<attr name="liftOnScrollTargetViewId" format="reference"/>
<!-- The color of the app bar when lifted, if lift on scroll is enabled. If null, the color is
determined by the elevation overlay. -->
<attr name="liftOnScrollColor" format="color"/>
<!-- The drawable to display in front of the layout's content, but behind the status bar.
Only works on Lollipop when used together with android:fitSystemWindows="true". -->
<attr name="statusBarForeground" format="color"/>
Expand Down
@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2022 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->

<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">

<com.google.android.material.appbar.AppBarLayout
android:id="@+id/app_bar"
android:layout_height="wrap_content"
android:layout_width="match_parent"
app:liftOnScroll="true"
app:liftOnScrollColor="@color/material_blue_grey_950">

<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_height="?attr/actionBarSize"
android:layout_width="match_parent"
app:layout_scrollFlags="scroll|enterAlways"/>

</com.google.android.material.appbar.AppBarLayout>

<include layout="@layout/include_appbar_scrollview"/>

</androidx.coordinatorlayout.widget.CoordinatorLayout>

Expand Up @@ -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;
Expand Down Expand Up @@ -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());
}
}

0 comments on commit c587dd1

Please sign in to comment.