|
41 | 41 | import androidx.core.math.MathUtils; |
42 | 42 | import androidx.customview.view.AbsSavedState; |
43 | 43 | import androidx.core.view.ViewCompat; |
| 44 | +import androidx.core.view.accessibility.AccessibilityNodeInfoCompat; |
| 45 | +import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat; |
| 46 | +import androidx.core.view.accessibility.AccessibilityViewCommand; |
44 | 47 | import androidx.customview.widget.ViewDragHelper; |
45 | 48 | import android.util.AttributeSet; |
46 | 49 | import android.util.TypedValue; |
|
50 | 53 | import android.view.ViewConfiguration; |
51 | 54 | import android.view.ViewGroup; |
52 | 55 | import android.view.ViewParent; |
53 | | -import android.view.accessibility.AccessibilityEvent; |
54 | 56 | import androidx.coordinatorlayout.widget.CoordinatorLayout; |
55 | 57 | import androidx.coordinatorlayout.widget.CoordinatorLayout.LayoutParams; |
56 | 58 | import com.google.android.material.resources.MaterialResources; |
|
63 | 65 | /** |
64 | 66 | * An interaction behavior plugin for a child view of {@link CoordinatorLayout} to make it work as a |
65 | 67 | * bottom sheet. |
| 68 | + * |
| 69 | + * <p>For a persistent bottom sheet, to send useful accessibility events, use {@link |
| 70 | + * ViewCompat#setAccessibilityPaneTitle(View, CharSequence)} to set a title when in an expanded |
| 71 | + * state, and to remove the title when in a collapsed state. This can be tracked in {@link |
| 72 | + * BottomSheetCallback}. |
| 73 | + * |
| 74 | + * <p>For BottomSheetDialog use {@link BottomSheetDialog#setTitle(int)}, and for |
| 75 | + * BottomSheetDialogFragment use {@link ViewCompat#setAccessibilityPaneTitle(View, CharSequence)}. |
| 76 | + * The titles need to only be set once here as the views behave like windows in all states. |
66 | 77 | */ |
67 | 78 | public class BottomSheetBehavior<V extends View> extends CoordinatorLayout.Behavior<V> { |
68 | 79 |
|
@@ -362,6 +373,11 @@ public boolean onLayoutChild( |
362 | 373 | isShapeExpanded = state == STATE_EXPANDED; |
363 | 374 | materialShapeDrawable.setInterpolation(isShapeExpanded ? 0f : 1f); |
364 | 375 | } |
| 376 | + updateAccessibilityActions(); |
| 377 | + if (ViewCompat.getImportantForAccessibility(child) |
| 378 | + == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO) { |
| 379 | + ViewCompat.setImportantForAccessibility(child, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES); |
| 380 | + } |
365 | 381 | } |
366 | 382 | if (viewDragHelper == null) { |
367 | 383 | viewDragHelper = ViewDragHelper.create(parent, dragCallback); |
@@ -678,6 +694,8 @@ public void setFitToContents(boolean fitToContents) { |
678 | 694 | } |
679 | 695 | // Fix incorrect expanded settings depending on whether or not we are fitting sheet to contents. |
680 | 696 | setStateInternal((this.fitToContents && state == STATE_HALF_EXPANDED) ? STATE_EXPANDED : state); |
| 697 | + |
| 698 | + updateAccessibilityActions(); |
681 | 699 | } |
682 | 700 |
|
683 | 701 | /** |
@@ -796,6 +814,7 @@ public void setHideable(boolean hideable) { |
796 | 814 | // Lift up to collapsed state |
797 | 815 | setState(STATE_COLLAPSED); |
798 | 816 | } |
| 817 | + updateAccessibilityActions(); |
799 | 818 | } |
800 | 819 | } |
801 | 820 |
|
@@ -950,14 +969,11 @@ void setStateInternal(@State int state) { |
950 | 969 | updateImportantForAccessibility(false); |
951 | 970 | } |
952 | 971 |
|
953 | | - ViewCompat.setImportantForAccessibility( |
954 | | - bottomSheet, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES); |
955 | | - bottomSheet.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); |
956 | | - |
957 | 972 | updateDrawableForTargetState(state); |
958 | 973 | if (callback != null) { |
959 | 974 | callback.onStateChanged(bottomSheet, state); |
960 | 975 | } |
| 976 | + updateAccessibilityActions(); |
961 | 977 | } |
962 | 978 |
|
963 | 979 | private void updateDrawableForTargetState(@State int state) { |
@@ -1469,4 +1485,61 @@ private void updateImportantForAccessibility(boolean expanded) { |
1469 | 1485 | importantForAccessibilityMap = null; |
1470 | 1486 | } |
1471 | 1487 | } |
| 1488 | + |
| 1489 | + private void updateAccessibilityActions() { |
| 1490 | + if (viewRef == null) { |
| 1491 | + return; |
| 1492 | + } |
| 1493 | + V child = viewRef.get(); |
| 1494 | + if (child == null) { |
| 1495 | + return; |
| 1496 | + } |
| 1497 | + ViewCompat.removeAccessibilityAction(child, AccessibilityNodeInfoCompat.ACTION_COLLAPSE); |
| 1498 | + ViewCompat.removeAccessibilityAction(child, AccessibilityNodeInfoCompat.ACTION_EXPAND); |
| 1499 | + ViewCompat.removeAccessibilityAction(child, AccessibilityNodeInfoCompat.ACTION_DISMISS); |
| 1500 | + |
| 1501 | + if (hideable && state != STATE_HIDDEN) { |
| 1502 | + addAccessibilityActionForState(child, AccessibilityActionCompat.ACTION_DISMISS, STATE_HIDDEN); |
| 1503 | + } |
| 1504 | + |
| 1505 | + switch (state) { |
| 1506 | + case STATE_EXPANDED: |
| 1507 | + { |
| 1508 | + int nextState = fitToContents ? STATE_COLLAPSED : STATE_HALF_EXPANDED; |
| 1509 | + addAccessibilityActionForState( |
| 1510 | + child, AccessibilityActionCompat.ACTION_COLLAPSE, nextState); |
| 1511 | + break; |
| 1512 | + } |
| 1513 | + case STATE_HALF_EXPANDED: |
| 1514 | + { |
| 1515 | + addAccessibilityActionForState( |
| 1516 | + child, AccessibilityActionCompat.ACTION_COLLAPSE, STATE_COLLAPSED); |
| 1517 | + addAccessibilityActionForState( |
| 1518 | + child, AccessibilityActionCompat.ACTION_EXPAND, STATE_EXPANDED); |
| 1519 | + break; |
| 1520 | + } |
| 1521 | + case STATE_COLLAPSED: |
| 1522 | + { |
| 1523 | + int nextState = fitToContents ? STATE_EXPANDED : STATE_HALF_EXPANDED; |
| 1524 | + addAccessibilityActionForState(child, AccessibilityActionCompat.ACTION_EXPAND, nextState); |
| 1525 | + break; |
| 1526 | + } |
| 1527 | + default: // fall out |
| 1528 | + } |
| 1529 | + } |
| 1530 | + |
| 1531 | + private void addAccessibilityActionForState( |
| 1532 | + V child, AccessibilityActionCompat action, final int state) { |
| 1533 | + ViewCompat.replaceAccessibilityAction( |
| 1534 | + child, |
| 1535 | + action, |
| 1536 | + null, |
| 1537 | + new AccessibilityViewCommand() { |
| 1538 | + @Override |
| 1539 | + public boolean perform(@NonNull View view, @Nullable CommandArguments arguments) { |
| 1540 | + setState(state); |
| 1541 | + return true; |
| 1542 | + } |
| 1543 | + }); |
| 1544 | + } |
1472 | 1545 | } |
0 commit comments