Make rotary scrolling opt-in for RecyclerViews
Rotary scrolling isn't needed for most CarUiRecyclerViews because all
the items are focusable. It causes problems when there's a gap between
adjacent rows, as is the case in the launcher. This CL makes rotary
scrolling opt-in rather than opt-out for CarUiRecyclerViews. We can
enable rotary scrolling where we need it, either declaratively or
programmatically via a new method in CarUiUtils.
Test: try scrolling in Launcher and Settings
Test: try scrolling in scroll tab of RotaryPlayground
Test: set contentDescription programmatically to enable rotary scrolling
Test: make RotaryPlayground enable rotary scrolling programmatically
Bug: 160922045
Change-Id: Ia6ed5ddafedd9c628cb9d5430fd92b1388215ea8
Merged-In: Ia6ed5ddafedd9c628cb9d5430fd92b1388215ea8
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/res/layout/focus_parking_view_test_activity.xml b/car-ui-lib/car-ui-lib/src/androidTest/res/layout/focus_parking_view_test_activity.xml
index 0ad8cd7..9e55e36 100644
--- a/car-ui-lib/car-ui-lib/src/androidTest/res/layout/focus_parking_view_test_activity.xml
+++ b/car-ui-lib/car-ui-lib/src/androidTest/res/layout/focus_parking_view_test_activity.xml
@@ -36,5 +36,7 @@
<com.android.car.ui.recyclerview.CarUiRecyclerView
android:id="@+id/list"
android:layout_width="wrap_content"
- android:layout_height="wrap_content"/>
+ android:layout_height="wrap_content"
+ android:contentDescription="com.android.car.ui.utils.VERTICALLY_SCROLLABLE"/>
+ <!-- TODO(b/171339427) Use app:rotaryScrollEnabled instead of contentDescription. -->
</LinearLayout>
diff --git a/car-ui-lib/car-ui-lib/src/androidTest/res/layout/view_utils_test_activity.xml b/car-ui-lib/car-ui-lib/src/androidTest/res/layout/view_utils_test_activity.xml
index 558dd9f..f5b9028 100644
--- a/car-ui-lib/car-ui-lib/src/androidTest/res/layout/view_utils_test_activity.xml
+++ b/car-ui-lib/car-ui-lib/src/androidTest/res/layout/view_utils_test_activity.xml
@@ -91,6 +91,8 @@
<com.android.car.ui.recyclerview.CarUiRecyclerView
android:id="@+id/list5"
android:layout_width="wrap_content"
- android:layout_height="wrap_content"/>
+ android:layout_height="wrap_content"
+ android:contentDescription="com.android.car.ui.utils.VERTICALLY_SCROLLABLE"/>
+ <!-- TODO(b/171339427) Use app:rotaryScrollEnabled instead of contentDescription. -->
</com.android.car.ui.FocusArea>
</LinearLayout>
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiRecyclerView.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiRecyclerView.java
index 00e06b9..b755213 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiRecyclerView.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/recyclerview/CarUiRecyclerView.java
@@ -187,7 +187,7 @@
}
private void init(Context context, AttributeSet attrs, int defStyleAttr) {
- initRotaryScroll(context, attrs, defStyleAttr);
+ initRotaryScroll();
setClipToPadding(false);
TypedArray a = context.obtainStyledAttributes(
attrs,
@@ -283,28 +283,19 @@
}
/**
- * If this view's content description isn't set to opt out of scrolling via the rotary
- * controller, initialize it accordingly.
+ * If this view's content description is set to opt into scrolling via the rotary controller,
+ * initialize it accordingly.
*/
- private void initRotaryScroll(Context context, AttributeSet attrs, int defStyleAttr) {
+ private void initRotaryScroll() {
CharSequence contentDescription = getContentDescription();
- if (contentDescription == null) {
- TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RecyclerView,
- defStyleAttr, /* defStyleRes= */ 0);
- int orientation = a.getInt(R.styleable.RecyclerView_android_orientation,
- LinearLayout.VERTICAL);
- setContentDescription(
- orientation == LinearLayout.HORIZONTAL
- ? ROTARY_HORIZONTALLY_SCROLLABLE
- : ROTARY_VERTICALLY_SCROLLABLE);
- } else if (!ROTARY_HORIZONTALLY_SCROLLABLE.contentEquals(contentDescription)
- && !ROTARY_VERTICALLY_SCROLLABLE.contentEquals(contentDescription)) {
- return;
- }
+ boolean rotaryScrollEnabled = contentDescription != null
+ && (ROTARY_HORIZONTALLY_SCROLLABLE.contentEquals(contentDescription)
+ || ROTARY_VERTICALLY_SCROLLABLE.contentEquals(contentDescription));
- // Convert SOURCE_ROTARY_ENCODER scroll events into SOURCE_MOUSE scroll events that
- // RecyclerView knows how to handle.
- setOnGenericMotionListener((v, event) -> {
+ // If rotary scrolling is enabled, set a generic motion event listener to convert
+ // SOURCE_ROTARY_ENCODER scroll events into SOURCE_MOUSE scroll events that RecyclerView
+ // knows how to handle.
+ setOnGenericMotionListener(rotaryScrollEnabled ? (v, event) -> {
if (event.getAction() == MotionEvent.ACTION_SCROLL) {
if (event.getSource() == InputDevice.SOURCE_ROTARY_ENCODER) {
MotionEvent mouseEvent = MotionEvent.obtain(event);
@@ -314,11 +305,11 @@
}
}
return false;
- });
+ } : null);
- // Mark this view as focusable. This view will be focused when no focusable elements are
- // visible.
- setFocusable(true);
+ // If rotary scrolling is enabled, mark this view as focusable. This view will be focused
+ // when no focusable elements are visible.
+ setFocusable(rotaryScrollEnabled);
// Focus this view before descendants so that the RotaryService can focus this view when it
// wants to.
@@ -534,6 +525,12 @@
removeItemDecoration(mDividerItemDecorationGrid);
}
+ @Override
+ public void setContentDescription(CharSequence contentDescription) {
+ super.setContentDescription(contentDescription);
+ initRotaryScroll();
+ }
+
private static RuntimeException andLog(String msg, Throwable t) {
Log.e(TAG, msg, t);
throw new RuntimeException(msg, t);
diff --git a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/utils/CarUiUtils.java b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/utils/CarUiUtils.java
index b27a81a..ce4d8a5 100644
--- a/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/utils/CarUiUtils.java
+++ b/car-ui-lib/car-ui-lib/src/main/java/com/android/car/ui/utils/CarUiUtils.java
@@ -15,6 +15,9 @@
*/
package com.android.car.ui.utils;
+import static com.android.car.ui.utils.RotaryConstants.ROTARY_HORIZONTALLY_SCROLLABLE;
+import static com.android.car.ui.utils.RotaryConstants.ROTARY_VERTICALLY_SCROLLABLE;
+
import android.app.Activity;
import android.content.Context;
import android.content.ContextWrapper;
@@ -153,6 +156,19 @@
}
/**
+ * Enables rotary scrolling for {@code view}, either vertically (if {@code isVertical} is true)
+ * or horizontally (if {@code isVertical} is false). With rotary scrolling enabled, rotating the
+ * rotary controller will scroll rather than moving the focus when moving the focus would cause
+ * a lot of scrolling. Rotary scrolling should be enabled for scrolling views which contain
+ * content which the user may want to see but can't interact with, either alone or along with
+ * interactive (focusable) content.
+ */
+ public static void setRotaryScrollEnabled(@NonNull View view, boolean isVertical) {
+ view.setContentDescription(
+ isVertical ? ROTARY_VERTICALLY_SCROLLABLE : ROTARY_HORIZONTALLY_SCROLLABLE);
+ }
+
+ /**
* It behaves similarly to {@link View#findViewById(int)}, except that on Q and below,
* it will first resolve the id to whatever it references.
*