summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--library/full-support/src/com/android/setupwizardlib/GlifRecyclerLayout.java6
-rw-r--r--library/full-support/src/com/android/setupwizardlib/SetupWizardRecyclerLayout.java22
-rw-r--r--library/full-support/src/com/android/setupwizardlib/template/RecyclerViewScrollHandlingDelegate.java83
-rw-r--r--library/full-support/src/com/android/setupwizardlib/util/RecyclerViewRequireScrollHelper.java75
-rw-r--r--library/full-support/test/instrumentation/src/com/android/setupwizardlib/test/RecyclerViewRequireScrollHelperTest.java146
-rw-r--r--library/full-support/test/robotest/src/com/android/setupwizardlib/template/RecyclerViewScrollHandlingDelegateTest.java89
-rw-r--r--library/main/res/layout/suw_glif_template_content.xml4
-rw-r--r--library/main/src/com/android/setupwizardlib/GlifLayout.java10
-rw-r--r--library/main/src/com/android/setupwizardlib/GlifListLayout.java6
-rw-r--r--library/main/src/com/android/setupwizardlib/SetupWizardLayout.java27
-rw-r--r--library/main/src/com/android/setupwizardlib/SetupWizardListLayout.java21
-rw-r--r--library/main/src/com/android/setupwizardlib/template/ListViewScrollHandlingDelegate.java87
-rw-r--r--library/main/src/com/android/setupwizardlib/template/RequireScrollMixin.java260
-rw-r--r--library/main/src/com/android/setupwizardlib/template/ScrollViewScrollHandlingDelegate.java80
-rw-r--r--library/main/src/com/android/setupwizardlib/util/AbstractRequireScrollHelper.java78
-rw-r--r--library/main/src/com/android/setupwizardlib/util/ListViewRequireScrollHelper.java81
-rw-r--r--library/main/src/com/android/setupwizardlib/util/RequireScrollHelper.java64
-rw-r--r--library/main/src/com/android/setupwizardlib/view/NavigationBar.java2
-rw-r--r--library/self.gradle2
-rw-r--r--library/test/instrumentation/src/com/android/setupwizardlib/test/ListViewRequireScrollHelperTest.java163
-rw-r--r--library/test/instrumentation/src/com/android/setupwizardlib/test/RequireScrollHelperTest.java125
-rw-r--r--library/test/robotest/src/com/android/setupwizardlib/template/ListViewScrollHandlingDelegateTest.java121
-rw-r--r--library/test/robotest/src/com/android/setupwizardlib/template/RequireScrollMixinTest.java168
-rw-r--r--library/test/robotest/src/com/android/setupwizardlib/template/ScrollViewScrollHandlingDelegateTest.java88
24 files changed, 1030 insertions, 778 deletions
diff --git a/library/full-support/src/com/android/setupwizardlib/GlifRecyclerLayout.java b/library/full-support/src/com/android/setupwizardlib/GlifRecyclerLayout.java
index 755baec..d1a7947 100644
--- a/library/full-support/src/com/android/setupwizardlib/GlifRecyclerLayout.java
+++ b/library/full-support/src/com/android/setupwizardlib/GlifRecyclerLayout.java
@@ -29,6 +29,8 @@ import android.view.View;
import android.view.ViewGroup;
import com.android.setupwizardlib.template.RecyclerMixin;
+import com.android.setupwizardlib.template.RecyclerViewScrollHandlingDelegate;
+import com.android.setupwizardlib.template.RequireScrollMixin;
/**
* A GLIF themed layout with a RecyclerView. {@code android:entries} can also be used to specify an
@@ -65,6 +67,10 @@ public class GlifRecyclerLayout extends GlifLayout {
private void init(Context context, AttributeSet attrs, int defStyleAttr) {
mRecyclerMixin.parseAttributes(attrs, defStyleAttr);
registerMixin(RecyclerMixin.class, mRecyclerMixin);
+
+ final RequireScrollMixin requireScrollMixin = getMixin(RequireScrollMixin.class);
+ requireScrollMixin.setScrollHandlingDelegate(
+ new RecyclerViewScrollHandlingDelegate(requireScrollMixin, getRecyclerView()));
}
@Override
diff --git a/library/full-support/src/com/android/setupwizardlib/SetupWizardRecyclerLayout.java b/library/full-support/src/com/android/setupwizardlib/SetupWizardRecyclerLayout.java
index 228bfeb..870a805 100644
--- a/library/full-support/src/com/android/setupwizardlib/SetupWizardRecyclerLayout.java
+++ b/library/full-support/src/com/android/setupwizardlib/SetupWizardRecyclerLayout.java
@@ -22,14 +22,13 @@ import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.Adapter;
import android.support.v7.widget.RecyclerView.ViewHolder;
import android.util.AttributeSet;
-import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.android.setupwizardlib.template.RecyclerMixin;
-import com.android.setupwizardlib.util.RecyclerViewRequireScrollHelper;
-import com.android.setupwizardlib.view.NavigationBar;
+import com.android.setupwizardlib.template.RecyclerViewScrollHandlingDelegate;
+import com.android.setupwizardlib.template.RequireScrollMixin;
/**
* A setup wizard layout for use with {@link android.support.v7.widget.RecyclerView}.
@@ -66,6 +65,11 @@ public class SetupWizardRecyclerLayout extends SetupWizardLayout {
private void init(Context context, AttributeSet attrs, int defStyleAttr) {
mRecyclerMixin.parseAttributes(attrs, defStyleAttr);
registerMixin(RecyclerMixin.class, mRecyclerMixin);
+
+
+ final RequireScrollMixin requireScrollMixin = getMixin(RequireScrollMixin.class);
+ requireScrollMixin.setScrollHandlingDelegate(
+ new RecyclerViewScrollHandlingDelegate(requireScrollMixin, getRecyclerView()));
}
@Override
@@ -134,18 +138,6 @@ public class SetupWizardRecyclerLayout extends SetupWizardLayout {
return super.findViewById(id);
}
- @Override
- public void requireScrollToBottom() {
- final NavigationBar navigationBar = getNavigationBar();
- final RecyclerView recyclerView = getRecyclerView();
- if (navigationBar != null && recyclerView != null) {
- RecyclerViewRequireScrollHelper.requireScroll(navigationBar, recyclerView);
- } else {
- Log.e(TAG, "Both suw_layout_navigation_bar and suw_recycler_view must exist in"
- + " the template to require scrolling.");
- }
- }
-
/**
* Sets the start inset of the divider. This will use the default divider drawable set in the
* theme and inset it {@code inset} pixels to the right (or left in RTL layouts).
diff --git a/library/full-support/src/com/android/setupwizardlib/template/RecyclerViewScrollHandlingDelegate.java b/library/full-support/src/com/android/setupwizardlib/template/RecyclerViewScrollHandlingDelegate.java
new file mode 100644
index 0000000..41fb03e
--- /dev/null
+++ b/library/full-support/src/com/android/setupwizardlib/template/RecyclerViewScrollHandlingDelegate.java
@@ -0,0 +1,83 @@
+/*
+ * 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 com.android.setupwizardlib.template;
+
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v7.widget.RecyclerView;
+import android.util.Log;
+
+import com.android.setupwizardlib.template.RequireScrollMixin.ScrollHandlingDelegate;
+
+/**
+ * {@link ScrollHandlingDelegate} which analyzes scroll events from {@link RecyclerView} and
+ * notifies {@link RequireScrollMixin} about scrollability changes.
+ */
+public class RecyclerViewScrollHandlingDelegate implements ScrollHandlingDelegate {
+
+ private static final String TAG = "RVRequireScrollMixin";
+
+ @Nullable
+ private final RecyclerView mRecyclerView;
+
+ @NonNull
+ private final RequireScrollMixin mRequireScrollMixin;
+
+ public RecyclerViewScrollHandlingDelegate(
+ @NonNull RequireScrollMixin requireScrollMixin,
+ @Nullable RecyclerView recyclerView) {
+ mRequireScrollMixin = requireScrollMixin;
+ mRecyclerView = recyclerView;
+ }
+
+ private boolean canScrollDown() {
+ if (mRecyclerView != null) {
+ // Compatibility implementation of View#canScrollVertically
+ final int offset = mRecyclerView.computeVerticalScrollOffset();
+ final int range = mRecyclerView.computeVerticalScrollRange()
+ - mRecyclerView.computeVerticalScrollExtent();
+ return range != 0 && offset < range - 1;
+ }
+ return false;
+ }
+
+ @Override
+ public void startListening() {
+ if (mRecyclerView != null) {
+ mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
+ @Override
+ public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
+ mRequireScrollMixin.notifyScrollabilityChange(canScrollDown());
+ }
+ });
+
+ if (canScrollDown()) {
+ mRequireScrollMixin.notifyScrollabilityChange(true);
+ }
+ } else {
+ Log.w(TAG, "Cannot require scroll. Recycler view is null.");
+ }
+ }
+
+ @Override
+ public void pageScrollDown() {
+ if (mRecyclerView != null) {
+ final int height = mRecyclerView.getHeight();
+ mRecyclerView.smoothScrollBy(0, height);
+ }
+ }
+}
diff --git a/library/full-support/src/com/android/setupwizardlib/util/RecyclerViewRequireScrollHelper.java b/library/full-support/src/com/android/setupwizardlib/util/RecyclerViewRequireScrollHelper.java
deleted file mode 100644
index ad9354a..0000000
--- a/library/full-support/src/com/android/setupwizardlib/util/RecyclerViewRequireScrollHelper.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright (C) 2016 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.setupwizardlib.util;
-
-import android.support.v7.widget.RecyclerView;
-
-import com.android.setupwizardlib.view.NavigationBar;
-
-/**
- * Add this helper to require the recycler view to be scrolled to the bottom, making sure that the
- * user sees all content on the screen. This will change the navigation bar to show the more button
- * instead of the next button when there is more content to be seen. When the more button is
- * clicked, the scroll view will be scrolled one page down.
- */
-public class RecyclerViewRequireScrollHelper extends AbstractRequireScrollHelper {
-
- public static void requireScroll(NavigationBar navigationBar, RecyclerView recyclerView) {
- new RecyclerViewRequireScrollHelper(navigationBar, recyclerView).requireScroll();
- }
-
- private final RecyclerView mRecyclerView;
-
- private RecyclerViewRequireScrollHelper(NavigationBar navigationBar,
- RecyclerView recyclerView) {
- super(navigationBar);
- mRecyclerView = recyclerView;
- }
-
- @Override
- protected void requireScroll() {
- super.requireScroll();
- mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
- @Override
- public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
- if (!canScrollDown()) {
- notifyScrolledToBottom();
- } else {
- notifyRequiresScroll();
- }
- }
- });
-
- if (canScrollDown()) {
- notifyRequiresScroll();
- }
- }
-
- private boolean canScrollDown() {
- // Compatibility implementation of View#canScrollVertically
- final int offset = mRecyclerView.computeVerticalScrollOffset();
- final int range = mRecyclerView.computeVerticalScrollRange()
- - mRecyclerView.computeVerticalScrollExtent();
- return range != 0 && offset < range - 1;
- }
-
- @Override
- protected void pageScrollDown() {
- final int height = mRecyclerView.getHeight();
- mRecyclerView.smoothScrollBy(0, height);
- }
-}
diff --git a/library/full-support/test/instrumentation/src/com/android/setupwizardlib/test/RecyclerViewRequireScrollHelperTest.java b/library/full-support/test/instrumentation/src/com/android/setupwizardlib/test/RecyclerViewRequireScrollHelperTest.java
deleted file mode 100644
index fae1f1a..0000000
--- a/library/full-support/test/instrumentation/src/com/android/setupwizardlib/test/RecyclerViewRequireScrollHelperTest.java
+++ /dev/null
@@ -1,146 +0,0 @@
-/*
- * Copyright (C) 2016 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.setupwizardlib.test;
-
-import static org.junit.Assert.assertEquals;
-
-import android.content.Context;
-import android.os.Build;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
-import android.support.v7.widget.RecyclerView;
-import android.view.View;
-
-import com.android.setupwizardlib.util.RecyclerViewRequireScrollHelper;
-import com.android.setupwizardlib.view.NavigationBar;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class RecyclerViewRequireScrollHelperTest {
-
- private TestRecyclerView mRecyclerView;
- private NavigationBar mNavigationBar;
-
- @Before
- public void setUp() throws Exception {
- final Context context = InstrumentationRegistry.getContext();
- mRecyclerView = new TestRecyclerView(context);
- mNavigationBar = new TestNavigationBar(context);
-
- mRecyclerView.layout(0, 0, 50, 50);
- }
-
- @Test
- public void testRequireScroll() {
- RecyclerViewRequireScrollHelper.requireScroll(mNavigationBar, mRecyclerView);
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO) {
- assertEquals("More button should be shown initially", View.VISIBLE,
- mNavigationBar.getMoreButton().getVisibility());
- assertEquals("Next button should be gone initially", View.GONE,
- mNavigationBar.getNextButton().getVisibility());
- }
- }
-
- @Test
- public void testScrolledToBottom() {
- RecyclerViewRequireScrollHelper.requireScroll(mNavigationBar, mRecyclerView);
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO) {
- assertEquals("More button should be shown when scroll is required", View.VISIBLE,
- mNavigationBar.getMoreButton().getVisibility());
- assertEquals("Next button should not be shown when scroll is required", View.GONE,
- mNavigationBar.getNextButton().getVisibility());
-
- mRecyclerView.scrollOffset = 20;
- mRecyclerView.listener.onScrolled(mRecyclerView, 0, 20);
- assertEquals("More button should be hidden when scrolled to bottom", View.GONE,
- mNavigationBar.getMoreButton().getVisibility());
- assertEquals("Next button should be shown when scrolled to bottom", View.VISIBLE,
- mNavigationBar.getNextButton().getVisibility());
- }
- }
-
- @Test
- public void testClickScrollButton() {
- RecyclerViewRequireScrollHelper.requireScroll(mNavigationBar, mRecyclerView);
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO) {
- assertEquals("ScrollView page should be initially 0", 0, mRecyclerView.scrollDistance);
- mNavigationBar.getMoreButton().performClick();
- assertEquals("ScrollView page should be scrolled by 50px",
- 50, mRecyclerView.scrollDistance);
- }
- }
-
- private static class TestRecyclerView extends RecyclerView {
-
- public int scrollOffset = 0;
- public int scrollRange = 20;
- public int scrollExtent = 0;
-
- public int scrollDistance = 0;
-
- public OnScrollListener listener;
-
- TestRecyclerView(Context context) {
- super(context);
- }
-
- @Override
- public void addOnScrollListener(OnScrollListener listener) {
- super.addOnScrollListener(listener);
- this.listener = listener;
- }
-
- @Override
- public int computeVerticalScrollOffset() {
- return scrollOffset;
- }
-
- @Override
- public int computeVerticalScrollRange() {
- return scrollRange;
- }
-
- @Override
- public int computeVerticalScrollExtent() {
- return scrollExtent;
- }
-
- @Override
- public void smoothScrollBy(int dx, int dy) {
- super.smoothScrollBy(dx, dy);
- scrollDistance += dy;
- }
- }
-
- private static class TestNavigationBar extends NavigationBar {
-
- TestNavigationBar(Context context) {
- super(context);
- }
-
- @Override
- public boolean post(Runnable action) {
- action.run();
- return true;
- }
- }
-}
diff --git a/library/full-support/test/robotest/src/com/android/setupwizardlib/template/RecyclerViewScrollHandlingDelegateTest.java b/library/full-support/test/robotest/src/com/android/setupwizardlib/template/RecyclerViewScrollHandlingDelegateTest.java
new file mode 100644
index 0000000..b509389
--- /dev/null
+++ b/library/full-support/test/robotest/src/com/android/setupwizardlib/template/RecyclerViewScrollHandlingDelegateTest.java
@@ -0,0 +1,89 @@
+/*
+ * 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 com.android.setupwizardlib.template;
+
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.robolectric.RuntimeEnvironment.application;
+
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.RecyclerView.OnScrollListener;
+
+import com.android.setupwizardlib.BuildConfig;
+import com.android.setupwizardlib.robolectric.SuwLibRobolectricTestRunner;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+
+@Config(constants = BuildConfig.class, sdk = { Config.OLDEST_SDK, Config.NEWEST_SDK })
+@RunWith(SuwLibRobolectricTestRunner.class)
+public class RecyclerViewScrollHandlingDelegateTest {
+
+ @Mock
+ private RequireScrollMixin mRequireScrollMixin;
+
+ private RecyclerView mRecyclerView;
+ private RecyclerViewScrollHandlingDelegate mDelegate;
+ private ArgumentCaptor<OnScrollListener> mListenerCaptor;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ mRecyclerView = spy(new RecyclerView(application));
+ doReturn(20).when(mRecyclerView).computeVerticalScrollRange();
+ doReturn(0).when(mRecyclerView).computeVerticalScrollExtent();
+ doReturn(0).when(mRecyclerView).computeVerticalScrollOffset();
+ mListenerCaptor = ArgumentCaptor.forClass(OnScrollListener.class);
+ doNothing().when(mRecyclerView).addOnScrollListener(mListenerCaptor.capture());
+
+ mDelegate = new RecyclerViewScrollHandlingDelegate(mRequireScrollMixin, mRecyclerView);
+ mRecyclerView.layout(0, 0, 50, 50);
+ }
+
+ @Test
+ public void testRequireScroll() {
+ mDelegate.startListening();
+ verify(mRequireScrollMixin).notifyScrollabilityChange(true);
+ }
+
+ @Test
+ public void testScrolledToBottom() {
+ mDelegate.startListening();
+ verify(mRequireScrollMixin).notifyScrollabilityChange(true);
+
+ doReturn(20).when(mRecyclerView).computeVerticalScrollOffset();
+ mListenerCaptor.getValue().onScrolled(mRecyclerView, 0, 20);
+
+ verify(mRequireScrollMixin).notifyScrollabilityChange(false);
+ }
+
+ @Test
+ public void testClickScrollButton() {
+ mDelegate.pageScrollDown();
+ verify(mRecyclerView).smoothScrollBy(anyInt(), eq(50));
+ }
+}
diff --git a/library/main/res/layout/suw_glif_template_content.xml b/library/main/res/layout/suw_glif_template_content.xml
index f5a32a6..0fe35a0 100644
--- a/library/main/res/layout/suw_glif_template_content.xml
+++ b/library/main/res/layout/suw_glif_template_content.xml
@@ -24,7 +24,7 @@
<!-- Ignore UnusedAttribute: scrollIndicators is new in M. Default to no indicators in older
versions. -->
- <ScrollView
+ <com.android.setupwizardlib.view.BottomScrollView
android:id="@+id/suw_scroll_view"
android:layout_width="match_parent"
android:layout_height="0dp"
@@ -48,7 +48,7 @@
</LinearLayout>
- </ScrollView>
+ </com.android.setupwizardlib.view.BottomScrollView>
<ViewStub
android:id="@+id/suw_layout_footer"
diff --git a/library/main/src/com/android/setupwizardlib/GlifLayout.java b/library/main/src/com/android/setupwizardlib/GlifLayout.java
index 667d699..f4d52a5 100644
--- a/library/main/src/com/android/setupwizardlib/GlifLayout.java
+++ b/library/main/src/com/android/setupwizardlib/GlifLayout.java
@@ -41,6 +41,8 @@ import com.android.setupwizardlib.template.ColoredHeaderMixin;
import com.android.setupwizardlib.template.HeaderMixin;
import com.android.setupwizardlib.template.IconMixin;
import com.android.setupwizardlib.template.ProgressBarMixin;
+import com.android.setupwizardlib.template.RequireScrollMixin;
+import com.android.setupwizardlib.template.ScrollViewScrollHandlingDelegate;
import com.android.setupwizardlib.view.StatusBarBackgroundLayout;
/**
@@ -106,6 +108,14 @@ public class GlifLayout extends TemplateLayout {
registerMixin(IconMixin.class, new IconMixin(this, attrs, defStyleAttr));
registerMixin(ProgressBarMixin.class, new ProgressBarMixin(this));
registerMixin(ButtonFooterMixin.class, new ButtonFooterMixin(this));
+ final RequireScrollMixin requireScrollMixin = new RequireScrollMixin(this);
+ registerMixin(RequireScrollMixin.class, requireScrollMixin);
+
+ final ScrollView scrollView = getScrollView();
+ if (scrollView != null) {
+ requireScrollMixin.setScrollHandlingDelegate(
+ new ScrollViewScrollHandlingDelegate(requireScrollMixin, scrollView));
+ }
TypedArray a = getContext().obtainStyledAttributes(attrs,
R.styleable.SuwGlifLayout, defStyleAttr, 0);
diff --git a/library/main/src/com/android/setupwizardlib/GlifListLayout.java b/library/main/src/com/android/setupwizardlib/GlifListLayout.java
index 14c7bd7..c6443f9 100644
--- a/library/main/src/com/android/setupwizardlib/GlifListLayout.java
+++ b/library/main/src/com/android/setupwizardlib/GlifListLayout.java
@@ -28,6 +28,8 @@ import android.widget.ListAdapter;
import android.widget.ListView;
import com.android.setupwizardlib.template.ListMixin;
+import com.android.setupwizardlib.template.ListViewScrollHandlingDelegate;
+import com.android.setupwizardlib.template.RequireScrollMixin;
/**
* A GLIF themed layout with a ListView. {@code android:entries} can also be used to specify an
@@ -70,6 +72,10 @@ public class GlifListLayout extends GlifLayout {
private void init(Context context, AttributeSet attrs, int defStyleAttr) {
mListMixin = new ListMixin(this, attrs, defStyleAttr);
registerMixin(ListMixin.class, mListMixin);
+
+ final RequireScrollMixin requireScrollMixin = getMixin(RequireScrollMixin.class);
+ requireScrollMixin.setScrollHandlingDelegate(
+ new ListViewScrollHandlingDelegate(requireScrollMixin, getListView()));
}
@Override
diff --git a/library/main/src/com/android/setupwizardlib/SetupWizardLayout.java b/library/main/src/com/android/setupwizardlib/SetupWizardLayout.java
index 2364e9b..065d2ef 100644
--- a/library/main/src/com/android/setupwizardlib/SetupWizardLayout.java
+++ b/library/main/src/com/android/setupwizardlib/SetupWizardLayout.java
@@ -42,8 +42,8 @@ import android.widget.TextView;
import com.android.setupwizardlib.template.HeaderMixin;
import com.android.setupwizardlib.template.NavigationBarMixin;
import com.android.setupwizardlib.template.ProgressBarMixin;
-import com.android.setupwizardlib.util.RequireScrollHelper;
-import com.android.setupwizardlib.view.BottomScrollView;
+import com.android.setupwizardlib.template.RequireScrollMixin;
+import com.android.setupwizardlib.template.ScrollViewScrollHandlingDelegate;
import com.android.setupwizardlib.view.Illustration;
import com.android.setupwizardlib.view.NavigationBar;
@@ -82,6 +82,14 @@ public class SetupWizardLayout extends TemplateLayout {
registerMixin(HeaderMixin.class, new HeaderMixin(this, attrs, defStyleAttr));
registerMixin(ProgressBarMixin.class, new ProgressBarMixin(this));
registerMixin(NavigationBarMixin.class, new NavigationBarMixin(this));
+ final RequireScrollMixin requireScrollMixin = new RequireScrollMixin(this);
+ registerMixin(RequireScrollMixin.class, requireScrollMixin);
+
+ final ScrollView scrollView = getScrollView();
+ if (scrollView != null) {
+ requireScrollMixin.setScrollHandlingDelegate(
+ new ScrollViewScrollHandlingDelegate(requireScrollMixin, scrollView));
+ }
final TypedArray a = getContext().obtainStyledAttributes(attrs,
R.styleable.SuwSetupWizardLayout, defStyleAttr, 0);
@@ -156,11 +164,7 @@ public class SetupWizardLayout extends TemplateLayout {
final SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
final boolean isProgressBarShown = ss.mIsProgressBarShown;
- if (isProgressBarShown) {
- showProgressBar();
- } else {
- hideProgressBar();
- }
+ setProgressBarShown(isProgressBarShown);
}
@Override
@@ -189,13 +193,12 @@ public class SetupWizardLayout extends TemplateLayout {
}
public void requireScrollToBottom() {
+ final RequireScrollMixin requireScrollMixin = getMixin(RequireScrollMixin.class);
final NavigationBar navigationBar = getNavigationBar();
- final ScrollView scrollView = getScrollView();
- if (navigationBar != null && (scrollView instanceof BottomScrollView)) {
- RequireScrollHelper.requireScroll(navigationBar, (BottomScrollView) scrollView);
+ if (navigationBar != null) {
+ requireScrollMixin.requireScrollWithNavigationBar(navigationBar);
} else {
- Log.e(TAG, "Both suw_layout_navigation_bar and suw_bottom_scroll_view must exist in"
- + " the template to require scrolling.");
+ Log.e(TAG, "Cannot require scroll. Navigation bar is null.");
}
}
diff --git a/library/main/src/com/android/setupwizardlib/SetupWizardListLayout.java b/library/main/src/com/android/setupwizardlib/SetupWizardListLayout.java
index bb96d58..0457451 100644
--- a/library/main/src/com/android/setupwizardlib/SetupWizardListLayout.java
+++ b/library/main/src/com/android/setupwizardlib/SetupWizardListLayout.java
@@ -21,7 +21,6 @@ import android.content.Context;
import android.graphics.drawable.Drawable;
import android.os.Build.VERSION_CODES;
import android.util.AttributeSet;
-import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -29,8 +28,8 @@ import android.widget.ListAdapter;
import android.widget.ListView;
import com.android.setupwizardlib.template.ListMixin;
-import com.android.setupwizardlib.util.ListViewRequireScrollHelper;
-import com.android.setupwizardlib.view.NavigationBar;
+import com.android.setupwizardlib.template.ListViewScrollHandlingDelegate;
+import com.android.setupwizardlib.template.RequireScrollMixin;
public class SetupWizardListLayout extends SetupWizardLayout {
@@ -65,6 +64,10 @@ public class SetupWizardListLayout extends SetupWizardLayout {
private void init(Context context, AttributeSet attrs, int defStyleAttr) {
mListMixin = new ListMixin(this, attrs, defStyleAttr);
registerMixin(ListMixin.class, mListMixin);
+
+ final RequireScrollMixin requireScrollMixin = getMixin(RequireScrollMixin.class);
+ requireScrollMixin.setScrollHandlingDelegate(
+ new ListViewScrollHandlingDelegate(requireScrollMixin, getListView()));
}
@Override
@@ -101,18 +104,6 @@ public class SetupWizardListLayout extends SetupWizardLayout {
return mListMixin.getAdapter();
}
- @Override
- public void requireScrollToBottom() {
- final NavigationBar navigationBar = getNavigationBar();
- final ListView listView = getListView();
- if (navigationBar != null && listView != null) {
- ListViewRequireScrollHelper.requireScroll(navigationBar, listView);
- } else {
- Log.e(TAG, "Both suw_layout_navigation_bar and list must exist in"
- + " the template to require scrolling.");
- }
- }
-
/**
* Sets the start inset of the divider. This will use the default divider drawable set in the
* theme and inset it {@code inset} pixels to the right (or left in RTL layouts).
diff --git a/library/main/src/com/android/setupwizardlib/template/ListViewScrollHandlingDelegate.java b/library/main/src/com/android/setupwizardlib/template/ListViewScrollHandlingDelegate.java
new file mode 100644
index 0000000..f55d06d
--- /dev/null
+++ b/library/main/src/com/android/setupwizardlib/template/ListViewScrollHandlingDelegate.java
@@ -0,0 +1,87 @@
+/*
+ * 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 com.android.setupwizardlib.template;
+
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.util.Log;
+import android.widget.AbsListView;
+import android.widget.ListAdapter;
+import android.widget.ListView;
+
+import com.android.setupwizardlib.template.RequireScrollMixin.ScrollHandlingDelegate;
+
+/**
+ * {@link ScrollHandlingDelegate} which analyzes scroll events from {@link ListView} and
+ * notifies {@link RequireScrollMixin} about scrollability changes.
+ */
+public class ListViewScrollHandlingDelegate implements ScrollHandlingDelegate,
+ AbsListView.OnScrollListener {
+
+ private static final String TAG = "ListViewDelegate";
+
+ private static final int SCROLL_DURATION = 500;
+
+ @NonNull
+ private final RequireScrollMixin mRequireScrollMixin;
+
+ @Nullable
+ private final ListView mListView;
+
+ public ListViewScrollHandlingDelegate(
+ @NonNull RequireScrollMixin requireScrollMixin,
+ @Nullable ListView listView) {
+ mRequireScrollMixin = requireScrollMixin;
+ mListView = listView;
+ }
+
+ @Override
+ public void startListening() {
+ if (mListView != null) {
+ mListView.setOnScrollListener(this);
+
+ final ListAdapter adapter = mListView.getAdapter();
+ if (mListView.getLastVisiblePosition() < adapter.getCount()) {
+ mRequireScrollMixin.notifyScrollabilityChange(true);
+ }
+ } else {
+ Log.w(TAG, "Cannot require scroll. List view is null");
+ }
+ }
+
+ @Override
+ public void pageScrollDown() {
+ if (mListView != null) {
+ final int height = mListView.getHeight();
+ mListView.smoothScrollBy(height, SCROLL_DURATION);
+ }
+ }
+
+ @Override
+ public void onScrollStateChanged(AbsListView view, int scrollState) {
+ }
+
+ @Override
+ public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
+ int totalItemCount) {
+ if (firstVisibleItem + visibleItemCount >= totalItemCount) {
+ mRequireScrollMixin.notifyScrollabilityChange(false);
+ } else {
+ mRequireScrollMixin.notifyScrollabilityChange(true);
+ }
+ }
+}
diff --git a/library/main/src/com/android/setupwizardlib/template/RequireScrollMixin.java b/library/main/src/com/android/setupwizardlib/template/RequireScrollMixin.java
new file mode 100644
index 0000000..231c064
--- /dev/null
+++ b/library/main/src/com/android/setupwizardlib/template/RequireScrollMixin.java
@@ -0,0 +1,260 @@
+/*
+ * 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 com.android.setupwizardlib.template;
+
+import android.os.Handler;
+import android.os.Looper;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.StringRes;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+
+import com.android.setupwizardlib.TemplateLayout;
+import com.android.setupwizardlib.view.NavigationBar;
+
+/**
+ * A mixin to require the a scrollable container (BottomScrollView, RecyclerView or ListView) to
+ * be scrolled to bottom, making sure that the user sees all content above and below the fold.
+ */
+public class RequireScrollMixin implements Mixin {
+
+ /* static section */
+
+ /**
+ * Listener for when the require-scroll state changes. Note that this only requires the user to
+ * scroll to the bottom once - if the user scrolled to the bottom and back-up, scrolling to
+ * bottom is not required again.
+ */
+ public interface OnRequireScrollStateChangedListener {
+
+ /**
+ * Called when require-scroll state changed.
+ *
+ * @param scrollNeeded True if the user should be required to scroll to bottom.
+ */
+ void onRequireScrollStateChanged(boolean scrollNeeded);
+ }
+
+ /**
+ * A delegate to detect scrollability changes and to scroll the page. This provides a layer
+ * of abstraction for BottomScrollView, RecyclerView and ListView. The delegate should call
+ * {@link #notifyScrollabilityChange(boolean)} when the view scrollability is changed.
+ */
+ interface ScrollHandlingDelegate {
+
+ /**
+ * Starts listening to scrollability changes at the target scrollable container.
+ */
+ void startListening();
+
+ /**
+ * Scroll the page content down by one page.
+ */
+ void pageScrollDown();
+ }
+
+ /* non-static section */
+
+ @NonNull
+ private final TemplateLayout mTemplateLayout;
+
+ private final Handler mHandler = new Handler(Looper.getMainLooper());
+
+ private boolean mRequiringScrollToBottom = false;
+
+ // Whether the user have seen the more button yet.
+ private boolean mEverScrolledToBottom = false;
+
+ private ScrollHandlingDelegate mDelegate;
+
+ @Nullable
+ private OnRequireScrollStateChangedListener mListener;
+
+ /**
+ * @param templateLayout The template containing this mixin
+ */
+ public RequireScrollMixin(@NonNull TemplateLayout templateLayout) {
+ mTemplateLayout = templateLayout;
+ }
+
+ /**
+ * Sets the delegate to handle scrolling. The type of delegate should depend on whether the
+ * scrolling view is a BottomScrollView, RecyclerView or ListView.
+ */
+ public void setScrollHandlingDelegate(@NonNull ScrollHandlingDelegate delegate) {
+ mDelegate = delegate;
+ }
+
+ /**
+ * Listen to require scroll state changes. When scroll is required,
+ * {@link OnRequireScrollStateChangedListener#onRequireScrollStateChanged(boolean)} is called
+ * with {@code true}, and vice versa.
+ */
+ public void setOnRequireScrollStateChangedListener(
+ @Nullable OnRequireScrollStateChangedListener listener) {
+ mListener = listener;
+ }
+
+ /**
+ * @return The scroll state listener previously set, or {@code null} if none is registered.
+ */
+ public OnRequireScrollStateChangedListener getOnRequireScrollStateChangedListener() {
+ return mListener;
+ }
+
+ /**
+ * Creates an {@link OnClickListener} which if scrolling is required, will scroll the page down,
+ * and if scrolling is not required, delegates to the wrapped {@code listener}. Note that you
+ * should call {@link #requireScroll()} as well in order to start requiring scrolling.
+ *
+ * @param listener The listener to be invoked when scrolling is not needed and the user taps on
+ * the button. If {@code null}, the click listener will be a no-op when scroll
+ * is not required.
+ * @return A new {@link OnClickListener} which will scroll the page down or delegate to the
+ * given listener depending on the current require-scroll state.
+ */
+ public OnClickListener createOnClickListener(@Nullable final OnClickListener listener) {
+ return new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ if (mRequiringScrollToBottom) {
+ mDelegate.pageScrollDown();
+ } else if (listener != null) {
+ listener.onClick(view);
+ }
+ }
+ };
+ }
+
+ /**
+ * Coordinate with the given navigation bar to require scrolling on the page. The more button
+ * will be shown instead of the next button while scrolling is required.
+ */
+ public void requireScrollWithNavigationBar(@NonNull final NavigationBar navigationBar) {
+ setOnRequireScrollStateChangedListener(
+ new OnRequireScrollStateChangedListener() {
+ @Override
+ public void onRequireScrollStateChanged(boolean scrollNeeded) {
+ navigationBar.getMoreButton()
+ .setVisibility(scrollNeeded ? View.VISIBLE : View.GONE);
+ navigationBar.getNextButton()
+ .setVisibility(scrollNeeded ? View.GONE : View.VISIBLE);
+ }
+ });
+ navigationBar.getMoreButton().setOnClickListener(createOnClickListener(null));
+ requireScroll();
+ }
+
+ /**
+ * @see #requireScrollWithButton(Button, CharSequence, OnClickListener)
+ */
+ public void requireScrollWithButton(
+ @NonNull Button button,
+ @StringRes int moreText,
+ @Nullable OnClickListener onClickListener) {
+ requireScrollWithButton(button, button.getContext().getText(moreText), onClickListener);
+ }
+
+ /**
+ * Use the given {@code button} to require scrolling. When scrolling is required, the button
+ * label will change to {@code moreText}, and tapping the button will cause the page to scroll
+ * down.
+ *
+ * <p>Note: Calling {@link View#setOnClickListener} on the button after this method will remove
+ * its link to the require-scroll mechanism. If you need to do that, obtain the click listener
+ * from {@link #createOnClickListener(OnClickListener)}.
+ *
+ * <p>Note: The normal button label is taken from the button's text at the time of calling this
+ * method. Calling {@link android.widget.TextView#setText} after calling this method causes
+ * undefined behavior.
+ *
+ * @param button The button to use for require scroll. The button's "normal" label is taken from
+ * the text at the time of calling this method, and the click listener of it will
+ * be replaced.
+ * @param moreText The button label when scroll is required.
+ * @param onClickListener The listener for clicks when scrolling is not required.
+ */
+ public void requireScrollWithButton(
+ @NonNull final Button button,
+ final CharSequence moreText,
+ @Nullable OnClickListener onClickListener) {
+ final CharSequence nextText = button.getText();
+ button.setOnClickListener(createOnClickListener(onClickListener));
+ setOnRequireScrollStateChangedListener(new OnRequireScrollStateChangedListener() {
+ @Override
+ public void onRequireScrollStateChanged(boolean scrollNeeded) {
+ button.setText(scrollNeeded ? moreText : nextText);
+ }
+ });
+ requireScroll();
+ }
+
+ /**
+ * @return True if scrolling is required. Note that this mixin only requires the user to
+ * scroll to the bottom once - if the user scrolled to the bottom and back-up, scrolling to
+ * bottom is not required again.
+ */
+ public boolean isScrollingRequired() {
+ return mRequiringScrollToBottom;
+ }
+
+ /**
+ * Start requiring scrolling on the layout. After calling this method, this mixin will start
+ * listening to scroll events from the scrolling container, and call
+ * {@link OnRequireScrollStateChangedListener} when the scroll state changes.
+ */
+ public void requireScroll() {
+ mDelegate.startListening();
+ }
+
+ /**
+ * {@link ScrollHandlingDelegate} should call this method when the scrollability of the
+ * scrolling container changed, so this mixin can recompute whether scrolling should be
+ * required.
+ *
+ * @param canScrollDown True if the view can scroll down further.
+ */
+ void notifyScrollabilityChange(boolean canScrollDown) {
+ if (canScrollDown == mRequiringScrollToBottom) {
+ // Already at the desired require-scroll state
+ return;
+ }
+ if (canScrollDown) {
+ if (!mEverScrolledToBottom) {
+ postScrollStateChange(true);
+ mRequiringScrollToBottom = true;
+ }
+ } else {
+ postScrollStateChange(false);
+ mRequiringScrollToBottom = false;
+ mEverScrolledToBottom = true;
+ }
+ }
+
+ private void postScrollStateChange(final boolean scrollNeeded) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (mListener != null) {
+ mListener.onRequireScrollStateChanged(scrollNeeded);
+ }
+ }
+ });
+ }
+}
diff --git a/library/main/src/com/android/setupwizardlib/template/ScrollViewScrollHandlingDelegate.java b/library/main/src/com/android/setupwizardlib/template/ScrollViewScrollHandlingDelegate.java
new file mode 100644
index 0000000..d159465
--- /dev/null
+++ b/library/main/src/com/android/setupwizardlib/template/ScrollViewScrollHandlingDelegate.java
@@ -0,0 +1,80 @@
+/*
+ * 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 com.android.setupwizardlib.template;
+
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.util.Log;
+import android.widget.ScrollView;
+
+import com.android.setupwizardlib.template.RequireScrollMixin.ScrollHandlingDelegate;
+import com.android.setupwizardlib.view.BottomScrollView;
+import com.android.setupwizardlib.view.BottomScrollView.BottomScrollListener;
+
+/**
+ * {@link ScrollHandlingDelegate} which analyzes scroll events from {@link BottomScrollView} and
+ * notifies {@link RequireScrollMixin} about scrollability changes.
+ */
+public class ScrollViewScrollHandlingDelegate
+ implements ScrollHandlingDelegate, BottomScrollListener {
+
+ private static final String TAG = "ScrollViewDelegate";
+
+ @NonNull
+ private final RequireScrollMixin mRequireScrollMixin;
+
+ @Nullable
+ private final BottomScrollView mScrollView;
+
+ public ScrollViewScrollHandlingDelegate(
+ @NonNull RequireScrollMixin requireScrollMixin,
+ @Nullable ScrollView scrollView) {
+ mRequireScrollMixin = requireScrollMixin;
+ if (scrollView instanceof BottomScrollView) {
+ mScrollView = (BottomScrollView) scrollView;
+ } else {
+ Log.w(TAG, "Cannot set non-BottomScrollView. Found=" + scrollView);
+ mScrollView = null;
+ }
+ }
+
+ @Override
+ public void onScrolledToBottom() {
+ mRequireScrollMixin.notifyScrollabilityChange(false);
+ }
+
+ @Override
+ public void onRequiresScroll() {
+ mRequireScrollMixin.notifyScrollabilityChange(true);
+ }
+
+ @Override
+ public void startListening() {
+ if (mScrollView != null) {
+ mScrollView.setBottomScrollListener(this);
+ } else {
+ Log.w(TAG, "Cannot require scroll. Scroll view is null.");
+ }
+ }
+
+ @Override
+ public void pageScrollDown() {
+ if (mScrollView != null) {
+ mScrollView.pageScroll(ScrollView.FOCUS_DOWN);
+ }
+ }
+}
diff --git a/library/main/src/com/android/setupwizardlib/util/AbstractRequireScrollHelper.java b/library/main/src/com/android/setupwizardlib/util/AbstractRequireScrollHelper.java
deleted file mode 100644
index 2697371..0000000
--- a/library/main/src/com/android/setupwizardlib/util/AbstractRequireScrollHelper.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright (C) 2016 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.setupwizardlib.util;
-
-import android.view.View;
-
-import com.android.setupwizardlib.view.NavigationBar;
-
-/**
- * Add this helper to require the scroll view to be scrolled to the bottom, making sure that the
- * user sees all content on the screen. This will change the navigation bar to show the more button
- * instead of the next button when there is more content to be seen. When the more button is
- * clicked, the scroll view will be scrolled one page down.
- */
-public abstract class AbstractRequireScrollHelper implements View.OnClickListener {
-
- private final NavigationBar mNavigationBar;
-
- private boolean mScrollNeeded;
- // Whether the user have seen the more button yet.
- private boolean mScrollNotified = false;
-
- protected AbstractRequireScrollHelper(NavigationBar navigationBar) {
- mNavigationBar = navigationBar;
- }
-
- protected void requireScroll() {
- mNavigationBar.getMoreButton().setOnClickListener(this);
- }
-
- protected void notifyScrolledToBottom() {
- if (mScrollNeeded) {
- mNavigationBar.post(new Runnable() {
- @Override
- public void run() {
- mNavigationBar.getNextButton().setVisibility(View.VISIBLE);
- mNavigationBar.getMoreButton().setVisibility(View.GONE);
- }
- });
- mScrollNeeded = false;
- mScrollNotified = true;
- }
- }
-
- protected void notifyRequiresScroll() {
- if (!mScrollNeeded && !mScrollNotified) {
- mNavigationBar.post(new Runnable() {
- @Override
- public void run() {
- mNavigationBar.getNextButton().setVisibility(View.GONE);
- mNavigationBar.getMoreButton().setVisibility(View.VISIBLE);
- }
- });
- mScrollNeeded = true;
- }
- }
-
- @Override
- public void onClick(View view) {
- pageScrollDown();
- }
-
- protected abstract void pageScrollDown();
-}
diff --git a/library/main/src/com/android/setupwizardlib/util/ListViewRequireScrollHelper.java b/library/main/src/com/android/setupwizardlib/util/ListViewRequireScrollHelper.java
deleted file mode 100644
index 7877569..0000000
--- a/library/main/src/com/android/setupwizardlib/util/ListViewRequireScrollHelper.java
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * Copyright (C) 2016 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.setupwizardlib.util;
-
-import android.os.Build;
-import android.widget.AbsListView;
-import android.widget.ListAdapter;
-import android.widget.ListView;
-
-import com.android.setupwizardlib.view.NavigationBar;
-
-/**
- * Add this helper to require the list view to be scrolled to the bottom, making sure that the
- * user sees all content on the screen. This will change the navigation bar to show the more button
- * instead of the next button when there is more content to be seen. When the more button is
- * clicked, the list view will be scrolled one page down.
- */
-public class ListViewRequireScrollHelper extends AbstractRequireScrollHelper
- implements AbsListView.OnScrollListener {
-
- public static void requireScroll(NavigationBar navigationBar, ListView listView) {
- new ListViewRequireScrollHelper(navigationBar, listView).requireScroll();
- }
-
- private final ListView mListView;
-
- private ListViewRequireScrollHelper(NavigationBar navigationBar, ListView listView) {
- super(navigationBar);
- mListView = listView;
- }
-
- @Override
- protected void requireScroll() {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO) {
- // APIs to scroll a list only exists on Froyo or above.
- super.requireScroll();
- mListView.setOnScrollListener(this);
-
- final ListAdapter adapter = mListView.getAdapter();
- if (mListView.getLastVisiblePosition() < adapter.getCount()) {
- notifyRequiresScroll();
- }
- }
- }
-
- @Override
- protected void pageScrollDown() {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO) {
- final int height = mListView.getHeight();
- mListView.smoothScrollBy(height, 500);
- }
- }
-
- @Override
- public void onScrollStateChanged(AbsListView view, int scrollState) {
- }
-
- @Override
- public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
- int totalItemCount) {
- if (firstVisibleItem + visibleItemCount >= totalItemCount) {
- notifyScrolledToBottom();
- } else {
- notifyRequiresScroll();
- }
- }
-}
diff --git a/library/main/src/com/android/setupwizardlib/util/RequireScrollHelper.java b/library/main/src/com/android/setupwizardlib/util/RequireScrollHelper.java
deleted file mode 100644
index cce336f..0000000
--- a/library/main/src/com/android/setupwizardlib/util/RequireScrollHelper.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2015 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.setupwizardlib.util;
-
-import android.widget.ScrollView;
-
-import com.android.setupwizardlib.view.BottomScrollView;
-import com.android.setupwizardlib.view.NavigationBar;
-
-/**
- * Add this helper to require the scroll view to be scrolled to the bottom, making sure that the
- * user sees all content on the screen. This will change the navigation bar to show the more button
- * instead of the next button when there is more content to be seen. When the more button is
- * clicked, the scroll view will be scrolled one page down.
- */
-public class RequireScrollHelper extends AbstractRequireScrollHelper
- implements BottomScrollView.BottomScrollListener {
-
- public static void requireScroll(NavigationBar navigationBar, BottomScrollView scrollView) {
- new RequireScrollHelper(navigationBar, scrollView).requireScroll();
- }
-
- private final BottomScrollView mScrollView;
-
- private RequireScrollHelper(NavigationBar navigationBar, BottomScrollView scrollView) {
- super(navigationBar);
- mScrollView = scrollView;
- }
-
- @Override
- protected void requireScroll() {
- super.requireScroll();
- mScrollView.setBottomScrollListener(this);
- }
-
- @Override
- protected void pageScrollDown() {
- mScrollView.pageScroll(ScrollView.FOCUS_DOWN);
- }
-
- @Override
- public void onScrolledToBottom() {
- notifyScrolledToBottom();
- }
-
- @Override
- public void onRequiresScroll() {
- notifyRequiresScroll();
- }
-}
diff --git a/library/main/src/com/android/setupwizardlib/view/NavigationBar.java b/library/main/src/com/android/setupwizardlib/view/NavigationBar.java
index 9bb123f..2a1dd28 100644
--- a/library/main/src/com/android/setupwizardlib/view/NavigationBar.java
+++ b/library/main/src/com/android/setupwizardlib/view/NavigationBar.java
@@ -35,7 +35,7 @@ import com.android.setupwizardlib.R;
* next button. By default, the more button is hidden, and typically the next button will be hidden
* if the more button is shown.
*
- * @see com.android.setupwizardlib.util.RequireScrollHelper
+ * @see com.android.setupwizardlib.template.RequireScrollMixin
*/
public class NavigationBar extends LinearLayout implements View.OnClickListener {
diff --git a/library/self.gradle b/library/self.gradle
index d5ce0d3..f6d14af 100644
--- a/library/self.gradle
+++ b/library/self.gradle
@@ -43,7 +43,7 @@ android.sourceSets {
}
testGingerbreadCompat {
- java.srcDirs = ['eclair-mr1/test/robotest/src']
+ java.srcDirs = ['eclair-mr1/test/robotest/src', 'full-support/test/robotest/src']
}
}
android.defaultConfig.testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
diff --git a/library/test/instrumentation/src/com/android/setupwizardlib/test/ListViewRequireScrollHelperTest.java b/library/test/instrumentation/src/com/android/setupwizardlib/test/ListViewRequireScrollHelperTest.java
deleted file mode 100644
index 58ceb6b..0000000
--- a/library/test/instrumentation/src/com/android/setupwizardlib/test/ListViewRequireScrollHelperTest.java
+++ /dev/null
@@ -1,163 +0,0 @@
-/*
- * Copyright (C) 2016 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.setupwizardlib.test;
-
-import static org.junit.Assert.assertEquals;
-
-import android.content.Context;
-import android.os.Build;
-import android.os.SystemClock;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.BaseAdapter;
-import android.widget.ListView;
-
-import com.android.setupwizardlib.util.ListViewRequireScrollHelper;
-import com.android.setupwizardlib.view.NavigationBar;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class ListViewRequireScrollHelperTest {
-
- private TestListView mListView;
- private NavigationBar mNavigationBar;
-
- @Before
- public void setUp() throws Exception {
- mListView = new TestListView(InstrumentationRegistry.getTargetContext());
- mNavigationBar = new TestNavigationBar(InstrumentationRegistry.getTargetContext());
-
- mListView.layout(0, 0, 50, 50);
- }
-
- @Test
- public void testRequireScroll() throws Throwable {
- ListViewRequireScrollHelper.requireScroll(mNavigationBar, mListView);
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO) {
- assertEquals("More button should be shown initially", View.VISIBLE,
- mNavigationBar.getMoreButton().getVisibility());
- assertEquals("Next button should be gone initially", View.GONE,
- mNavigationBar.getNextButton().getVisibility());
- }
- }
-
- @Test
- public void testScrolledToBottom() throws Throwable {
- ListViewRequireScrollHelper.requireScroll(mNavigationBar, mListView);
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO) {
- SystemClock.sleep(500);
- assertEquals("More button should be shown when scroll is required", View.VISIBLE,
- mNavigationBar.getMoreButton().getVisibility());
- assertEquals("Next button should not be shown when scroll is required", View.GONE,
- mNavigationBar.getNextButton().getVisibility());
-
- InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
- @Override
- public void run() {
- mListView.lastVisiblePosition = 20;
- mListView.listener.onScroll(mListView, 2, 20, 20);
- }
- });
- SystemClock.sleep(500);
- assertEquals("More button should be hidden when scrolled to bottom", View.GONE,
- mNavigationBar.getMoreButton().getVisibility());
- assertEquals("Next button should be shown when scrolled to bottom", View.VISIBLE,
- mNavigationBar.getNextButton().getVisibility());
- }
- }
-
- @Test
- public void testClickScrollButton() throws Throwable {
- ListViewRequireScrollHelper.requireScroll(mNavigationBar, mListView);
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO) {
- assertEquals("ScrollView page should be initially 0", 0, mListView.scrollDistance);
- mNavigationBar.getMoreButton().performClick();
- assertEquals("ScrollView page should be scrolled by 50px",
- 50, mListView.scrollDistance);
- }
- }
-
- private static class TestListView extends ListView {
-
- public int lastVisiblePosition = 0;
- public int scrollDistance = 0;
- public OnScrollListener listener;
-
- TestListView(Context context) {
- super(context);
- setAdapter(new BaseAdapter() {
- @Override
- public int getCount() {
- return 20;
- }
-
- @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) {
- return new View(parent.getContext());
- }
- });
- }
-
- @Override
- public int getLastVisiblePosition() {
- return lastVisiblePosition;
- }
-
- @Override
- public void smoothScrollBy(int distance, int duration) {
- super.smoothScrollBy(distance, duration);
- scrollDistance += distance;
- }
-
- @Override
- public void setOnScrollListener(OnScrollListener l) {
- super.setOnScrollListener(l);
- listener = l;
- }
- }
-
- private static class TestNavigationBar extends NavigationBar {
-
- TestNavigationBar(Context context) {
- super(context);
- }
-
- @Override
- public boolean post(Runnable action) {
- // Make the post action synchronous
- action.run();
- return true;
- }
- }
-}
diff --git a/library/test/instrumentation/src/com/android/setupwizardlib/test/RequireScrollHelperTest.java b/library/test/instrumentation/src/com/android/setupwizardlib/test/RequireScrollHelperTest.java
deleted file mode 100644
index f5e4bbd..0000000
--- a/library/test/instrumentation/src/com/android/setupwizardlib/test/RequireScrollHelperTest.java
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * Copyright (C) 2015 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.setupwizardlib.test;
-
-import static org.junit.Assert.assertEquals;
-
-import android.content.Context;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.SmallTest;
-import android.support.test.runner.AndroidJUnit4;
-import android.view.View;
-
-import com.android.setupwizardlib.util.RequireScrollHelper;
-import com.android.setupwizardlib.view.BottomScrollView;
-import com.android.setupwizardlib.view.NavigationBar;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class RequireScrollHelperTest {
-
- private TestBottomScrollView mScrollView;
- private NavigationBar mNavigationBar;
-
- @Before
- public void setUp() throws Exception {
- mScrollView = new TestBottomScrollView(InstrumentationRegistry.getContext());
- mNavigationBar = new TestNavigationBar(InstrumentationRegistry.getContext());
- }
-
- @Test
- public void testRequireScroll() {
- RequireScrollHelper.requireScroll(mNavigationBar, mScrollView);
- assertEquals("More button should be gone initially", View.GONE,
- mNavigationBar.getMoreButton().getVisibility());
- assertEquals("Next button should be shown", View.VISIBLE,
- mNavigationBar.getNextButton().getVisibility());
-
- mScrollView.listener.onRequiresScroll();
- assertEquals("More button should be shown when scroll is required", View.VISIBLE,
- mNavigationBar.getMoreButton().getVisibility());
- assertEquals("Next button should not be shown when scroll is required", View.GONE,
- mNavigationBar.getNextButton().getVisibility());
- }
-
- @Test
- public void testScrolledToBottom() {
- RequireScrollHelper.requireScroll(mNavigationBar, mScrollView);
- mScrollView.listener.onRequiresScroll();
- assertEquals("More button should be shown when scroll is required", View.VISIBLE,
- mNavigationBar.getMoreButton().getVisibility());
- assertEquals("Next button should not be shown when scroll is required", View.GONE,
- mNavigationBar.getNextButton().getVisibility());
-
- mScrollView.listener.onScrolledToBottom();
- assertEquals("More button should be hidden when scrolled to bottom", View.GONE,
- mNavigationBar.getMoreButton().getVisibility());
- assertEquals("Next button should be shown when scrolled to bottom", View.VISIBLE,
- mNavigationBar.getNextButton().getVisibility());
- }
-
- @Test
- public void testClickScrollButton() {
- RequireScrollHelper.requireScroll(mNavigationBar, mScrollView);
- assertEquals("ScrollView page should be initially 0", 0, mScrollView.page);
- mScrollView.listener.onRequiresScroll();
- mNavigationBar.getMoreButton().performClick();
- assertEquals("ScrollView page should be scrolled by 1", 1, mScrollView.page);
- }
-
- private static class TestBottomScrollView extends BottomScrollView {
-
- public BottomScrollListener listener;
- public int page = 0;
-
- TestBottomScrollView(Context context) {
- super(context);
- }
-
- @Override
- public void setBottomScrollListener(BottomScrollListener listener) {
- this.listener = listener;
- }
-
- @Override
- public boolean pageScroll(int direction) {
- if (direction == FOCUS_DOWN) {
- page++;
- } else if (direction == FOCUS_UP) {
- page--;
- }
- return super.pageScroll(direction);
- }
- }
-
- private static class TestNavigationBar extends NavigationBar {
-
- TestNavigationBar(Context context) {
- super(context);
- }
-
- @Override
- public boolean post(Runnable action) {
- action.run();
- return true;
- }
- }
-}
diff --git a/library/test/robotest/src/com/android/setupwizardlib/template/ListViewScrollHandlingDelegateTest.java b/library/test/robotest/src/com/android/setupwizardlib/template/ListViewScrollHandlingDelegateTest.java
new file mode 100644
index 0000000..fa81dc0
--- /dev/null
+++ b/library/test/robotest/src/com/android/setupwizardlib/template/ListViewScrollHandlingDelegateTest.java
@@ -0,0 +1,121 @@
+/*
+ * 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 com.android.setupwizardlib.template;
+
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.robolectric.RuntimeEnvironment.application;
+
+import android.content.Context;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AbsListView.OnScrollListener;
+import android.widget.BaseAdapter;
+import android.widget.ListView;
+
+import com.android.setupwizardlib.BuildConfig;
+import com.android.setupwizardlib.robolectric.SuwLibRobolectricTestRunner;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+
+@Config(constants = BuildConfig.class, sdk = { Config.OLDEST_SDK, Config.NEWEST_SDK })
+@RunWith(SuwLibRobolectricTestRunner.class)
+public class ListViewScrollHandlingDelegateTest {
+
+ @Mock
+ private RequireScrollMixin mRequireScrollMixin;
+
+ private ListView mListView;
+ private ListViewScrollHandlingDelegate mDelegate;
+ private ArgumentCaptor<OnScrollListener> mListenerCaptor;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ mListView = spy(new TestListView(application));
+ mDelegate = new ListViewScrollHandlingDelegate(mRequireScrollMixin, mListView);
+
+ mListenerCaptor = ArgumentCaptor.forClass(OnScrollListener.class);
+ doNothing().when(mListView).setOnScrollListener(mListenerCaptor.capture());
+
+ mListView.layout(0, 0, 50, 50);
+ }
+
+ @Test
+ public void testRequireScroll() throws Throwable {
+ mDelegate.startListening();
+
+ verify(mRequireScrollMixin).notifyScrollabilityChange(true);
+ }
+
+ @Test
+ public void testScrolledToBottom() throws Throwable {
+ mDelegate.startListening();
+
+ verify(mRequireScrollMixin).notifyScrollabilityChange(true);
+
+ doReturn(20).when(mListView).getLastVisiblePosition();
+ mListenerCaptor.getValue().onScroll(mListView, 2, 20, 20);
+
+ verify(mRequireScrollMixin).notifyScrollabilityChange(false);
+ }
+
+ @Test
+ public void testPageScrollDown() throws Throwable {
+ mDelegate.pageScrollDown();
+ verify(mListView).smoothScrollBy(eq(50), anyInt());
+ }
+
+ private static class TestListView extends ListView {
+
+ TestListView(Context context) {
+ super(context);
+ setAdapter(new BaseAdapter() {
+ @Override
+ public int getCount() {
+ return 20;
+ }
+
+ @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) {
+ return new View(parent.getContext());
+ }
+ });
+ }
+ }
+}
diff --git a/library/test/robotest/src/com/android/setupwizardlib/template/RequireScrollMixinTest.java b/library/test/robotest/src/com/android/setupwizardlib/template/RequireScrollMixinTest.java
new file mode 100644
index 0000000..8e39c43
--- /dev/null
+++ b/library/test/robotest/src/com/android/setupwizardlib/template/RequireScrollMixinTest.java
@@ -0,0 +1,168 @@
+/*
+ * 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 com.android.setupwizardlib.template;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.robolectric.RuntimeEnvironment.application;
+
+import android.annotation.SuppressLint;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+
+import com.android.setupwizardlib.BuildConfig;
+import com.android.setupwizardlib.TemplateLayout;
+import com.android.setupwizardlib.robolectric.SuwLibRobolectricTestRunner;
+import com.android.setupwizardlib.template.RequireScrollMixin.OnRequireScrollStateChangedListener;
+import com.android.setupwizardlib.template.RequireScrollMixin.ScrollHandlingDelegate;
+import com.android.setupwizardlib.view.NavigationBar;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+
+@Config(constants = BuildConfig.class, sdk = { Config.OLDEST_SDK, Config.NEWEST_SDK })
+@RunWith(SuwLibRobolectricTestRunner.class)
+public class RequireScrollMixinTest {
+
+ @Mock
+ private TemplateLayout mTemplateLayout;
+
+ @Mock
+ private ScrollHandlingDelegate mDelegate;
+
+ private RequireScrollMixin mRequireScrollMixin;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ doReturn(application).when(mTemplateLayout).getContext();
+ mRequireScrollMixin = new RequireScrollMixin(mTemplateLayout);
+ mRequireScrollMixin.setScrollHandlingDelegate(mDelegate);
+ }
+
+ @Test
+ public void testRequireScroll() {
+ mRequireScrollMixin.requireScroll();
+
+ verify(mDelegate).startListening();
+ }
+
+ @Test
+ public void testScrollStateChangedListener() {
+ OnRequireScrollStateChangedListener listener =
+ mock(OnRequireScrollStateChangedListener.class);
+ mRequireScrollMixin.setOnRequireScrollStateChangedListener(listener);
+ assertFalse("Scrolling should not be required initially",
+ mRequireScrollMixin.isScrollingRequired());
+
+ mRequireScrollMixin.notifyScrollabilityChange(true);
+ verify(listener).onRequireScrollStateChanged(true);
+ assertTrue("Scrolling should be required when there is more content below the fold",
+ mRequireScrollMixin.isScrollingRequired());
+
+ mRequireScrollMixin.notifyScrollabilityChange(false);
+ verify(listener).onRequireScrollStateChanged(false);
+ assertFalse("Scrolling should not be required after scrolling to bottom",
+ mRequireScrollMixin.isScrollingRequired());
+
+ // Once the user has scrolled to the bottom, they should not be forced to scroll down again
+ mRequireScrollMixin.notifyScrollabilityChange(true);
+ verifyNoMoreInteractions(listener);
+
+ assertFalse("Scrolling should not be required after scrolling to bottom once",
+ mRequireScrollMixin.isScrollingRequired());
+
+ assertSame(listener, mRequireScrollMixin.getOnRequireScrollStateChangedListener());
+ }
+
+ @Test
+ public void testCreateOnClickListener() {
+ OnClickListener wrappedListener = mock(OnClickListener.class);
+ final OnClickListener onClickListener =
+ mRequireScrollMixin.createOnClickListener(wrappedListener);
+
+ mRequireScrollMixin.notifyScrollabilityChange(true);
+ onClickListener.onClick(null);
+
+ verify(wrappedListener, never()).onClick(any(View.class));
+ verify(mDelegate).pageScrollDown();
+
+ mRequireScrollMixin.notifyScrollabilityChange(false);
+ onClickListener.onClick(null);
+
+ verify(wrappedListener).onClick(any(View.class));
+ }
+
+ @Test
+ public void testRequireScrollWithNavigationBar() {
+ final NavigationBar navigationBar = new NavigationBar(application);
+ mRequireScrollMixin.requireScrollWithNavigationBar(navigationBar);
+
+ mRequireScrollMixin.notifyScrollabilityChange(true);
+ assertEquals("More button should be visible",
+ View.VISIBLE, navigationBar.getMoreButton().getVisibility());
+ assertEquals("Next button should be hidden",
+ View.GONE, navigationBar.getNextButton().getVisibility());
+
+ navigationBar.getMoreButton().performClick();
+ verify(mDelegate).pageScrollDown();
+
+ mRequireScrollMixin.notifyScrollabilityChange(false);
+ assertEquals("More button should be hidden",
+ View.GONE, navigationBar.getMoreButton().getVisibility());
+ assertEquals("Next button should be visible",
+ View.VISIBLE, navigationBar.getNextButton().getVisibility());
+ }
+
+ @SuppressLint("SetTextI18n") // It's OK for testing
+ @Test
+ public void testRequireScrollWithButton() {
+ final Button button = new Button(application);
+ button.setText("OriginalLabel");
+ OnClickListener wrappedListener = mock(OnClickListener.class);
+ mRequireScrollMixin.requireScrollWithButton(
+ button, "TestMoreLabel", wrappedListener);
+
+ assertEquals("Button label should be kept initially", "OriginalLabel", button.getText());
+
+ mRequireScrollMixin.notifyScrollabilityChange(true);
+ assertEquals("TestMoreLabel", button.getText());
+ button.performClick();
+ verify(wrappedListener, never()).onClick(eq(button));
+ verify(mDelegate).pageScrollDown();
+
+ mRequireScrollMixin.notifyScrollabilityChange(false);
+ assertEquals("OriginalLabel", button.getText());
+ button.performClick();
+ verify(wrappedListener).onClick(eq(button));
+ }
+}
diff --git a/library/test/robotest/src/com/android/setupwizardlib/template/ScrollViewScrollHandlingDelegateTest.java b/library/test/robotest/src/com/android/setupwizardlib/template/ScrollViewScrollHandlingDelegateTest.java
new file mode 100644
index 0000000..f77e256
--- /dev/null
+++ b/library/test/robotest/src/com/android/setupwizardlib/template/ScrollViewScrollHandlingDelegateTest.java
@@ -0,0 +1,88 @@
+/*
+ * 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 com.android.setupwizardlib.template;
+
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.robolectric.RuntimeEnvironment.application;
+
+import com.android.setupwizardlib.BuildConfig;
+import com.android.setupwizardlib.robolectric.SuwLibRobolectricTestRunner;
+import com.android.setupwizardlib.view.BottomScrollView;
+import com.android.setupwizardlib.view.BottomScrollView.BottomScrollListener;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.annotation.Config;
+
+@Config(constants = BuildConfig.class, sdk = { Config.OLDEST_SDK, Config.NEWEST_SDK })
+@RunWith(SuwLibRobolectricTestRunner.class)
+public class ScrollViewScrollHandlingDelegateTest {
+
+ @Mock
+ private RequireScrollMixin mRequireScrollMixin;
+
+ private BottomScrollView mScrollView;
+ private ScrollViewScrollHandlingDelegate mDelegate;
+ private ArgumentCaptor<BottomScrollListener> mListenerCaptor;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ mScrollView = spy(new BottomScrollView(application));
+ mDelegate = new ScrollViewScrollHandlingDelegate(mRequireScrollMixin, mScrollView);
+
+ mListenerCaptor = ArgumentCaptor.forClass(BottomScrollListener.class);
+ doNothing().when(mScrollView).setBottomScrollListener(mListenerCaptor.capture());
+
+ mScrollView.layout(0, 0, 50, 50);
+ }
+
+ @Test
+ public void testRequireScroll() throws Throwable {
+ mDelegate.startListening();
+
+ mListenerCaptor.getValue().onRequiresScroll();
+ verify(mRequireScrollMixin).notifyScrollabilityChange(true);
+ }
+
+ @Test
+ public void testScrolledToBottom() throws Throwable {
+ mDelegate.startListening();
+
+ mListenerCaptor.getValue().onRequiresScroll();
+ verify(mRequireScrollMixin).notifyScrollabilityChange(true);
+
+ mListenerCaptor.getValue().onScrolledToBottom();
+
+ verify(mRequireScrollMixin).notifyScrollabilityChange(false);
+ }
+
+ @Test
+ public void testPageScrollDown() throws Throwable {
+ mDelegate.pageScrollDown();
+ verify(mScrollView).smoothScrollBy(anyInt(), eq(50));
+ }
+}