1efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam/* 2efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam * Copyright (C) 2015 The Android Open Source Project 3efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam * 4efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam * Licensed under the Apache License, Version 2.0 (the "License"); 5efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam * you may not use this file except in compliance with the License. 6efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam * You may obtain a copy of the License at 7efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam * 8efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam * http://www.apache.org/licenses/LICENSE-2.0 9efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam * 10efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam * Unless required by applicable law or agreed to in writing, software 11efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam * distributed under the License is distributed on an "AS IS" BASIS, 12efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam * See the License for the specific language governing permissions and 14efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam * limitations under the License. 15efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam */ 16efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam 17efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lampackage com.android.setupwizardlib.view; 18efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam 19efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lamimport android.content.Context; 20efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lamimport android.util.AttributeSet; 21efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lamimport android.view.View; 22efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lamimport android.widget.ScrollView; 23efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam 248c10c403c063aff3f17c4949b0fe9a88536ae580Maurice Lamimport com.android.setupwizardlib.annotations.VisibleForTesting; 25578dd1c111c2461620777b315eab3a95bee9ecd1Maurice Lam 26efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam/** 27efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam * An extension of ScrollView that will invoke a listener callback when the ScrollView needs 28efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam * scrolling, and when the ScrollView is being scrolled to the bottom. This is often used in Setup 29efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam * Wizard as a way to ensure that users see all the content before proceeding. 30efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam */ 31efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lampublic class BottomScrollView extends ScrollView { 32efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam 33efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam public interface BottomScrollListener { 34efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam void onScrolledToBottom(); 35efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam void onRequiresScroll(); 36efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam } 37efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam 38efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam private BottomScrollListener mListener; 39efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam private int mScrollThreshold; 40efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam private boolean mRequiringScroll = false; 41efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam 424afdb145d43cf42397ed47ecb1e85624ea39a81bMaurice Lam private final Runnable mCheckScrollRunnable = new Runnable() { 434afdb145d43cf42397ed47ecb1e85624ea39a81bMaurice Lam @Override 444afdb145d43cf42397ed47ecb1e85624ea39a81bMaurice Lam public void run() { 454afdb145d43cf42397ed47ecb1e85624ea39a81bMaurice Lam checkScroll(); 464afdb145d43cf42397ed47ecb1e85624ea39a81bMaurice Lam } 474afdb145d43cf42397ed47ecb1e85624ea39a81bMaurice Lam }; 484afdb145d43cf42397ed47ecb1e85624ea39a81bMaurice Lam 49efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam public BottomScrollView(Context context) { 50efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam super(context); 51efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam } 52efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam 53efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam public BottomScrollView(Context context, AttributeSet attrs) { 54efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam super(context, attrs); 55efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam } 56efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam 57efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam public BottomScrollView(Context context, AttributeSet attrs, int defStyle) { 58efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam super(context, attrs, defStyle); 59efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam } 60efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam 61efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam public void setBottomScrollListener(BottomScrollListener l) { 62efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam mListener = l; 63efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam } 64efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam 65578dd1c111c2461620777b315eab3a95bee9ecd1Maurice Lam @VisibleForTesting 66578dd1c111c2461620777b315eab3a95bee9ecd1Maurice Lam public int getScrollThreshold() { 67578dd1c111c2461620777b315eab3a95bee9ecd1Maurice Lam return mScrollThreshold; 68578dd1c111c2461620777b315eab3a95bee9ecd1Maurice Lam } 69578dd1c111c2461620777b315eab3a95bee9ecd1Maurice Lam 70efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam @Override 71efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam protected void onLayout(boolean changed, int l, int t, int r, int b) { 72efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam super.onLayout(changed, l, t, r, b); 73578dd1c111c2461620777b315eab3a95bee9ecd1Maurice Lam final View child = getChildAt(0); 74efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam if (child != null) { 75578dd1c111c2461620777b315eab3a95bee9ecd1Maurice Lam mScrollThreshold = Math.max(0, child.getMeasuredHeight() - b + t - getPaddingBottom()); 76efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam } 77efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam if (b - t > 0) { 784afdb145d43cf42397ed47ecb1e85624ea39a81bMaurice Lam // Post check scroll in the next run loop, so that the callback methods will be invoked 794afdb145d43cf42397ed47ecb1e85624ea39a81bMaurice Lam // after the layout pass. This way a new layout pass will be scheduled if view 804afdb145d43cf42397ed47ecb1e85624ea39a81bMaurice Lam // properties are changed in the callbacks. 814afdb145d43cf42397ed47ecb1e85624ea39a81bMaurice Lam post(mCheckScrollRunnable); 82efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam } 83efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam } 84efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam 85efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam @Override 86efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam protected void onScrollChanged(int l, int t, int oldl, int oldt) { 87efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam super.onScrollChanged(l, t, oldl, oldt); 88efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam if (oldt != t) { 89efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam checkScroll(); 90efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam } 91efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam } 92efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam 93efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam private void checkScroll() { 94efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam if (mListener != null) { 95efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam if (getScrollY() >= mScrollThreshold) { 96efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam mListener.onScrolledToBottom(); 97efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam } else if (!mRequiringScroll) { 98efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam mRequiringScroll = true; 99efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam mListener.onRequiresScroll(); 100efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam } 101efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam } 102efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam } 103efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam 104efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam} 105