StickyHeaderScrollView.java revision 2646e1d82ec6d133b35b775f044e156fca6d9d75
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.graphics.Canvas;
22import android.graphics.RectF;
23import android.os.Build;
24import android.util.AttributeSet;
25import android.view.MotionEvent;
26import android.view.View;
27import android.view.WindowInsets;
28
29/**
30 * This class provides sticky header functionality in a scroll view, to use with
31 * SetupWizardIllustration. To use this, add a subview tagged with "sticky", or a subview tagged
32 * with "stickyContainer" and one of its child tagged as "sticky". The sticky container will be
33 * drawn when the sticky element hits the top of the view.
34 *
35 * There are a few things to note:
36 * 1. The two supported scenarios are StickyHeaderScrollView -> subview (stickyContainer) -> sticky,
37 *    and StickyHeaderScrollView -> container -> subview (sticky).
38 *    The arrow (->) represents parent/child relationship and must be immediate child.
39 * 2. The view does not work well with padding. b/16190933
40 * 3. If fitsSystemWindows is true, then this will offset the sticking position by the height of
41 *    the system decorations at the top of the screen.
42 *
43 * @see StickyHeaderListView
44 */
45public class StickyHeaderScrollView extends BottomScrollView {
46
47    private View mSticky;
48    private View mStickyContainer;
49    private int mStatusBarInset = 0;
50    private RectF mStickyRect = new RectF();
51
52    public StickyHeaderScrollView(Context context) {
53        super(context);
54    }
55
56    public StickyHeaderScrollView(Context context, AttributeSet attrs) {
57        super(context, attrs);
58    }
59
60    public StickyHeaderScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
61        super(context, attrs, defStyleAttr);
62    }
63
64    @Override
65    protected void onLayout(boolean changed, int l, int t, int r, int b) {
66        super.onLayout(changed, l, t, r, b);
67        if (mSticky == null) {
68            updateStickyView();
69        }
70    }
71
72    public void updateStickyView() {
73        mSticky = findViewWithTag("sticky");
74        mStickyContainer = findViewWithTag("stickyContainer");
75    }
76
77    @Override
78    public boolean dispatchTouchEvent(MotionEvent ev) {
79        if (mStickyRect.contains(ev.getX(), ev.getY())) {
80            ev.offsetLocation(-mStickyRect.left, -mStickyRect.top);
81            return mStickyContainer.dispatchTouchEvent(ev);
82        } else {
83            return super.dispatchTouchEvent(ev);
84        }
85    }
86
87    @Override
88    public void draw(Canvas canvas) {
89        super.draw(canvas);
90        if (mSticky != null) {
91            final int saveCount = canvas.save();
92            // The view to draw when sticking to the top
93            final View drawTarget = mStickyContainer != null ? mStickyContainer : mSticky;
94            // The offset to draw the view at when sticky
95            final int drawOffset = mStickyContainer != null ? mSticky.getTop() : 0;
96            // Position of the draw target, relative to the outside of the scrollView
97            final int drawTop = drawTarget.getTop() - getScrollY();
98            if (drawTop + drawOffset < mStatusBarInset || !drawTarget.isShown()) {
99                // ScrollView translates the whole canvas so we have to compensate for that
100                mStickyRect.set(0, -drawOffset + mStatusBarInset, drawTarget.getWidth(),
101                        drawTarget.getHeight() - drawOffset + mStatusBarInset);
102                canvas.translate(0, -drawTop + mStickyRect.top);
103                canvas.clipRect(0, 0, drawTarget.getWidth(), drawTarget.getHeight());
104                drawTarget.draw(canvas);
105            } else {
106                mStickyRect.setEmpty();
107            }
108            canvas.restoreToCount(saveCount);
109        }
110        onDrawScrollBars(canvas);
111    }
112
113    @Override
114    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
115    public WindowInsets onApplyWindowInsets(WindowInsets insets) {
116        if (getFitsSystemWindows()) {
117            mStatusBarInset = insets.getSystemWindowInsetTop();
118            insets = insets.replaceSystemWindowInsets(
119                    insets.getSystemWindowInsetLeft(),
120                    0, /* top */
121                    insets.getSystemWindowInsetRight(),
122                    insets.getSystemWindowInsetBottom()
123            );
124        }
125        return insets;
126    }
127}
128