Skip to content

Commit 72228f4

Browse files
Material Design Teamdsn5ft
authored andcommitted
[AppBarLayout] Fix scrolling for a11y
Ensure the CoL can scroll to the bottommost elements of the scrolling child, which may not be visible due to the app bar. Also, set the node info properties to expose the CoL to auto-scrolling. PiperOrigin-RevId: 447596411
1 parent b2a3fbe commit 72228f4

File tree

4 files changed

+83
-7
lines changed

4 files changed

+83
-7
lines changed

lib/java/com/google/android/material/appbar/AppBarLayout.java

Lines changed: 50 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,10 +60,12 @@
6060
import androidx.coordinatorlayout.widget.CoordinatorLayout;
6161
import androidx.core.graphics.drawable.DrawableCompat;
6262
import androidx.core.util.ObjectsCompat;
63+
import androidx.core.view.AccessibilityDelegateCompat;
6364
import androidx.core.view.NestedScrollingChild;
6465
import androidx.core.view.ViewCompat;
6566
import androidx.core.view.ViewCompat.NestedScrollType;
6667
import androidx.core.view.WindowInsetsCompat;
68+
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
6769
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat;
6870
import androidx.core.view.accessibility.AccessibilityViewCommand;
6971
import androidx.customview.view.AbsSavedState;
@@ -1381,6 +1383,8 @@ public abstract static class BaseDragCallback<T extends AppBarLayout> {
13811383
@Nullable private WeakReference<View> lastNestedScrollingChildRef;
13821384
private BaseDragCallback onDragCallback;
13831385

1386+
private boolean coordinatorLayoutA11yScrollable;
1387+
13841388
public BaseBehavior() {}
13851389

13861390
public BaseBehavior(Context context, AttributeSet attrs) {
@@ -1754,18 +1758,54 @@ private void updateAccessibilityActions(
17541758
if (!(lp.getBehavior() instanceof ScrollingViewBehavior)) {
17551759
return;
17561760
}
1757-
addAccessibilityScrollActions(coordinatorLayout, appBarLayout, scrollingView);
1761+
1762+
// Don't add actions if the children do not have scrolling flags.
1763+
if (!childrenHaveScrollFlags(appBarLayout)) {
1764+
return;
1765+
}
1766+
1767+
if (!ViewCompat.hasAccessibilityDelegate(coordinatorLayout)) {
1768+
ViewCompat.setAccessibilityDelegate(
1769+
coordinatorLayout,
1770+
new AccessibilityDelegateCompat() {
1771+
@Override
1772+
public void onInitializeAccessibilityNodeInfo(
1773+
View host, @NonNull AccessibilityNodeInfoCompat info) {
1774+
super.onInitializeAccessibilityNodeInfo(host, info);
1775+
info.setScrollable(coordinatorLayoutA11yScrollable);
1776+
info.setClassName(ScrollView.class.getName());
1777+
}
1778+
});
1779+
}
1780+
1781+
coordinatorLayoutA11yScrollable =
1782+
addAccessibilityScrollActions(coordinatorLayout, appBarLayout, scrollingView);
1783+
}
1784+
1785+
private boolean childrenHaveScrollFlags(AppBarLayout appBarLayout) {
1786+
final int childCount = appBarLayout.getChildCount();
1787+
for (int i = 0; i < childCount; i++) {
1788+
final View child = appBarLayout.getChildAt(i);
1789+
final LayoutParams childLp = (LayoutParams) child.getLayoutParams();
1790+
final int flags = childLp.scrollFlags;
1791+
1792+
if (flags != AppBarLayout.LayoutParams.SCROLL_FLAG_NO_SCROLL) {
1793+
return true;
1794+
}
1795+
}
1796+
return false;
17581797
}
17591798

1760-
private void addAccessibilityScrollActions(
1799+
private boolean addAccessibilityScrollActions(
17611800
final CoordinatorLayout coordinatorLayout,
17621801
@NonNull final T appBarLayout,
17631802
@NonNull final View scrollingView) {
1764-
if (getTopBottomOffsetForScrollingSibling() != -appBarLayout.getTotalScrollRange()
1765-
&& scrollingView.canScrollVertically(1)) {
1766-
// Add a collapsing action if the view can scroll up and the offset isn't the abl scroll
1767-
// range. (This offset means the view is completely collapsed). Collapse to minimum height.
1803+
boolean a11yScrollable = false;
1804+
if (getTopBottomOffsetForScrollingSibling() != -appBarLayout.getTotalScrollRange()) {
1805+
// Add a collapsing action if the view offset isn't the abl scroll range.
1806+
// (The same offset means the view is completely collapsed). Collapse to minimum height.
17681807
addActionToExpand(coordinatorLayout, appBarLayout, ACTION_SCROLL_FORWARD, false);
1808+
a11yScrollable = true;
17691809
}
17701810
// Don't add an expanding action if the sibling offset is 0, which would mean the abl is
17711811
// completely expanded.
@@ -1794,13 +1834,16 @@ public boolean perform(@NonNull View view, @Nullable CommandArguments arguments)
17941834
return true;
17951835
}
17961836
});
1837+
a11yScrollable = true;
17971838
}
17981839
} else {
17991840
// If the view can't scroll down, we are probably at the top of the scrolling content so
18001841
// expand completely.
18011842
addActionToExpand(coordinatorLayout, appBarLayout, ACTION_SCROLL_BACKWARD, true);
1843+
a11yScrollable = true;
18021844
}
18031845
}
1846+
return a11yScrollable;
18041847
}
18051848

