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