• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.google.android.setupdesign.view;
18 
19 import android.content.Context;
20 import androidx.annotation.VisibleForTesting;
21 import android.util.AttributeSet;
22 import android.view.View;
23 import android.widget.ScrollView;
24 
25 /**
26  * An extension of ScrollView that will invoke a listener callback when the ScrollView needs
27  * scrolling, and when the ScrollView is being scrolled to the bottom. This is often used in Setup
28  * Wizard as a way to ensure that users see all the content before proceeding.
29  */
30 public class BottomScrollView extends ScrollView {
31 
32   public interface BottomScrollListener {
onScrolledToBottom()33     void onScrolledToBottom();
34 
onRequiresScroll()35     void onRequiresScroll();
36   }
37 
38   private BottomScrollListener listener;
39   private int scrollThreshold;
40   private boolean requiringScroll = false;
41 
42   private final Runnable checkScrollRunnable =
43       new Runnable() {
44         @Override
45         public void run() {
46           checkScroll();
47         }
48       };
49 
BottomScrollView(Context context)50   public BottomScrollView(Context context) {
51     super(context);
52   }
53 
BottomScrollView(Context context, AttributeSet attrs)54   public BottomScrollView(Context context, AttributeSet attrs) {
55     super(context, attrs);
56   }
57 
BottomScrollView(Context context, AttributeSet attrs, int defStyle)58   public BottomScrollView(Context context, AttributeSet attrs, int defStyle) {
59     super(context, attrs, defStyle);
60   }
61 
setBottomScrollListener(BottomScrollListener l)62   public void setBottomScrollListener(BottomScrollListener l) {
63     listener = l;
64   }
65 
66   @VisibleForTesting
getBottomScrollListener()67   public BottomScrollListener getBottomScrollListener() {
68     return listener;
69   }
70 
71   @VisibleForTesting
getScrollThreshold()72   public int getScrollThreshold() {
73     return scrollThreshold;
74   }
75 
76   @Override
onLayout(boolean changed, int l, int t, int r, int b)77   protected void onLayout(boolean changed, int l, int t, int r, int b) {
78     super.onLayout(changed, l, t, r, b);
79     final View child = getChildAt(0);
80     if (child != null) {
81       scrollThreshold = Math.max(0, child.getMeasuredHeight() - b + t - getPaddingBottom());
82     }
83     if (b - t > 0) {
84       // Post check scroll in the next run loop, so that the callback methods will be invoked
85       // after the layout pass. This way a new layout pass will be scheduled if view
86       // properties are changed in the callbacks.
87       post(checkScrollRunnable);
88     }
89   }
90 
91   @Override
onScrollChanged(int l, int t, int oldl, int oldt)92   protected void onScrollChanged(int l, int t, int oldl, int oldt) {
93     super.onScrollChanged(l, t, oldl, oldt);
94     if (oldt != t) {
95       checkScroll();
96     }
97   }
98 
checkScroll()99   private void checkScroll() {
100     if (listener != null) {
101       if (getScrollY() >= scrollThreshold) {
102         listener.onScrolledToBottom();
103       } else if (!requiringScroll) {
104         requiringScroll = true;
105         listener.onRequiresScroll();
106       }
107     }
108   }
109 }
110