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