Add the support to drag the thumb in scrollbar.
Bug: 154244647
Bug: 156488147
Test: Manual
Change-Id: I2ad24e2500a0fe35aca102c5fbc6bbc795a22c4f
Merged-In: I2ad24e2500a0fe35aca102c5fbc6bbc795a22c4f
diff --git a/car-ui-lib/src/com/android/car/ui/recyclerview/CarUiRecyclerView.java b/car-ui-lib/src/com/android/car/ui/recyclerview/CarUiRecyclerView.java
index 63ed070..b75e904 100644
--- a/car-ui-lib/src/com/android/car/ui/recyclerview/CarUiRecyclerView.java
+++ b/car-ui-lib/src/com/android/car/ui/recyclerview/CarUiRecyclerView.java
@@ -183,41 +183,35 @@
context.getDrawable(R.drawable.car_ui_divider),
mNumOfColumns);
+ int topOffset = a.getInteger(R.styleable.CarUiRecyclerView_topOffset, /* defValue= */0);
+ int bottomOffset = a.getInteger(
+ R.styleable.CarUiRecyclerView_bottomOffset, /* defValue= */0);
if (mCarUiRecyclerViewLayout == CarUiRecyclerViewLayout.LINEAR) {
- int linearTopOffset =
- a.getInteger(R.styleable.CarUiRecyclerView_topOffset, /* defValue= */ 0);
- int linearBottomOffset =
- a.getInteger(R.styleable.CarUiRecyclerView_bottomOffset, /* defValue= */ 0);
-
if (enableDivider) {
addItemDecoration(mDividerItemDecorationLinear);
}
RecyclerView.ItemDecoration topOffsetItemDecoration =
- new LinearOffsetItemDecoration(linearTopOffset, OffsetPosition.START);
+ new LinearOffsetItemDecoration(topOffset, OffsetPosition.START);
RecyclerView.ItemDecoration bottomOffsetItemDecoration =
- new LinearOffsetItemDecoration(linearBottomOffset, OffsetPosition.END);
+ new LinearOffsetItemDecoration(bottomOffset, OffsetPosition.END);
addItemDecoration(topOffsetItemDecoration);
addItemDecoration(bottomOffsetItemDecoration);
setLayoutManager(new LinearLayoutManager(getContext()));
} else {
- int gridTopOffset =
- a.getInteger(R.styleable.CarUiRecyclerView_topOffset, /* defValue= */ 0);
- int gridBottomOffset =
- a.getInteger(R.styleable.CarUiRecyclerView_bottomOffset, /* defValue= */ 0);
if (enableDivider) {
addItemDecoration(mDividerItemDecorationGrid);
}
mOffsetItemDecoration =
- new GridOffsetItemDecoration(gridTopOffset, mNumOfColumns,
+ new GridOffsetItemDecoration(topOffset, mNumOfColumns,
OffsetPosition.START);
GridOffsetItemDecoration bottomOffsetItemDecoration =
- new GridOffsetItemDecoration(gridBottomOffset, mNumOfColumns,
+ new GridOffsetItemDecoration(bottomOffset, mNumOfColumns,
OffsetPosition.END);
addItemDecoration(mOffsetItemDecoration);
diff --git a/car-ui-lib/src/com/android/car/ui/recyclerview/DefaultScrollBar.java b/car-ui-lib/src/com/android/car/ui/recyclerview/DefaultScrollBar.java
index 0e68c4b..7052820 100644
--- a/car-ui-lib/src/com/android/car/ui/recyclerview/DefaultScrollBar.java
+++ b/car-ui-lib/src/com/android/car/ui/recyclerview/DefaultScrollBar.java
@@ -91,6 +91,10 @@
getRecyclerView().setOnFlingListener(null);
mSnapHelper.attachToRecyclerView(getRecyclerView());
+ // enables fast scrolling.
+ FastScroller fastScroller = new FastScroller(mRecyclerView, mScrollTrack, mScrollView);
+ fastScroller.enable();
+
mScrollView.addOnLayoutChangeListener(
(View v,
int left,
diff --git a/car-ui-lib/src/com/android/car/ui/recyclerview/FastScroller.java b/car-ui-lib/src/com/android/car/ui/recyclerview/FastScroller.java
new file mode 100644
index 0000000..9a44110
--- /dev/null
+++ b/car-ui-lib/src/com/android/car/ui/recyclerview/FastScroller.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.ui.recyclerview;
+
+import static com.android.car.ui.utils.CarUiUtils.requireViewByRefId;
+
+import android.view.MotionEvent;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.android.car.ui.R;
+
+/**
+ * Class responsible for fast scrolling. This class offers two functionalities.
+ * <ul>
+ * <li>User can hold the thumb and drag.</li>
+ * <li>User can click anywhere on the track and thumb will scroll to that position.</li>
+ * </ul>
+ */
+class FastScroller implements View.OnTouchListener {
+
+ private float mTouchDownY = -1;
+
+ private View mScrollTrackView;
+ private boolean mIsDragging;
+ private View mScrollThumb;
+ private RecyclerView mRecyclerView;
+
+ FastScroller(@NonNull RecyclerView recyclerView, @NonNull View scrollTrackView,
+ @NonNull View scrollView) {
+ mRecyclerView = recyclerView;
+ mScrollTrackView = scrollTrackView;
+ mScrollThumb = requireViewByRefId(scrollView, R.id.car_ui_scrollbar_thumb);
+ }
+
+ void enable() {
+ if (mRecyclerView != null) {
+ mScrollTrackView.setOnTouchListener(this);
+ }
+ }
+
+ @Override
+ public boolean onTouch(View v, MotionEvent me) {
+ switch (me.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ mTouchDownY = me.getY();
+ mIsDragging = false;
+ break;
+ case MotionEvent.ACTION_MOVE:
+ mIsDragging = true;
+ float thumbBottom = mScrollThumb.getY() + mScrollThumb.getHeight();
+ // check if the move coordinates are within the bounds of the thumb. i.e user is
+ // holding and dragging the thumb.
+ if (!(me.getY() + mScrollTrackView.getY() < thumbBottom
+ && me.getY() + mScrollTrackView.getY() > mScrollThumb.getY())) {
+ // don't do anything if touch is detected outside the thumb
+ return true;
+ }
+ // calculate where the center of the thumb is on the screen.
+ float thumbCenter = mScrollThumb.getY() + mScrollThumb.getHeight() / 2.0f;
+ // me.getY() returns the coordinates relative to the view. For example, if we
+ // click the top left of the scroll track the coordinates will be 0,0. Hence, we
+ // need to add the relative coordinates to the actual coordinates computed by the
+ // thumb center and add them to get the final Y coordinate. "(me.getY() -
+ // mTouchDownY)" calculates the distance that is moved from the previous touch
+ // event.
+ verticalScrollTo(thumbCenter + (me.getY() - mTouchDownY));
+ mTouchDownY = me.getY();
+ break;
+ case MotionEvent.ACTION_UP:
+ default:
+ mTouchDownY = -1;
+ // if not dragged then it's a click. When a click is detected on the track and
+ // within the range we want to move the center of the thumb to the Y coordinate
+ // of the clicked position.
+ if (!mIsDragging) {
+ verticalScrollTo(me.getY() + mScrollTrackView.getY());
+ }
+ }
+ return true;
+ }
+
+ private void verticalScrollTo(float y) {
+ int scrollingBy = calculateScrollDistance(y);
+ if (scrollingBy != 0) {
+ mRecyclerView.scrollBy(0, scrollingBy);
+ }
+ }
+
+ private int calculateScrollDistance(float newDragPos) {
+ final int[] scrollbarRange = getVerticalRange();
+ int scrollbarLength = scrollbarRange[1] - scrollbarRange[0];
+
+ float thumbCenter = mScrollThumb.getY() + mScrollThumb.getHeight() / 2.0f;
+
+ if (scrollbarLength == 0) {
+ return 0;
+ }
+ // percentage of data to be scrolled.
+ float percentage = ((newDragPos - thumbCenter) / (float) scrollbarLength);
+ int totalPossibleOffset =
+ mRecyclerView.computeVerticalScrollRange() - mRecyclerView.getHeight();
+ int scrollingBy = (int) (percentage * totalPossibleOffset);
+ int absoluteOffset = mRecyclerView.computeVerticalScrollOffset() + scrollingBy;
+ if (absoluteOffset < 0) {
+ return 0;
+ }
+ return scrollingBy;
+ }
+
+ /**
+ * Gets the (min, max) vertical positions of the vertical scroll bar. The range starts from the
+ * center of thumb when thumb is top aligned to center of the thumb when thumb is bottom
+ * aligned.
+ */
+ private int[] getVerticalRange() {
+ int[] verticalRange = new int[2];
+ verticalRange[0] = (int) mScrollTrackView.getY() + mScrollThumb.getHeight() / 2;
+ verticalRange[1] = (int) mScrollTrackView.getY() + mScrollTrackView.getHeight()
+ - mScrollThumb.getHeight() / 2;
+ return verticalRange;
+ }
+}