Skip to content

Commit cb71e2d

Browse files
cketchammelaniegoetz
authored andcommitted
Update BottomAppBar hideOnScroll offset to account for the shadow drawn at the top of the view.
PiperOrigin-RevId: 250562675
1 parent cbdd8f6 commit cb71e2d

File tree

5 files changed

+149
-25
lines changed

5 files changed

+149
-25
lines changed

lib/java/com/google/android/material/behavior/HideBottomViewOnScrollBehavior.java

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ public class HideBottomViewOnScrollBehavior<V extends View> extends CoordinatorL
4343

4444
private int height = 0;
4545
private int currentState = STATE_SCROLLED_UP;
46+
private int additionalHiddenOffsetY = 0;
4647
private ViewPropertyAnimator currentAnimator;
4748

4849
public HideBottomViewOnScrollBehavior() {}
@@ -59,6 +60,15 @@ public boolean onLayoutChild(CoordinatorLayout parent, V child, int layoutDirect
5960
return super.onLayoutChild(parent, child, layoutDirection);
6061
}
6162

63+
/** Sets an additional offset for the y position used to hide the view. */
64+
public void setAdditionalHiddenOffsetY(V child, int offset) {
65+
additionalHiddenOffsetY = offset;
66+
67+
if (currentState == STATE_SCROLLED_DOWN) {
68+
child.setTranslationY(height + additionalHiddenOffsetY);
69+
}
70+
}
71+
6272
@Override
6373
public boolean onStartNestedScroll(
6474
CoordinatorLayout coordinatorLayout,
@@ -78,9 +88,9 @@ public void onNestedScroll(
7888
int dyConsumed,
7989
int dxUnconsumed,
8090
int dyUnconsumed) {
81-
if (currentState != STATE_SCROLLED_DOWN && dyConsumed > 0) {
91+
if (dyConsumed > 0) {
8292
slideDown(child);
83-
} else if (currentState != STATE_SCROLLED_UP && dyConsumed < 0) {
93+
} else if (dyConsumed < 0) {
8494
slideUp(child);
8595
}
8696
}
@@ -90,6 +100,10 @@ public void onNestedScroll(
90100
* screen.
91101
*/
92102
public void slideUp(V child) {
103+
if (currentState == STATE_SCROLLED_UP) {
104+
return;
105+
}
106+
93107
if (currentAnimator != null) {
94108
currentAnimator.cancel();
95109
child.clearAnimation();
@@ -104,13 +118,20 @@ public void slideUp(V child) {
104118
* screen.
105119
*/
106120
public void slideDown(V child) {
121+
if (currentState == STATE_SCROLLED_DOWN) {
122+
return;
123+
}
124+
107125
if (currentAnimator != null) {
108126
currentAnimator.cancel();
109127
child.clearAnimation();
110128
}
111129
currentState = STATE_SCROLLED_DOWN;
112130
animateChildTo(
113-
child, height, EXIT_ANIMATION_DURATION, AnimationUtils.FAST_OUT_LINEAR_IN_INTERPOLATOR);
131+
child,
132+
height + additionalHiddenOffsetY,
133+
EXIT_ANIMATION_DURATION,
134+
AnimationUtils.FAST_OUT_LINEAR_IN_INTERPOLATOR);
114135
}
115136

116137
private void animateChildTo(V child, int targetY, long duration, TimeInterpolator interpolator) {

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

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -369,9 +369,23 @@ public void setHideOnScroll(boolean hide) {
369369
hideOnScroll = hide;
370370
}
371371

372+
/** Animates the {@link BottomAppBar} so it hides off the screen. */
373+
public void performHide() {
374+
getBehavior().slideDown(this);
375+
}
376+
377+
/** Animates the {@link BottomAppBar} so it is shown on the screen. */
378+
public void performShow() {
379+
getBehavior().slideUp(this);
380+
}
381+
372382
@Override
373383
public void setElevation(float elevation) {
374384
materialShapeDrawable.setElevation(elevation);
385+
// Make sure the shadow isn't shown if this view slides down with hideOnScroll.
386+
int topShadowHeight =
387+
materialShapeDrawable.getShadowRadius() - materialShapeDrawable.getShadowOffsetY();
388+
getBehavior().setAdditionalHiddenOffsetY(this, topShadowHeight);
375389
}
376390

377391
/**
@@ -788,7 +802,7 @@ public void setSubtitle(CharSequence subtitle) {
788802

789803
@NonNull
790804
@Override
791-
public CoordinatorLayout.Behavior<BottomAppBar> getBehavior() {
805+
public Behavior getBehavior() {
792806
if (behavior == null) {
793807
behavior = new Behavior();
794808
}

lib/java/com/google/android/material/shape/MaterialShapeDrawable.java

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -745,12 +745,10 @@ public void setShadowCompatRotation(int shadowRotation) {
745745
}
746746

747747
/**
748-
* Get the shadow radius rendered by the path.
749-
*
750-
* @return the shadow radius rendered by the path.
751-
* @deprecated use {@link #getElevation()} instead.
748+
* Get the shadow radius rendered by the path in pixels. This method should be used only when the
749+
* actual size of the shadow is required. Usually {@link getElevation()} should be used instead to
750+
* get the actual elevation of this view as it might be different.
752751
*/
753-
@Deprecated
754752
public int getShadowRadius() {
755753
return drawableState.shadowCompatRadius;
756754
}
@@ -990,14 +988,8 @@ private void drawStrokeShape(Canvas canvas) {
990988

991989
private void prepareCanvasForShadow(Canvas canvas) {
992990
// Calculate the translation to offset the canvas for the given offset and rotation.
993-
int shadowOffsetX =
994-
(int)
995-
(drawableState.shadowCompatOffset
996-
* Math.sin(Math.toRadians(drawableState.shadowCompatRotation)));
997-
int shadowOffsetY =
998-
(int)
999-
(drawableState.shadowCompatOffset
1000-
* Math.cos(Math.toRadians(drawableState.shadowCompatRotation)));
991+
int shadowOffsetX = getShadowOffsetX();
992+
int shadowOffsetY = getShadowOffsetY();
1001993

1002994
// We only handle clipping as a convenience for older apis where we are trying to seamlessly
1003995
// provide fake shadows. On newer versions of android, we require that the parent is set so that
@@ -1034,20 +1026,28 @@ private void drawCompatShadow(Canvas canvas) {
10341026
edgeShadowOperation[index].draw(shadowRenderer, drawableState.shadowCompatRadius, canvas);
10351027
}
10361028

1037-
int shadowOffsetX =
1038-
(int)
1039-
(drawableState.shadowCompatOffset
1040-
* Math.sin(Math.toRadians(drawableState.shadowCompatRotation)));
1041-
int shadowOffsetY =
1042-
(int)
1043-
(drawableState.shadowCompatOffset
1044-
* Math.cos(Math.toRadians(drawableState.shadowCompatRotation)));
1029+
int shadowOffsetX = getShadowOffsetX();
1030+
int shadowOffsetY = getShadowOffsetY();
10451031

10461032
canvas.translate(-shadowOffsetX, -shadowOffsetY);
10471033
canvas.drawPath(path, clearPaint);
10481034
canvas.translate(shadowOffsetX, shadowOffsetY);
10491035
}
10501036

1037+
/** Returns the X offset of the shadow from the bounds of the shape. */
1038+
public int getShadowOffsetX() {
1039+
return (int)
1040+
(drawableState.shadowCompatOffset
1041+
* Math.sin(Math.toRadians(drawableState.shadowCompatRotation)));
1042+
}
1043+
1044+
/** Returns the Y offset of the shadow from the bounds of the shape. */
1045+
public int getShadowOffsetY() {
1046+
return (int)
1047+
(drawableState.shadowCompatOffset
1048+
* Math.cos(Math.toRadians(drawableState.shadowCompatRotation)));
1049+
}
1050+
10511051
/** @deprecated see {@link ShapeAppearancePathProvider} */
10521052
@Deprecated
10531053
public void getPathForSize(int width, int height, Path path) {
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<!--
3+
Copyright (C) 2019 The Android Open Source Project
4+
5+
Licensed under the Apache License, Version 2.0 (the "License");
6+
you may not use this file except in compliance with the License.
7+
You may obtain a copy of the License at
8+
9+
http://www.apache.org/licenses/LICENSE-2.0
10+
11+
Unless required by applicable law or agreed to in writing, software
12+
distributed under the License is distributed on an "AS IS" BASIS,
13+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
See the License for the specific language governing permissions and
15+
limitations under the License.
16+
-->
17+
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
18+
xmlns:tools="http://schemas.android.com/tools"
19+
package="com.google.android.material.bottomappbar">
20+
21+
<uses-sdk
22+
tools:overrideLibrary="androidx.test, android.app, androidx.test.rule,
23+
androidx.test.espresso, androidx.test.espresso.idling"/>
24+
25+
<application>
26+
<uses-library android:name="android.test.runner"/>
27+
</application>
28+
29+
<instrumentation
30+
android:name="androidx.test.runner.AndroidJUnitRunner"
31+
android:targetPackage="com.google.android.material.testapp"/>
32+
</manifest>
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* Copyright (C) 2019 The Android Open Source Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.android.material.bottomappbar;
18+
19+
import static org.junit.Assert.assertTrue;
20+
21+
import androidx.appcompat.app.AppCompatActivity;
22+
import androidx.test.filters.SmallTest;
23+
import androidx.test.rule.ActivityTestRule;
24+
import androidx.test.runner.AndroidJUnit4;
25+
import org.junit.Before;
26+
import org.junit.Rule;
27+
import org.junit.Test;
28+
import org.junit.runner.RunWith;
29+
30+
@SmallTest
31+
@RunWith(AndroidJUnit4.class)
32+
public class BottomAppBarBehaviorTest {
33+
34+
@Rule
35+
public final ActivityTestRule<AppCompatActivity> activityTestRule =
36+
new ActivityTestRule<>(AppCompatActivity.class);
37+
38+
BottomAppBar bar;
39+
40+
@Before
41+
public void createBottomAppBar() {
42+
bar = new BottomAppBar(activityTestRule.getActivity());
43+
}
44+
45+
@Test
46+
public void testMovedDown_elevationIncreasedWhileHidden() throws Throwable {
47+
bar.setElevation(10);
48+
bar.performHide();
49+
float originalYTranslation = bar.getTranslationY();
50+
51+
bar.setElevation(30);
52+
53+
assertTrue(
54+
"The bar should have bee moved further down to hide the larger shadow.",
55+
bar.getTranslationY() > originalYTranslation);
56+
}
57+
}

0 commit comments

Comments
 (0)