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