Adding an overloaded smoothScrollBy with duration.
Bug: 118339555
Test: RecyclerViewLayoutTest
Change-Id: I9ac51d68bc2b18071a5d5457d17be39cb0c736a8
diff --git a/recyclerview/recyclerview/api/1.1.0-alpha05.txt b/recyclerview/recyclerview/api/1.1.0-alpha05.txt
index aea607d..95bc7c3 100644
--- a/recyclerview/recyclerview/api/1.1.0-alpha05.txt
+++ b/recyclerview/recyclerview/api/1.1.0-alpha05.txt
@@ -446,6 +446,7 @@
method public void setViewCacheExtension(androidx.recyclerview.widget.RecyclerView.ViewCacheExtension?);
method public void smoothScrollBy(@Px int, @Px int);
method public void smoothScrollBy(@Px int, @Px int, android.view.animation.Interpolator?);
+ method public void smoothScrollBy(@Px int, @Px int, android.view.animation.Interpolator?, int);
method public void smoothScrollToPosition(int);
method public boolean startNestedScroll(int, int);
method public void stopNestedScroll(int);
@@ -461,6 +462,7 @@
field public static final int SCROLL_STATE_SETTLING = 2; // 0x2
field public static final int TOUCH_SLOP_DEFAULT = 0; // 0x0
field public static final int TOUCH_SLOP_PAGING = 1; // 0x1
+ field public static final int UNDEFINED_DURATION = -2147483648; // 0x80000000
field public static final int VERTICAL = 1; // 0x1
}
diff --git a/recyclerview/recyclerview/api/1.1.0-alpha06.txt b/recyclerview/recyclerview/api/1.1.0-alpha06.txt
index aea607d..95bc7c3 100644
--- a/recyclerview/recyclerview/api/1.1.0-alpha06.txt
+++ b/recyclerview/recyclerview/api/1.1.0-alpha06.txt
@@ -446,6 +446,7 @@
method public void setViewCacheExtension(androidx.recyclerview.widget.RecyclerView.ViewCacheExtension?);
method public void smoothScrollBy(@Px int, @Px int);
method public void smoothScrollBy(@Px int, @Px int, android.view.animation.Interpolator?);
+ method public void smoothScrollBy(@Px int, @Px int, android.view.animation.Interpolator?, int);
method public void smoothScrollToPosition(int);
method public boolean startNestedScroll(int, int);
method public void stopNestedScroll(int);
@@ -461,6 +462,7 @@
field public static final int SCROLL_STATE_SETTLING = 2; // 0x2
field public static final int TOUCH_SLOP_DEFAULT = 0; // 0x0
field public static final int TOUCH_SLOP_PAGING = 1; // 0x1
+ field public static final int UNDEFINED_DURATION = -2147483648; // 0x80000000
field public static final int VERTICAL = 1; // 0x1
}
diff --git a/recyclerview/recyclerview/api/current.txt b/recyclerview/recyclerview/api/current.txt
index aea607d..95bc7c3 100644
--- a/recyclerview/recyclerview/api/current.txt
+++ b/recyclerview/recyclerview/api/current.txt
@@ -446,6 +446,7 @@
method public void setViewCacheExtension(androidx.recyclerview.widget.RecyclerView.ViewCacheExtension?);
method public void smoothScrollBy(@Px int, @Px int);
method public void smoothScrollBy(@Px int, @Px int, android.view.animation.Interpolator?);
+ method public void smoothScrollBy(@Px int, @Px int, android.view.animation.Interpolator?, int);
method public void smoothScrollToPosition(int);
method public boolean startNestedScroll(int, int);
method public void stopNestedScroll(int);
@@ -461,6 +462,7 @@
field public static final int SCROLL_STATE_SETTLING = 2; // 0x2
field public static final int TOUCH_SLOP_DEFAULT = 0; // 0x0
field public static final int TOUCH_SLOP_PAGING = 1; // 0x1
+ field public static final int UNDEFINED_DURATION = -2147483648; // 0x80000000
field public static final int VERTICAL = 1; // 0x1
}
diff --git a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewLayoutTest.java b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewLayoutTest.java
index fd5cbcf..f6fe43c 100644
--- a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewLayoutTest.java
+++ b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewLayoutTest.java
@@ -2633,6 +2633,189 @@
}
@Test
+ public void smoothScrollBy_negativeDuration_completesSynchronously() throws Throwable {
+ smoothScrollBy_completesSynchronously(-1);
+ }
+
+ @Test
+ public void smoothScrollBy_durationOf0_completesSynchronously() throws Throwable {
+ smoothScrollBy_completesSynchronously(0);
+ }
+
+ private void smoothScrollBy_completesSynchronously(final int duration) throws Throwable {
+ // Arrange
+
+ RecyclerView recyclerView = new RecyclerView(getActivity());
+ recyclerView.setAdapter(new TestAdapter(1000));
+ recyclerView.setLayoutManager(new TestLayoutManager());
+ setRecyclerView(recyclerView);
+ getInstrumentation().waitForIdleSync();
+
+ final int[] onScrolledCallCount = new int[1];
+ final int[] onScrolledTotalScrolled = new int[1];
+ final boolean[] stateChangeCalled = new boolean[1];
+
+ recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
+
+ @Override
+ public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
+ stateChangeCalled[0] = true;
+ }
+
+ @Override
+ public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
+ onScrolledCallCount[0]++;
+ onScrolledTotalScrolled[0] += dy;
+ }
+ });
+
+
+ mActivityRule.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+
+ // Act
+ mRecyclerView.smoothScrollBy(0, 100, null, duration);
+
+ // Assert
+ assertEquals(1, onScrolledCallCount[0]);
+ assertEquals(100, onScrolledTotalScrolled[0]);
+ assertFalse(stateChangeCalled[0]);
+ }
+ });
+ }
+
+ @Test
+ public void smoothScrollBy_durationOf1_completesAsynchronously() throws Throwable {
+ smoothScrollBy_completesAsynchronously(1);
+ }
+
+ @Test
+ public void smoothScrollBy_durationOfUndefined_completesAsynchronously() throws Throwable {
+ smoothScrollBy_completesAsynchronously(RecyclerView.UNDEFINED_DURATION);
+ }
+
+ private void smoothScrollBy_completesAsynchronously(final int duration) throws Throwable {
+
+ // Arrange
+
+ RecyclerView recyclerView = new RecyclerView(getActivity());
+ recyclerView.setAdapter(new TestAdapter(1000));
+ recyclerView.setLayoutManager(new TestLayoutManager());
+ setRecyclerView(recyclerView);
+ getInstrumentation().waitForIdleSync();
+
+ final int[] onScrolledCallCount = new int[1];
+ final int[] onScrolledTotalScrolled = new int[1];
+ final ArrayList<Integer> onScrollStateChangedStates = new ArrayList<>();
+ final CountDownLatch latch = new CountDownLatch(1);
+
+ recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
+
+ @Override
+ public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
+ onScrollStateChangedStates.add(newState);
+ if (newState == SCROLL_STATE_IDLE) {
+ latch.countDown();
+ }
+ }
+
+ @Override
+ public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
+ onScrolledTotalScrolled[0] += dy;
+ onScrolledCallCount[0]++;
+ }
+ });
+
+ // Act
+ mActivityRule.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+
+ // Act
+ mRecyclerView.smoothScrollBy(0, 100, null, duration);
+
+ // Assert that only onScrollStateChange has been called with settling.
+ assertEquals(0, onScrolledCallCount[0]);
+ assertEquals(1, onScrollStateChangedStates.size());
+ assertEquals(SCROLL_STATE_SETTLING, (int) onScrollStateChangedStates.get(0));
+ }
+ });
+ latch.await(5, TimeUnit.SECONDS);
+
+ // Assert that we did indeed finish
+ assertEquals(100, onScrolledTotalScrolled[0]);
+ assertEquals(2, onScrollStateChangedStates.size());
+ assertEquals(SCROLL_STATE_IDLE, (int) onScrollStateChangedStates.get(1));
+ }
+
+ @Test
+ public void smoothScrollBy_fastDurationIsFasterThanSlowDuration() throws Throwable {
+
+ // Arrange
+
+ final RecyclerView recyclerView0 = new RecyclerView(getActivity());
+ recyclerView0.setAdapter(new TestAdapter(1000));
+ recyclerView0.setLayoutManager(new TestLayoutManager());
+
+ final RecyclerView recyclerView1 = new RecyclerView(getActivity());
+ recyclerView1.setAdapter(new TestAdapter(1000));
+ recyclerView1.setLayoutManager(new TestLayoutManager());
+
+ mActivityRule.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ getActivity().getContainer().addView(recyclerView0);
+ getActivity().getContainer().addView(recyclerView1);
+ }
+ });
+
+ getInstrumentation().waitForIdleSync();
+
+ final int[] totalScrolled = new int[]{0, 0};
+ final ArrayList<Integer> completionOrder = new ArrayList<>();
+ final CountDownLatch latch = new CountDownLatch(2);
+
+ recyclerView0.addOnScrollListener(new RecyclerView.OnScrollListener() {
+ @Override
+ public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
+ super.onScrolled(recyclerView, dx, dy);
+ totalScrolled[0] += dy;
+ if (totalScrolled[0] == 100) {
+ completionOrder.add(0);
+ latch.countDown();
+ }
+ }
+ });
+
+ recyclerView1.addOnScrollListener(new RecyclerView.OnScrollListener() {
+ @Override
+ public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
+ super.onScrolled(recyclerView, dx, dy);
+ totalScrolled[1] += dy;
+ if (totalScrolled[1] == 100) {
+ completionOrder.add(1);
+ latch.countDown();
+ }
+ }
+ });
+
+ // Act
+ mActivityRule.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ recyclerView0.smoothScrollBy(0, 100, null, 100);
+ recyclerView1.smoothScrollBy(0, 100, null, 1000);
+ }
+ });
+ latch.await(5, TimeUnit.SECONDS);
+
+ // Assert
+ assertEquals(0, (int) completionOrder.get(0));
+ assertEquals(1, (int) completionOrder.get(1));
+ }
+
+ @Test
public void scrollStateForSmoothScroll() throws Throwable {
TestAdapter testAdapter = new TestAdapter(10);
TestLayoutManager tlm = new TestLayoutManager();
diff --git a/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/RecyclerView.java b/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/RecyclerView.java
index 51374b5..85eab5f 100644
--- a/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/RecyclerView.java
+++ b/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/RecyclerView.java
@@ -290,7 +290,10 @@
*/
public static final int TOUCH_SLOP_PAGING = 1;
- static final int UNDEFINED_DURATION = Integer.MIN_VALUE;
+ /**
+ * Constant that represents that a duration has not been defined.
+ */
+ public static final int UNDEFINED_DURATION = Integer.MIN_VALUE;
static final int MAX_SCROLL_DURATION = 2000;
@@ -2314,9 +2317,27 @@
* @param dx Pixels to scroll horizontally
* @param dy Pixels to scroll vertically
* @param interpolator {@link Interpolator} to be used for scrolling. If it is
- * {@code null}, RecyclerView is going to use the default interpolator.
+ * {@code null}, RecyclerView will use an internal default interpolator.
*/
public void smoothScrollBy(@Px int dx, @Px int dy, @Nullable Interpolator interpolator) {
+ smoothScrollBy(dx, dy, interpolator, UNDEFINED_DURATION);
+ }
+
+ /**
+ * Smooth scrolls the RecyclerView by a given distance.
+ *
+ * @param dx x distance in pixels.
+ * @param dy y distance in pixels.
+ * @param interpolator {@link Interpolator} to be used for scrolling. If it is {@code null},
+ * RecyclerView will use an internal default interpolator.
+ * @param duration Duration of the animation in milliseconds. Set to {@link #UNDEFINED_DURATION}
+ * to have the duration be automatically calculated based on an internally
+ * defined standard initial velocity. A duration less than 1 (that does not
+ * equal UNDEFINED_DURATION), will result in a call to
+ * {@link #scrollBy(int, int)}.
+ */
+ public void smoothScrollBy(@Px int dx, @Px int dy, @Nullable Interpolator interpolator,
+ int duration) {
if (mLayout == null) {
Log.e(TAG, "Cannot smooth scroll without a LayoutManager set. "
+ "Call setLayoutManager with a non-null argument.");
@@ -2332,7 +2353,12 @@
dy = 0;
}
if (dx != 0 || dy != 0) {
- mViewFlinger.smoothScrollBy(dx, dy, UNDEFINED_DURATION, interpolator);
+ boolean durationSuggestsAnimation = duration == UNDEFINED_DURATION || duration > 0;
+ if (durationSuggestsAnimation) {
+ mViewFlinger.smoothScrollBy(dx, dy, duration, interpolator);
+ } else {
+ scrollBy(dx, dy);
+ }
}
}
@@ -5134,6 +5160,7 @@
|| mAdapterHelper.hasPendingUpdates();
}
+ // Effectively private. Set to default to avoid synthetic accessor.
class ViewFlinger implements Runnable {
private int mLastFlingX;
private int mLastFlingY;
@@ -5330,6 +5357,17 @@
postOnAnimation();
}
+ /**
+ * Smooth scrolls the RecyclerView by a given distance.
+ *
+ * @param dx x distance in pixels.
+ * @param dy y distance in pixels.
+ * @param duration Duration of the animation in milliseconds. Set to
+ * {@link #UNDEFINED_DURATION} to have the duration automatically calculated
+ * based on an internally defined standard velocity.
+ * @param interpolator {@link Interpolator} to be used for scrolling. If it is {@code null},
+ * RecyclerView will use an internal default interpolator.
+ */
public void smoothScrollBy(int dx, int dy, int duration,
@Nullable Interpolator interpolator) {
diff --git a/samples/Support7Demos/src/main/AndroidManifest.xml b/samples/Support7Demos/src/main/AndroidManifest.xml
index 769c2fe..cd51a31 100644
--- a/samples/Support7Demos/src/main/AndroidManifest.xml
+++ b/samples/Support7Demos/src/main/AndroidManifest.xml
@@ -444,6 +444,15 @@
</intent-filter>
</activity>
+ <activity android:name=".widget.RecyclerViewSmoothScrollByActivity"
+ android:label="@string/recycler_view_smooth_scroll_by"
+ android:theme="@style/Theme.AppCompat">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="com.example.android.supportv7.SAMPLE_CODE" />
+ </intent-filter>
+ </activity>
+
<activity android:name=".widget.RvInNestedScrollViewActivity"
android:label="@string/rv_in_nestedScrollView"
android:theme="@style/Theme.AppCompat">
diff --git a/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/RecyclerViewSmoothScrollByActivity.java b/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/RecyclerViewSmoothScrollByActivity.java
new file mode 100644
index 0000000..7e84c3f
--- /dev/null
+++ b/samples/Support7Demos/src/main/java/com/example/android/supportv7/widget/RecyclerViewSmoothScrollByActivity.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2019 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.example.android.supportv7.widget;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.text.Editable;
+import android.view.ViewGroup;
+import android.view.animation.Interpolator;
+import android.widget.EditText;
+
+import androidx.recyclerview.widget.DividerItemDecoration;
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.example.android.supportv7.Cheeses;
+import com.example.android.supportv7.R;
+import com.example.android.supportv7.widget.adapter.SimpleStringAdapter;
+
+/**
+ * Simple activity to test {@link RecyclerView#smoothScrollBy(int, int, Interpolator, int)}
+ * functionality.
+ */
+public class RecyclerViewSmoothScrollByActivity extends Activity {
+
+ private RecyclerView mRecyclerView;
+ private EditText mEditText;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.activity_rv_smoothscrollby);
+
+ mRecyclerView = findViewById(R.id.recyclerView);
+ mEditText = findViewById(R.id.editTextDuration);
+
+ mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
+ mRecyclerView.setAdapter(new SimpleStringAdapter(this, Cheeses.sCheeseStrings) {
+ @Override
+ public ViewHolder onCreateViewHolder(ViewGroup parent,
+ int viewType) {
+ final ViewHolder vh = super
+ .onCreateViewHolder(parent, viewType);
+ return vh;
+ }
+ });
+ mRecyclerView
+ .addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL));
+
+ findViewById(R.id.buttonUp).setOnClickListener(v -> scroll(false));
+ findViewById(R.id.buttonDown).setOnClickListener(v -> scroll(true));
+ }
+
+ private void scroll(boolean down) {
+ int duration = 100;
+ Editable editable = mEditText.getText();
+ if (editable != null) {
+ duration = Integer.parseInt(editable.toString());
+ }
+ mRecyclerView.smoothScrollBy(0, down ? 1000 : -1000, null, duration);
+ }
+}
diff --git a/samples/Support7Demos/src/main/res/layout/activity_rv_smoothscrollby.xml b/samples/Support7Demos/src/main/res/layout/activity_rv_smoothscrollby.xml
new file mode 100644
index 0000000..311d3ad
--- /dev/null
+++ b/samples/Support7Demos/src/main/res/layout/activity_rv_smoothscrollby.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright 2019 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.
+ -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <EditText
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:id="@+id/editTextDuration"
+ android:text="100"/>
+
+ <Button
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="down"
+ android:id="@+id/buttonDown"/>
+
+ <Button
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="up"
+ android:id="@+id/buttonUp"/>
+
+ <androidx.recyclerview.widget.RecyclerView
+ android:layout_width="match_parent"
+ android:layout_height="0dp"
+ android:layout_weight="1"
+ android:id="@+id/recyclerView" />
+
+
+</LinearLayout>
\ No newline at end of file
diff --git a/samples/Support7Demos/src/main/res/values/strings.xml b/samples/Support7Demos/src/main/res/values/strings.xml
index cb961ec..b87603e 100644
--- a/samples/Support7Demos/src/main/res/values/strings.xml
+++ b/samples/Support7Demos/src/main/res/values/strings.xml
@@ -148,6 +148,7 @@
<string name="sample_media_route_activity_presentation">Local Playback on Presentation Display</string>
<string name="recycler_view">RecyclerView/RecyclerViewActivity</string>
+ <string name="recycler_view_smooth_scroll_by">RecyclerView/RecyclerView SmoothScrollBy Activity</string>
<string name="recycler_view_stableid">RecyclerView/Stable Ids</string>
<string name="pager_recycler_view">RecyclerView/PagerRecyclerViewActivity</string>
<string name="animated_recycler_view">RecyclerView/Animated RecyclerView</string>