18061849
private void addActionToExpand(
@@ -2216,6 +2259,7 @@ public void onDependentViewRemoved(
22162259
if (dependency instanceof AppBarLayout) {
22172260
ViewCompat.removeAccessibilityAction(parent, ACTION_SCROLL_FORWARD.getId());
22182261
ViewCompat.removeAccessibilityAction(parent, ACTION_SCROLL_BACKWARD.getId());
2262+
ViewCompat.setAccessibilityDelegate(parent, null);
22192263
}
22202264
}
22212265

tests/javatests/com/google/android/material/appbar/AppBarLayoutBaseTest.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@
2727
import static com.google.android.material.testutils.TestUtilsActions.setTitle;
2828
import static org.hamcrest.CoreMatchers.equalTo;
2929
import static org.junit.Assert.assertEquals;
30+
import static org.junit.Assert.assertFalse;
3031
import static org.junit.Assert.assertThat;
32+
import static org.junit.Assert.assertTrue;
3133

3234
import android.graphics.Color;
3335
import android.os.Build;
@@ -148,4 +150,14 @@ protected void assertAccessibilityHasScrollBackwardAction(boolean hasScrollBackw
148150
equalTo(hasScrollBackward));
149151
}
150152
}
153+
154+
protected void assertAccessibilityScrollable(boolean isScrollable) {
155+
AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain();
156+
ViewCompat.onInitializeAccessibilityNodeInfo(mCoordinatorLayout, info);
157+
if (isScrollable) {
158+
assertTrue(info.isScrollable());
159+
} else {
160+
assertFalse(info.isScrollable());
161+
}
162+
}
151163
}

tests/javatests/com/google/android/material/appbar/AppBarWithCollapsingToolbarTest.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ public void testPinnedToolbar() throws Throwable {
5858
});
5959
assertAccessibilityHasScrollForwardAction(true);
6060
assertAccessibilityHasScrollBackwardAction(false);
61+
assertAccessibilityScrollable(true);
6162

6263
final int[] appbarOnScreenXY = new int[2];
6364
final int[] coordinatorLayoutOnScreenXY = new int[2];
@@ -88,6 +89,7 @@ public void testPinnedToolbar() throws Throwable {
8889
// for SCROLL_FLAG_SCROLL and SCROLL_EXIT_UNTIL_COLLAPSED, so it can't scroll backward.
8990
assertAccessibilityHasScrollForwardAction(false);
9091
assertAccessibilityHasScrollBackwardAction(false);
92+
assertAccessibilityScrollable(false);
9193
mAppBar.getLocationOnScreen(appbarOnScreenXY);
9294
// At this point the app bar should be visually snapped below the system status bar.
9395
// Allow for off-by-a-pixel margin of error.
@@ -155,6 +157,7 @@ public void testPinnedToolbar() throws Throwable {
155157
assertScrimAlpha(0);
156158
assertAccessibilityHasScrollForwardAction(true);
157159
assertAccessibilityHasScrollBackwardAction(false);
160+
assertAccessibilityScrollable(true);
158161
}
159162

160163
@Test
@@ -199,6 +202,7 @@ public void testScrollingToolbar() throws Throwable {
199202
});
200203
assertAccessibilityHasScrollForwardAction(true);
201204
assertAccessibilityHasScrollBackwardAction(false);
205+
assertAccessibilityScrollable(true);
202206

