Skip to content

Commit 9c33476

Browse files
imhappipaulfthomas
authored andcommitted
[A11y] Move responsibility of disabling hide on scroll to HideViewOnScrollBehavior and BottomAppBar
PiperOrigin-RevId: 738068009
1 parent d54087e commit 9c33476

File tree

8 files changed

+169
-51
lines changed

8 files changed

+169
-51
lines changed

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,11 +150,10 @@ public View onCreateDemoView(
150150
if (VERSION.SDK_INT >= VERSION_CODES.M) {
151151
am = getContext().getSystemService(AccessibilityManager.class);
152152
if (am != null && am.isTouchExplorationEnabled()) {
153-
bar.setHideOnScroll(false);
154153
bar.post(() -> content.setPadding(0, content.getPaddingTop(), 0, bar.getMeasuredHeight()));
155154
}
156155
}
157-
156+
158157
setUpDemoControls(view);
159158
setUpBottomAppBarShapeAppearance();
160159
return view;

catalog/java/io/material/catalog/dockedtoolbar/DockedToolbarMainDemoFragment.java

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,6 @@
3333
import androidx.annotation.MenuRes;
3434
import androidx.annotation.NonNull;
3535
import androidx.annotation.Nullable;
36-
import androidx.coordinatorlayout.widget.CoordinatorLayout;
37-
import androidx.coordinatorlayout.widget.CoordinatorLayout.Behavior;
3836
import com.google.android.material.dockedtoolbar.DockedToolbarLayout;
3937
import com.google.android.material.snackbar.Snackbar;
4038
import io.material.catalog.feature.DemoFragment;
@@ -43,7 +41,6 @@
4341
public class DockedToolbarMainDemoFragment extends DemoFragment {
4442

4543
private DockedToolbarLayout dockedToolbar;
46-
@Nullable private Behavior<View> behavior;
4744

4845
@NonNull
4946
@Override
@@ -55,9 +52,6 @@ public View onCreateDemoView(
5552
View view = layoutInflater.inflate(getLayoutResId(), viewGroup, /* attachToRoot= */ false);
5653
Toolbar toolbar = view.findViewById(R.id.toolbar);
5754
dockedToolbar = view.findViewById(R.id.docked_toolbar);
58-
behavior =
59-
(CoordinatorLayout.Behavior<View>)
60-
((CoordinatorLayout.LayoutParams) dockedToolbar.getLayoutParams()).getBehavior();
6155
((AppCompatActivity) requireActivity()).setSupportActionBar(toolbar);
6256

6357
Button leftArrowButton = view.findViewById(R.id.docked_toolbar_left_arrow_button);
@@ -78,18 +72,17 @@ public View onCreateDemoView(
7872
if (VERSION.SDK_INT >= VERSION_CODES.M) {
7973
AccessibilityManager am = getContext().getSystemService(AccessibilityManager.class);
8074
if (am != null) {
81-
am.addTouchExplorationStateChangeListener(enabled -> updateScrollBehaviorWithTalkback(bodyContainer, enabled));
75+
am.addTouchExplorationStateChangeListener(enabled -> updateContentPaddingOnTalkback(bodyContainer, enabled));
8276
if (am.isTouchExplorationEnabled()) {
83-
updateScrollBehaviorWithTalkback(bodyContainer, /* talkbackEnabled= */ true);
77+
updateContentPaddingOnTalkback(bodyContainer, /* talkbackEnabled= */ true);
8478
}
8579
}
8680
}
8781

8882
return view;
8983
}
9084

91-
private void updateScrollBehaviorWithTalkback(View content, boolean talkbackEnabled) {
92-
((CoordinatorLayout.LayoutParams) dockedToolbar.getLayoutParams()).setBehavior(talkbackEnabled ? null : behavior);
85+
private void updateContentPaddingOnTalkback(View content, boolean talkbackEnabled) {
9386
dockedToolbar.post(
9487
() ->
9588
content.setPadding(

catalog/java/io/material/catalog/floatingtoolbar/FloatingToolbarMainDemoFragment.java

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -20,23 +20,18 @@
2020
import android.graphics.Color;
2121
import android.graphics.Paint;
2222
import android.graphics.Typeface;
23-
import android.os.Build.VERSION;
24-
import android.os.Build.VERSION_CODES;
2523
import android.os.Bundle;
2624
import androidx.appcompat.app.AppCompatActivity;
2725
import androidx.appcompat.widget.Toolbar;
2826
import android.view.LayoutInflater;
2927
import android.view.View;
3028
import android.view.ViewGroup;
31-
import android.view.accessibility.AccessibilityManager;
3229
import android.widget.TextView;
3330
import androidx.annotation.ColorInt;
3431
import androidx.annotation.IdRes;
3532
import androidx.annotation.LayoutRes;
3633
import androidx.annotation.NonNull;
3734
import androidx.annotation.Nullable;
38-
import androidx.coordinatorlayout.widget.CoordinatorLayout;
39-
import com.google.android.material.behavior.HideViewOnScrollBehavior;
4035
import com.google.android.material.button.MaterialButton;
4136
import com.google.android.material.floatingtoolbar.FloatingToolbarLayout;
4237
import io.material.catalog.feature.DemoFragment;
@@ -149,28 +144,9 @@ public View onCreateDemoView(
149144

150145
// Select bottom configuration button to represent the toolbar that's initially visible.
151146
view.findViewById(R.id.bottom_button).performClick();
152-
153-
if (VERSION.SDK_INT >= VERSION_CODES.M) {
154-
AccessibilityManager am = getContext().getSystemService(AccessibilityManager.class);
155-
if (am != null) {
156-
am.addTouchExplorationStateChangeListener(enabled -> updateScrollBehaviorOnTalkback(floatingToolbars, enabled));
157-
if (am.isTouchExplorationEnabled()) {
158-
updateScrollBehaviorOnTalkback(floatingToolbars, /* talkbackEnabled= */ true);
159-
}
160-
}
161-
}
162-
163147
return view;
164148
}
165149

166-
private void updateScrollBehaviorOnTalkback(
167-
@NonNull List<FloatingToolbarLayout> floatingToolbars, boolean talkbackEnabled) {
168-
for (FloatingToolbarLayout floatingToolbar : floatingToolbars) {
169-
((CoordinatorLayout.LayoutParams) floatingToolbar.getLayoutParams())
170-
.setBehavior(talkbackEnabled ? null : new HideViewOnScrollBehavior<>());
171-
}
172-
}
173-
174150
private void initializeOrientationButton(
175151
@NonNull View view,
176152
@NonNull List<FloatingToolbarLayout> floatingToolbars,

docs/components/BottomAppBar.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,11 +76,13 @@ in the menu:
7676
Bottom app bar can optionally hide on scroll with the `app:hideOnScroll`
7777
attribute. When this attribute is set to true, scrolling will hide the bottom
7878
app bar and prevent it from being seen by any screen readers which may be
79-
confusing for users.
79+
confusing for users. To prevent this, the hide behavior is automatically
80+
disabled when Talkback is enabled. Although discouraged for accessibility, you
81+
can optionally force the hide behavior by calling
82+
`bottomAppBar.disableHideOnTouchExploration(false)`.
8083

81-
When Talkback is enabled, this behavior should be disabled by setting
82-
`bottomAppBar.setHideOnScroll(false)`. Additionally, disabling this behavior
83-
causes any content to be obscured, make sure to add the appropriate bottom
84+
Depending on your layout, disabling the hide behavior may potentially cause
85+
content to be obscured behind the bar. Make sure to add the appropriate bottom
8486
padding of the height of the bottom app bar to the content. See below for an
8587
example:
8688

docs/components/DockedToolbar.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,35 @@ can set these accessibility flags like below:
183183
</com.google.android.material.dockedtoolbar.DockedToolbarLayout>
184184
```
185185

186+
#### Talkback
187+
188+
Docked toolbars can optionally use the `CoordinatorLayout.Behavior`
189+
`HideViewOnScrollBehavior` to hide the docked toolbar on scroll. This behavior
190+
is disabled when Talkback is enabled disabled due to screen readers not being
191+
able to see it if the docked toolbar is hidden when scrolled.
192+
193+
If using a docked toolbar in a layout that obscures any content when
194+
hide on scroll is disabled, make sure to add the appropriate padding to the
195+
content. For example, if the docked toolbar is on the bottom and it is
196+
obscuring the content, bottom padding should be added to the content.
197+
198+
See below for an example:
199+
200+
```
201+
val am = context.getSystemService(AccessibilityManager::class.java)
202+
if (am != null && am.isTouchExplorationEnabled) {
203+
(bar.layoutParams as? CoordinatorLayout.LayoutParams)?.behavior = null
204+
bar.post {
205+
content.setPadding(
206+
content.paddingLeft,
207+
content.paddingTop,
208+
content.paddingRight,
209+
content.paddingBottom + bar.measuredHeight
210+
)
211+
}
212+
}
213+
```
214+
186215
### Anatomy and key properties
187216

188217
The following is an anatomy diagram for the docked toolbar:

docs/components/FloatingToolbar.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -178,10 +178,10 @@ android:id="@+id/floating_toolbar"
178178

179179
#### Talkback
180180

181-
If optionally using the `CoordinatorLayout.Behavior` `HideViewOnScrollBehavior`
182-
to hide the floating toolbar on scroll, make sure to disable it when Talkback
183-
is enabled. Screen readers such as Talkback will not be able to see it if the
184-
floating toolbar is hidden when scrolled.
181+
Floating toolbars can optionally use the `CoordinatorLayout.Behavior`
182+
`HideViewOnScrollBehavior` to hide the floating toolbar on scroll. This behavior
183+
is disabled when Talkback is enabled disabled due to screen readers not being
184+
able to see it if the floating toolbar is hidden when scrolled.
185185

186186
If using a Floating toolbar in a layout that obscures any content when
187187
hide on scroll is disabled, make sure to add the appropriate padding to the
@@ -235,7 +235,9 @@ The following is an anatomy diagram for the floating toolbar:
235235
Standard style theme attribute: `?attr/floatingToolbarStyle`
236236
Vibrant style theme attribute: `?attr/floatingToolbarVibrantStyle`
237237

238-
Floating toolbar also provides specific styles for icon buttons, `Widget.Material3.FloatingToolbar.IconButton` and `Widget.Material3.FloatingToolbar.IconButton.Vibrant`.
238+
Floating toolbar also provides specific styles for icon buttons,
239+
`Widget.Material3.FloatingToolbar.IconButton` and
240+
`Widget.Material3.FloatingToolbar.IconButton.Vibrant`.
239241

240242
See the full list of
241243
[styles](https://github.com/material-components/material-components-android/tree/master/lib/java/com/google/android/material/floatingtoolbar/res/values/styles.xml) and

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

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,19 @@
1919
import com.google.android.material.R;
2020

2121
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
22+
import static androidx.core.content.ContextCompat.getSystemService;
2223

2324
import android.animation.Animator;
2425
import android.animation.AnimatorListenerAdapter;
2526
import android.animation.TimeInterpolator;
2627
import android.content.Context;
2728
import android.util.AttributeSet;
2829
import android.view.View;
30+
import android.view.View.OnAttachStateChangeListener;
2931
import android.view.ViewGroup;
3032
import android.view.ViewPropertyAnimator;
33+
import android.view.accessibility.AccessibilityManager;
34+
import android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener;
3135
import androidx.annotation.Dimension;
3236
import androidx.annotation.IntDef;
3337
import androidx.annotation.NonNull;
@@ -44,9 +48,9 @@
4448
* The {@link Behavior} for a View within a {@link CoordinatorLayout} to hide the view off the
4549
* bottom of the screen when scrolling down, and show it when scrolling up.
4650
*
47-
* <p> If Talkback is enabled, the hide on scroll behavior should be disabled until Talkback
48-
* is disabled. Ensure that the content is not obscured due to disabling this behavior by
49-
* adding padding to the content.
51+
* <p>If Touch Exploration is enabled, the hide on scroll behavior should be disabled until Touch
52+
* Exploration is disabled. Ensure that the content is not obscured due to disabling this behavior
53+
* by adding padding to the content.
5054
*
5155
* @deprecated Use {@link HideViewOnScrollBehavior} instead.
5256
* <p>TODO(b/378132394): Migrate usages of this class to {@link HideViewOnScrollBehavior}.
@@ -92,6 +96,11 @@ public interface OnScrollStateChangedListener {
9296

9397
private int height = 0;
9498

99+
private AccessibilityManager accessibilityManager;
100+
private TouchExplorationStateChangeListener touchExplorationListener;
101+
102+
private boolean disableOnTouchExploration = true;
103+
95104
/**
96105
* Positions the scroll state can be set to.
97106
*
@@ -133,9 +142,39 @@ public boolean onLayoutChild(
133142
child.getContext(),
134143
ENTER_EXIT_ANIM_EASING_ATTR,
135144
AnimationUtils.FAST_OUT_LINEAR_IN_INTERPOLATOR);
145+
disableIfTouchExplorationEnabled(child);
136146
return super.onLayoutChild(parent, child, layoutDirection);
137147
}
138148

149+
private void disableIfTouchExplorationEnabled(V child) {
150+
if (accessibilityManager == null) {
151+
accessibilityManager = getSystemService(child.getContext(), AccessibilityManager.class);
152+
}
153+
if (accessibilityManager != null && touchExplorationListener == null) {
154+
touchExplorationListener =
155+
enabled -> {
156+
if (enabled && isScrolledDown()) {
157+
slideUp(child);
158+
}
159+
};
160+
accessibilityManager.addTouchExplorationStateChangeListener(touchExplorationListener);
161+
child.addOnAttachStateChangeListener(
162+
new OnAttachStateChangeListener() {
163+
@Override
164+
public void onViewAttachedToWindow(@NonNull View v) {}
165+
166+
@Override
167+
public void onViewDetachedFromWindow(@NonNull View v) {
168+
if (touchExplorationListener != null && accessibilityManager != null) {
169+
accessibilityManager.removeTouchExplorationStateChangeListener(
170+
touchExplorationListener);
171+
touchExplorationListener = null;
172+
}
173+
}
174+
});
175+
}
176+
}
177+
139178
/**
140179
* Sets an additional offset for the y position used to hide the view.
141180
*
@@ -240,6 +279,13 @@ public void slideDown(@NonNull V child, boolean animate) {
240279
return;
241280
}
242281

282+
// If Touch Exploration is on, we should disable sliding down due to a11y issues.
283+
if (disableOnTouchExploration
284+
&& accessibilityManager != null
285+
&& accessibilityManager.isTouchExplorationEnabled()) {
286+
return;
287+
}
288+
243289
if (currentAnimator != null) {
244290
currentAnimator.cancel();
245291
child.clearAnimation();
@@ -299,4 +345,14 @@ public void removeOnScrollStateChangedListener(@NonNull OnScrollStateChangedList
299345
public void clearOnScrollStateChangedListeners() {
300346
onScrollStateChangedListeners.clear();
301347
}
348+
349+
/** Sets whether or not to disable this behavior if touch exploration is enabled. */
350+
public void disableOnTouchExploration(boolean disableOnTouchExploration) {
351+
this.disableOnTouchExploration = disableOnTouchExploration;
352+
}
353+
354+
/** Returns whether or not this behavior is disabled if touch exploration is enabled. */
355+
public boolean isDisabledOnTouchExploration() {
356+
return disableOnTouchExploration;
357+
}
302358
}

0 commit comments

Comments
 (0)