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