203207
// Perform a swipe-up gesture across the horizontal center of the screen, starting from
204208
// just below the AppBarLayout
@@ -209,6 +213,7 @@ public void testScrollingToolbar() throws Throwable {
209213
// has a scroll backward action.
210214
assertAccessibilityHasScrollForwardAction(false);
211215
assertAccessibilityHasScrollBackwardAction(true);
216+
assertAccessibilityScrollable(true);
212217

213218
mAppBar.getLocationOnScreen(appbarOnScreenXY);
214219
// At this point the app bar should not be visually "present" on the screen, with its bottom
@@ -258,6 +263,7 @@ public void testScrollingToolbar() throws Throwable {
258263
assertScrimAlpha(0);
259264
assertAccessibilityHasScrollForwardAction(true);
260265
assertAccessibilityHasScrollBackwardAction(false);
266+
assertAccessibilityScrollable(true);
261267

262268
// Perform yet another swipe-down gesture across the horizontal center of the screen.
263269
performVerticalSwipeDownGesture(

tests/javatests/com/google/android/material/appbar/AppBarWithToolbarTest.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,7 @@ public void testUpdateAccessibilityActionsWithEnterAlwaysFlag() throws Throwable
275275
// Very top of screen, can scroll forward to collapse but can't scroll backward.
276276
assertAccessibilityHasScrollForwardAction(true);
277277
assertAccessibilityHasScrollBackwardAction(false);
278+
assertAccessibilityScrollable(true);
278279

279280
// Perform a swipe-up gesture across the horizontal center of the screen.
280281
performVerticalSwipeUpGesture(
@@ -287,6 +288,7 @@ public void testUpdateAccessibilityActionsWithEnterAlwaysFlag() throws Throwable
287288
// the bar will always be entered/expanded on scroll.
288289
assertAccessibilityHasScrollForwardAction(false);
289290
assertAccessibilityHasScrollBackwardAction(true);
291+
assertAccessibilityScrollable(true);
290292
}
291293

292294
@Test
@@ -306,13 +308,16 @@ public void testUpdateAccessibilityActionWithViewsRemoved() throws Throwable {
306308

307309
assertAccessibilityHasScrollForwardAction(true);
308310
assertAccessibilityHasScrollBackwardAction(false);
311+
assertAccessibilityScrollable(true);
312+
309313
activityTestRule.runOnUiThread(
310314
() -> {
311315
mCoordinatorLayout.removeAllViews();
312316
});
313317

314318
assertAccessibilityHasScrollForwardAction(false);
315319
assertAccessibilityHasScrollBackwardAction(false);
320+
assertAccessibilityScrollable(false);
316321
}
317322

318323
@Test
@@ -330,12 +335,16 @@ public void testUpdateAccessibilityActionsWithSetScrollFlags() throws Throwable
330335

331336
AppBarLayout.LayoutParams lp = (AppBarLayout.LayoutParams) mToolbar.getLayoutParams();
332337

333-
// Disable scrolling and update the a11y actions.
338+
// Disable scrolling and call onLayout to update the a11y actions.
334339
lp.setScrollFlags(SCROLL_FLAG_NO_SCROLL);
335340
activityTestRule.runOnUiThread(
336341
() -> {
337342
mToolbar.setLayoutParams(lp);
343+
final CoordinatorLayout.Behavior<AppBarLayout> behavior =
344+
((CoordinatorLayout.LayoutParams) mAppBar.getLayoutParams()).getBehavior();
345+
behavior.onLayoutChild(mCoordinatorLayout, mAppBar, mAppBar.getLayoutDirection());
338346
});
347+
339348
assertAccessibilityHasScrollForwardAction(false);
340349
assertAccessibilityHasScrollBackwardAction(false);
341350

@@ -344,10 +353,14 @@ public void testUpdateAccessibilityActionsWithSetScrollFlags() throws Throwable
344353
activityTestRule.runOnUiThread(
345354
() -> {
346355
mToolbar.setLayoutParams(lp);
356+
final CoordinatorLayout.Behavior<AppBarLayout> behavior =
357+
((CoordinatorLayout.LayoutParams) mAppBar.getLayoutParams()).getBehavior();
358+
behavior.onLayoutChild(mCoordinatorLayout, mAppBar, mAppBar.getLayoutDirection());
347359
});
348360
// Can scroll forward to collapse, and cannot expand because it's already expanded.
349361
assertAccessibilityHasScrollForwardAction(true);
350362
assertAccessibilityHasScrollBackwardAction(false);
363+
assertAccessibilityScrollable(true);
351364

352365
// Perform a swipe-up gesture across the horizontal center of the screen. The toolbar should be
353366
// collapsed.
@@ -361,5 +374,6 @@ public void testUpdateAccessibilityActionsWithSetScrollFlags() throws Throwable
361374
// for SCROLL_FLAG_SCROLL, so it can't scroll backward.
362375
assertAccessibilityHasScrollForwardAction(false);
363376
assertAccessibilityHasScrollBackwardAction(false);
377+
assertAccessibilityScrollable(false);
364378
}
365379
}

0 commit comments

Comments
 (0)