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
17package com.android.setupwizardlib.view;
18
19import android.annotation.TargetApi;
20import android.content.Context;
21import android.os.Build;
22import android.util.AttributeSet;
23import android.view.View;
24import android.view.WindowInsets;
25
26/**
27 * This class provides sticky header functionality in a scroll view, to use with
28 * SetupWizardIllustration. To use this, add a subview tagged with "sticky", or a subview tagged
29 * with "stickyContainer" and one of its child tagged as "sticky". The sticky container will be
30 * drawn when the sticky element hits the top of the view.
31 *
32 * <p>There are a few things to note:
33 * <ol>
34 *   <li>The two supported scenarios are StickyHeaderScrollView -> subview (stickyContainer) ->
35 *   sticky, and StickyHeaderScrollView -> container -> subview (sticky).
36 *   The arrow (->) represents parent/child relationship and must be immediate child.
37 *   <li>If fitsSystemWindows is true, then this will offset the sticking position by the height of
38 *   the system decorations at the top of the screen.
39 *   <li>For versions before Honeycomb, this will behave like a regular ScrollView.
40 * </ol>
41 *
42 * @see StickyHeaderListView
43 */
44public class StickyHeaderScrollView extends BottomScrollView {
45
46    private View mSticky;
47    private View mStickyContainer;
48    private int mStatusBarInset = 0;
49
50    public StickyHeaderScrollView(Context context) {
51        super(context);
52    }
53
54    public StickyHeaderScrollView(Context context, AttributeSet attrs) {
55        super(context, attrs);
56    }
57
58    public StickyHeaderScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
59        super(context, attrs, defStyleAttr);
60    }
61
62    @Override
63    protected void onLayout(boolean changed, int l, int t, int r, int b) {
64        super.onLayout(changed, l, t, r, b);
65        if (mSticky == null) {
66            updateStickyView();
67        }
68        updateStickyHeaderPosition();
69    }
70
71    public void updateStickyView() {
72        mSticky = findViewWithTag("sticky");
73        mStickyContainer = findViewWithTag("stickyContainer");
74    }
75
76    private void updateStickyHeaderPosition() {
77        // Note: for pre-Honeycomb the header will not be moved, so this ScrollView essentially
78        // behaves like a normal BottomScrollView.
79        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
80            if (mSticky != null) {
81                // The view to draw when sticking to the top
82                final View drawTarget = mStickyContainer != null ? mStickyContainer : mSticky;
83                // The offset to draw the view at when sticky
84                final int drawOffset = mStickyContainer != null ? mSticky.getTop() : 0;
85                // Position of the draw target, relative to the outside of the scrollView
86                final int drawTop = drawTarget.getTop() - getScrollY();
87                if (drawTop + drawOffset < mStatusBarInset || !drawTarget.isShown()) {
88                    // ScrollView translates the whole canvas so we have to compensate for that
89                    drawTarget.setTranslationY(getScrollY() - drawOffset);
90                } else {
91                    drawTarget.setTranslationY(0);
92                }
93            }
94        }
95    }
96
97    @Override
98    protected void onScrollChanged(int l, int t, int oldl, int oldt) {
99        super.onScrollChanged(l, t, oldl, oldt);
100        updateStickyHeaderPosition();
101    }
102
103    @Override
104    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
105    public WindowInsets onApplyWindowInsets(WindowInsets insets) {
106        if (getFitsSystemWindows()) {
107            mStatusBarInset = insets.getSystemWindowInsetTop();
108            insets = insets.replaceSystemWindowInsets(
109                    insets.getSystemWindowInsetLeft(),
110                    0, /* top */
111                    insets.getSystemWindowInsetRight(),
112                    insets.getSystemWindowInsetBottom()
113            );
114        }
115        return insets;
116    }
117}
118