10ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam/* 20ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam * Copyright (C) 2017 The Android Open Source Project 30ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam * 40ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam * Licensed under the Apache License, Version 2.0 (the "License"); 50ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam * you may not use this file except in compliance with the License. 60ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam * You may obtain a copy of the License at 70ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam * 80ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam * http://www.apache.org/licenses/LICENSE-2.0 90ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam * 100ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam * Unless required by applicable law or agreed to in writing, software 110ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam * distributed under the License is distributed on an "AS IS" BASIS, 120ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 130ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam * See the License for the specific language governing permissions and 140ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam * limitations under the License. 150ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam */ 160ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam 170ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lampackage com.android.setupwizardlib.template; 180ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam 190ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lamimport android.os.Handler; 200ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lamimport android.os.Looper; 210ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lamimport android.support.annotation.NonNull; 220ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lamimport android.support.annotation.Nullable; 230ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lamimport android.support.annotation.StringRes; 240ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lamimport android.view.View; 250ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lamimport android.view.View.OnClickListener; 260ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lamimport android.widget.Button; 270ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam 280ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lamimport com.android.setupwizardlib.TemplateLayout; 290ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lamimport com.android.setupwizardlib.view.NavigationBar; 300ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam 310ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam/** 320ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam * A mixin to require the a scrollable container (BottomScrollView, RecyclerView or ListView) to 330ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam * be scrolled to bottom, making sure that the user sees all content above and below the fold. 340ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam */ 350ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lampublic class RequireScrollMixin implements Mixin { 360ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam 370ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam /* static section */ 380ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam 390ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam /** 400ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam * Listener for when the require-scroll state changes. Note that this only requires the user to 410ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam * scroll to the bottom once - if the user scrolled to the bottom and back-up, scrolling to 420ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam * bottom is not required again. 430ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam */ 440ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam public interface OnRequireScrollStateChangedListener { 450ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam 460ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam /** 470ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam * Called when require-scroll state changed. 480ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam * 490ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam * @param scrollNeeded True if the user should be required to scroll to bottom. 500ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam */ 510ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam void onRequireScrollStateChanged(boolean scrollNeeded); 520ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam } 530ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam 540ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam /** 550ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam * A delegate to detect scrollability changes and to scroll the page. This provides a layer 560ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam * of abstraction for BottomScrollView, RecyclerView and ListView. The delegate should call 570ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam * {@link #notifyScrollabilityChange(boolean)} when the view scrollability is changed. 580ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam */ 590ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam interface ScrollHandlingDelegate { 600ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam 610ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam /** 620ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam * Starts listening to scrollability changes at the target scrollable container. 630ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam */ 640ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam void startListening(); 650ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam 660ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam /** 670ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam * Scroll the page content down by one page. 680ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam */ 690ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam void pageScrollDown(); 700ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam } 710ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam 720ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam /* non-static section */ 730ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam 740ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam @NonNull 750ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam private final TemplateLayout mTemplateLayout; 760ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam 770ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam private final Handler mHandler = new Handler(Looper.getMainLooper()); 780ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam 790ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam private boolean mRequiringScrollToBottom = false; 800ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam 810ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam // Whether the user have seen the more button yet. 820ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam private boolean mEverScrolledToBottom = false; 830ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam 840ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam private ScrollHandlingDelegate mDelegate; 850ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam 860ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam @Nullable 870ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam private OnRequireScrollStateChangedListener mListener; 880ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam 890ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam /** 900ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam * @param templateLayout The template containing this mixin 910ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam */ 920ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam public RequireScrollMixin(@NonNull TemplateLayout templateLayout) { 930ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam mTemplateLayout = templateLayout; 940ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam } 950ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam 960ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam /** 970ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam * Sets the delegate to handle scrolling. The type of delegate should depend on whether the 980ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam * scrolling view is a BottomScrollView, RecyclerView or ListView. 990ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam */ 1000ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam public void setScrollHandlingDelegate(@NonNull ScrollHandlingDelegate delegate) { 1010ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam mDelegate = delegate; 1020ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam } 1030ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam 1040ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam /** 1050ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam * Listen to require scroll state changes. When scroll is required, 1060ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam * {@link OnRequireScrollStateChangedListener#onRequireScrollStateChanged(boolean)} is called 1070ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam * with {@code true}, and vice versa. 1080ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam */ 1090ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam public void setOnRequireScrollStateChangedListener( 1100ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam @Nullable OnRequireScrollStateChangedListener listener) { 1110ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam mListener = listener; 1120ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam } 1130ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam 1140ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam /** 1150ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam * @return The scroll state listener previously set, or {@code null} if none is registered. 1160ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam */ 1170ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam public OnRequireScrollStateChangedListener getOnRequireScrollStateChangedListener() { 1180ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam return mListener; 1190ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam } 1200ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam 1210ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam /** 1220ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam * Creates an {@link OnClickListener} which if scrolling is required, will scroll the page down, 1230ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam * and if scrolling is not required, delegates to the wrapped {@code listener}. Note that you 1240ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam * should call {@link #requireScroll()} as well in order to start requiring scrolling. 1250ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam * 1260ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam * @param listener The listener to be invoked when scrolling is not needed and the user taps on 1270ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam * the button. If {@code null}, the click listener will be a no-op when scroll 1280ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam * is not required. 1290ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam * @return A new {@link OnClickListener} which will scroll the page down or delegate to the 1300ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam * given listener depending on the current require-scroll state. 1310ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam */ 1320ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam public OnClickListener createOnClickListener(@Nullable final OnClickListener listener) { 1330ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam return new OnClickListener() { 1340ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam @Override 1350ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam public void onClick(View view) { 1360ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam if (mRequiringScrollToBottom) { 1370ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam mDelegate.pageScrollDown(); 1380ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam } else if (listener != null) { 1390ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam listener.onClick(view); 1400ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam } 1410ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam } 1420ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam }; 1430ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam } 1440ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam 1450ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam /** 1460ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam * Coordinate with the given navigation bar to require scrolling on the page. The more button 1470ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam * will be shown instead of the next button while scrolling is required. 1480ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam */ 1490ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam public void requireScrollWithNavigationBar(@NonNull final NavigationBar navigationBar) { 1500ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam setOnRequireScrollStateChangedListener( 1510ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam new OnRequireScrollStateChangedListener() { 1520ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam @Override 1530ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam public void onRequireScrollStateChanged(boolean scrollNeeded) { 1540ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam navigationBar.getMoreButton() 1550ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam .setVisibility(scrollNeeded ? View.VISIBLE : View.GONE); 1560ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam navigationBar.getNextButton() 1570ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam .setVisibility(scrollNeeded ? View.GONE : View.VISIBLE); 1580ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam } 1590ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam }); 1600ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam navigationBar.getMoreButton().setOnClickListener(createOnClickListener(null)); 1610ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam requireScroll(); 1620ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam } 1630ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam 1640ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam /** 1650ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam * @see #requireScrollWithButton(Button, CharSequence, OnClickListener) 1660ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam */ 1670ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam public void requireScrollWithButton( 1680ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam @NonNull Button button, 1690ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam @StringRes int moreText, 1700ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam @Nullable OnClickListener onClickListener) { 1710ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam requireScrollWithButton(button, button.getContext().getText(moreText), onClickListener); 1720ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam } 1730ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam 1740ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam /** 1750ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam * Use the given {@code button} to require scrolling. When scrolling is required, the button 1760ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam * label will change to {@code moreText}, and tapping the button will cause the page to scroll 1770ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam * down. 1780ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam * 1790ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam * <p>Note: Calling {@link View#setOnClickListener} on the button after this method will remove 1800ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam * its link to the require-scroll mechanism. If you need to do that, obtain the click listener 1810ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam * from {@link #createOnClickListener(OnClickListener)}. 1820ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam * 1830ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam * <p>Note: The normal button label is taken from the button's text at the time of calling this 1840ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam * method. Calling {@link android.widget.TextView#setText} after calling this method causes 1850ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam * undefined behavior. 1860ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam * 1870ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam * @param button The button to use for require scroll. The button's "normal" label is taken from 1880ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam * the text at the time of calling this method, and the click listener of it will 1890ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam * be replaced. 1900ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam * @param moreText The button label when scroll is required. 1910ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam * @param onClickListener The listener for clicks when scrolling is not required. 1920ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam */ 1930ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam public void requireScrollWithButton( 1940ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam @NonNull final Button button, 1950ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam final CharSequence moreText, 1960ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam @Nullable OnClickListener onClickListener) { 1970ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam final CharSequence nextText = button.getText(); 1980ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam button.setOnClickListener(createOnClickListener(onClickListener)); 1990ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam setOnRequireScrollStateChangedListener(new OnRequireScrollStateChangedListener() { 2000ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam @Override 2010ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam public void onRequireScrollStateChanged(boolean scrollNeeded) { 2020ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam button.setText(scrollNeeded ? moreText : nextText); 2030ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam } 2040ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam }); 2050ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam requireScroll(); 2060ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam } 2070ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam 2080ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam /** 2090ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam * @return True if scrolling is required. Note that this mixin only requires the user to 2100ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam * scroll to the bottom once - if the user scrolled to the bottom and back-up, scrolling to 2110ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam * bottom is not required again. 2120ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam */ 2130ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam public boolean isScrollingRequired() { 2140ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam return mRequiringScrollToBottom; 2150ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam } 2160ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam 2170ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam /** 2180ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam * Start requiring scrolling on the layout. After calling this method, this mixin will start 2190ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam * listening to scroll events from the scrolling container, and call 2200ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam * {@link OnRequireScrollStateChangedListener} when the scroll state changes. 2210ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam */ 2220ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam public void requireScroll() { 2230ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam mDelegate.startListening(); 2240ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam } 2250ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam 2260ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam /** 2270ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam * {@link ScrollHandlingDelegate} should call this method when the scrollability of the 2280ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam * scrolling container changed, so this mixin can recompute whether scrolling should be 2290ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam * required. 2300ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam * 2310ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam * @param canScrollDown True if the view can scroll down further. 2320ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam */ 2330ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam void notifyScrollabilityChange(boolean canScrollDown) { 2340ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam if (canScrollDown == mRequiringScrollToBottom) { 2350ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam // Already at the desired require-scroll state 2360ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam return; 2370ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam } 2380ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam if (canScrollDown) { 2390ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam if (!mEverScrolledToBottom) { 2400ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam postScrollStateChange(true); 2410ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam mRequiringScrollToBottom = true; 2420ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam } 2430ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam } else { 2440ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam postScrollStateChange(false); 2450ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam mRequiringScrollToBottom = false; 2460ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam mEverScrolledToBottom = true; 2470ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam } 2480ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam } 2490ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam 2500ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam private void postScrollStateChange(final boolean scrollNeeded) { 2510ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam mHandler.post(new Runnable() { 2520ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam @Override 2530ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam public void run() { 2540ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam if (mListener != null) { 2550ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam mListener.onRequireScrollStateChanged(scrollNeeded); 2560ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam } 2570ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam } 2580ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam }); 2590ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam } 2600ceb8d53e39ebb5bc103863787afb39ec5c41ad8Maurice Lam} 261