Backport of ListView.canScrollList in compat
Bug: 33100659
Test: ./gradlew support-compat:connectedCheck --info --daemon -Pandroid.testInstrumentationRunnerArguments.class=android.support.v4.widget.ListViewCompatTest
Test: ./gradlew support-core-ui:connectedCheck --info --daemon -Pandroid.testInstrumentationRunnerArguments.class=android.support.v4.widget.SwipeRefreshLayoutTest
Change-Id: If99bb26683671f5e722857d9c3600ea4d3d5fe53
diff --git a/api/26.0.0-SNAPSHOT.txt b/api/26.0.0-SNAPSHOT.txt
index f1040fe..b2a6003 100644
--- a/api/26.0.0-SNAPSHOT.txt
+++ b/api/26.0.0-SNAPSHOT.txt
@@ -9697,6 +9697,7 @@
}
public final class ListViewCompat {
+ method public static boolean canScrollList(android.widget.ListView, int);
method public static void scrollListBy(android.widget.ListView, int);
}
diff --git a/compat/java/android/support/v4/widget/ListViewCompat.java b/compat/java/android/support/v4/widget/ListViewCompat.java
index 1b1ec16..59ee741 100644
--- a/compat/java/android/support/v4/widget/ListViewCompat.java
+++ b/compat/java/android/support/v4/widget/ListViewCompat.java
@@ -22,8 +22,7 @@
import android.widget.ListView;
/**
- * Helper for accessing features in {@link ListView} introduced after API level
- * 4 in a backwards compatible fashion.
+ * Helper for accessing features in {@link ListView}
*/
public final class ListViewCompat {
@@ -35,8 +34,10 @@
*/
public static void scrollListBy(@NonNull ListView listView, int y) {
if (Build.VERSION.SDK_INT >= 19) {
+ // Call the framework version directly
listView.scrollListBy(y);
} else {
+ // provide backport on earlier versions
final int firstPosition = listView.getFirstVisiblePosition();
if (firstPosition == ListView.INVALID_POSITION) {
return;
@@ -52,5 +53,38 @@
}
}
+ /**
+ * Check if the items in the list can be scrolled in a certain direction.
+ *
+ * @param direction Negative to check scrolling up, positive to check
+ * scrolling down.
+ * @return true if the list can be scrolled in the specified direction,
+ * false otherwise.
+ * @see #scrollListBy(ListView, int)
+ */
+ public static boolean canScrollList(@NonNull ListView listView, int direction) {
+ if (Build.VERSION.SDK_INT >= 19) {
+ // Call the framework version directly
+ return listView.canScrollList(direction);
+ } else {
+ // provide backport on earlier versions
+ final int childCount = listView.getChildCount();
+ if (childCount == 0) {
+ return false;
+ }
+
+ final int firstPosition = listView.getFirstVisiblePosition();
+ if (direction > 0) {
+ final int lastBottom = listView.getChildAt(childCount - 1).getBottom();
+ final int lastPosition = firstPosition + childCount;
+ return lastPosition < listView.getCount()
+ || (lastBottom > listView.getHeight() - listView.getListPaddingBottom());
+ } else {
+ final int firstTop = listView.getChildAt(0).getTop();
+ return firstPosition > 0 || firstTop < listView.getListPaddingTop();
+ }
+ }
+ }
+
private ListViewCompat() {}
}
diff --git a/compat/tests/AndroidManifest.xml b/compat/tests/AndroidManifest.xml
index 5e9730c..ca75941 100644
--- a/compat/tests/AndroidManifest.xml
+++ b/compat/tests/AndroidManifest.xml
@@ -30,6 +30,8 @@
<application
android:supportsRtl="true"
android:theme="@style/TestActivityTheme">
+ <activity android:name="android.support.v4.widget.ListViewTestActivity"/>
+
<activity android:name="android.support.v4.widget.TextViewTestActivity"/>
<activity android:name="android.support.v4.view.VpaActivity"/>
diff --git a/compat/tests/java/android/support/v4/widget/ListViewCompatTest.java b/compat/tests/java/android/support/v4/widget/ListViewCompatTest.java
new file mode 100644
index 0000000..4fc65ec
--- /dev/null
+++ b/compat/tests/java/android/support/v4/widget/ListViewCompatTest.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2017 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 android.support.v4.widget;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.compat.test.R;
+import android.support.test.filters.SmallTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.v4.BaseInstrumentationTestCase;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver.OnGlobalLayoutListener;
+import android.widget.BaseAdapter;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import junit.framework.Assert;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@SmallTest
+public class ListViewCompatTest extends BaseInstrumentationTestCase<ListViewTestActivity> {
+ private ListView mListView;
+
+ public ListViewCompatTest() {
+ super(ListViewTestActivity.class);
+ }
+
+ @Before
+ public void setUp() throws Throwable {
+ mListView = (ListView) mActivityTestRule.getActivity().findViewById(R.id.content);
+ runOnMainAndLayoutSync(mActivityTestRule, mListView, new Runnable() {
+ @Override
+ public void run() {
+ mListView.setAdapter(new BaseAdapter() {
+ @Override
+ public int getCount() {
+ return 500;
+ }
+
+ @Override
+ public Object getItem(int position) {
+ return null;
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ if (convertView == null) {
+ convertView = LayoutInflater.from(mListView.getContext()).inflate(
+ R.layout.list_view_row, parent, false);
+ }
+ TextView result = (TextView) convertView;
+ result.setText("row #" + (position + 1));
+ return result;
+ }
+ });
+ }
+ }, false);
+ }
+
+ private void runOnMainAndLayoutSync(@NonNull final ActivityTestRule activityTestRule,
+ @NonNull final View view, @Nullable final Runnable runner, final boolean forceLayout)
+ throws Throwable {
+ final View rootView = view.getRootView();
+
+ final CountDownLatch latch = new CountDownLatch(1);
+
+ activityTestRule.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ final OnGlobalLayoutListener listener = new OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ rootView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
+ // countdown immediately since the layout we were waiting on has happened
+ latch.countDown();
+ }
+ };
+
+ rootView.getViewTreeObserver().addOnGlobalLayoutListener(listener);
+
+ if (runner != null) {
+ runner.run();
+ }
+
+ if (forceLayout) {
+ rootView.requestLayout();
+ }
+ }
+ });
+
+ try {
+ Assert.assertTrue("Expected layout pass within 5 seconds",
+ latch.await(5, TimeUnit.SECONDS));
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Test
+ public void testCanScroll() throws Throwable {
+ final int itemCount = mListView.getAdapter().getCount();
+
+ assertEquals(0, mListView.getFirstVisiblePosition());
+
+ // Verify that when we're at the top of the list, we can't scroll up but we can scroll
+ // down.
+ assertFalse(ListViewCompat.canScrollList(mListView, -1));
+ assertTrue(ListViewCompat.canScrollList(mListView, 1));
+
+ // Scroll down to the very end of the list
+ runOnMainAndLayoutSync(mActivityTestRule, mListView,
+ new Runnable() {
+ @Override
+ public void run() {
+ mListView.setStackFromBottom(true);
+ }
+ }, false);
+ assertEquals(itemCount - 1, mListView.getLastVisiblePosition());
+
+ // Verify that when we're at the bottom of the list, we can't scroll down but we can scroll
+ // up.
+ assertFalse(ListViewCompat.canScrollList(mListView, 1));
+ assertTrue(ListViewCompat.canScrollList(mListView, -1));
+ }
+}
diff --git a/compat/tests/java/android/support/v4/widget/ListViewTestActivity.java b/compat/tests/java/android/support/v4/widget/ListViewTestActivity.java
new file mode 100644
index 0000000..762889f
--- /dev/null
+++ b/compat/tests/java/android/support/v4/widget/ListViewTestActivity.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2017 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 android.support.v4.widget;
+
+import android.support.compat.test.R;
+import android.support.v4.BaseTestActivity;
+
+public class ListViewTestActivity extends BaseTestActivity {
+ @Override
+ protected int getContentViewLayoutResId() {
+ return R.layout.list_view_activity;
+ }
+}
diff --git a/compat/tests/res/layout/list_view_activity.xml b/compat/tests/res/layout/list_view_activity.xml
new file mode 100644
index 0000000..1a8d2c6
--- /dev/null
+++ b/compat/tests/res/layout/list_view_activity.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2017 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.
+ -->
+
+<ListView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/content"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:padding="48dp"
+ android:clipToPadding="false"/>
diff --git a/compat/tests/res/layout/list_view_row.xml b/compat/tests/res/layout/list_view_row.xml
new file mode 100644
index 0000000..f871960
--- /dev/null
+++ b/compat/tests/res/layout/list_view_row.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2017 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.
+ -->
+
+<TextView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="48dp"/>
\ No newline at end of file
diff --git a/core-ui/java/android/support/v4/widget/SwipeRefreshLayout.java b/core-ui/java/android/support/v4/widget/SwipeRefreshLayout.java
index 6f0a179..983683f 100644
--- a/core-ui/java/android/support/v4/widget/SwipeRefreshLayout.java
+++ b/core-ui/java/android/support/v4/widget/SwipeRefreshLayout.java
@@ -40,6 +40,7 @@
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Transformation;
import android.widget.AbsListView;
+import android.widget.ListView;
/**
* The SwipeRefreshLayout should be used whenever the user can refresh the
@@ -657,6 +658,9 @@
if (mChildScrollUpCallback != null) {
return mChildScrollUpCallback.canChildScrollUp(this, mTarget);
}
+ if (mTarget instanceof ListView) {
+ return ListViewCompat.canScrollList((ListView) mTarget, -1);
+ }
return ViewCompat.canScrollVertically(mTarget, -1);
}