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.annotation.TargetApi;
20efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lamimport android.content.Context;
21efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lamimport android.content.res.TypedArray;
22efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lamimport android.graphics.Canvas;
23efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lamimport android.graphics.RectF;
24efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lamimport android.os.Build;
25efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lamimport android.util.AttributeSet;
26efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lamimport android.view.LayoutInflater;
27efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lamimport android.view.MotionEvent;
28efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lamimport android.view.View;
29efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lamimport android.view.WindowInsets;
302f6da982127c0fe48d9f4002a94e4212076fadb2Maurice Lamimport android.view.accessibility.AccessibilityEvent;
31efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lamimport android.widget.ListView;
32efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam
33efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lamimport com.android.setupwizardlib.R;
34efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam
35efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam/**
36efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam * This class provides sticky header functionality in a list view, to use with
37efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam * SetupWizardIllustration. To use this, add a header tagged with "sticky", or a header tagged with
38efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam * "stickyContainer" and one of its child tagged as "sticky". The sticky container will be drawn
39efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam * when the sticky element hits the top of the view.
40efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam *
41b38561602db3fc1b31c7ee907da41ec2c53e4764Maurice Lam * <p>There are a few things to note:
42b38561602db3fc1b31c7ee907da41ec2c53e4764Maurice Lam * <ol>
43b38561602db3fc1b31c7ee907da41ec2c53e4764Maurice Lam *   <li>The two supported scenarios are StickyHeaderListView -> Header (stickyContainer) -> sticky,
44b38561602db3fc1b31c7ee907da41ec2c53e4764Maurice Lam *   and StickyHeaderListView -> Header (sticky). The arrow (->) represents parent/child
45b38561602db3fc1b31c7ee907da41ec2c53e4764Maurice Lam *   relationship and must be immediate child.
46b38561602db3fc1b31c7ee907da41ec2c53e4764Maurice Lam *   <li>The view does not work well with padding. b/16190933
47b38561602db3fc1b31c7ee907da41ec2c53e4764Maurice Lam *   <li>If fitsSystemWindows is true, then this will offset the sticking position by the height of
48b38561602db3fc1b31c7ee907da41ec2c53e4764Maurice Lam *   the system decorations at the top of the screen.
49b38561602db3fc1b31c7ee907da41ec2c53e4764Maurice Lam * </ol>
50efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam *
51efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam * @see StickyHeaderScrollView
52efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam */
53efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lampublic class StickyHeaderListView extends ListView {
54efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam
55efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam    private View mSticky;
56efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam    private View mStickyContainer;
57efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam    private int mStatusBarInset = 0;
58efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam    private RectF mStickyRect = new RectF();
59efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam
60efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam    public StickyHeaderListView(Context context) {
619f9367672191190f903955d09a4314d40869acc6Maurice Lam        super(context);
629f9367672191190f903955d09a4314d40869acc6Maurice Lam        init(null, android.R.attr.listViewStyle);
63efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam    }
64efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam
65efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam    public StickyHeaderListView(Context context, AttributeSet attrs) {
669f9367672191190f903955d09a4314d40869acc6Maurice Lam        super(context, attrs);
679f9367672191190f903955d09a4314d40869acc6Maurice Lam        init(attrs, android.R.attr.listViewStyle);
68efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam    }
69efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam
70efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam    public StickyHeaderListView(Context context, AttributeSet attrs, int defStyleAttr) {
719f9367672191190f903955d09a4314d40869acc6Maurice Lam        super(context, attrs, defStyleAttr);
729f9367672191190f903955d09a4314d40869acc6Maurice Lam        init(attrs, defStyleAttr);
73efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam    }
74efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam
759f9367672191190f903955d09a4314d40869acc6Maurice Lam    private void init(AttributeSet attrs, int defStyleAttr) {
769f9367672191190f903955d09a4314d40869acc6Maurice Lam        final TypedArray a = getContext().obtainStyledAttributes(attrs,
779f9367672191190f903955d09a4314d40869acc6Maurice Lam                R.styleable.SuwStickyHeaderListView, defStyleAttr, 0);
7895af12dac219e6945bab6142ea3a4099077314aaMaurice Lam        int headerResId = a.getResourceId(R.styleable.SuwStickyHeaderListView_suwHeader, 0);
7995af12dac219e6945bab6142ea3a4099077314aaMaurice Lam        if (headerResId != 0) {
809f9367672191190f903955d09a4314d40869acc6Maurice Lam            LayoutInflater inflater = LayoutInflater.from(getContext());
8195af12dac219e6945bab6142ea3a4099077314aaMaurice Lam            View header = inflater.inflate(headerResId, this, false);
820c0a00a3fbf90e5c80fd67dc002b6a680b7744acUdam Saini            addHeaderView(header, null, false);
83efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam        }
8495af12dac219e6945bab6142ea3a4099077314aaMaurice Lam        a.recycle();
85efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam    }
86efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam
87efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam    @Override
88efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam    protected void onLayout(boolean changed, int l, int t, int r, int b) {
89efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam        super.onLayout(changed, l, t, r, b);
90efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam        if (mSticky == null) {
91efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam            updateStickyView();
92efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam        }
93efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam    }
94efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam
95efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam    public void updateStickyView() {
96efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam        mSticky = findViewWithTag("sticky");
97efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam        mStickyContainer = findViewWithTag("stickyContainer");
98efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam    }
99efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam
100efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam    @Override
101efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam    public boolean dispatchTouchEvent(MotionEvent ev) {
102efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam        if (mStickyRect.contains(ev.getX(), ev.getY())) {
103efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam            ev.offsetLocation(-mStickyRect.left, -mStickyRect.top);
104efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam            return mStickyContainer.dispatchTouchEvent(ev);
105efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam        } else {
106efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam            return super.dispatchTouchEvent(ev);
107efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam        }
108efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam    }
109efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam
110efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam    @Override
111efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam    public void draw(Canvas canvas) {
112efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam        super.draw(canvas);
113efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam        if (mSticky != null) {
114efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam            final int saveCount = canvas.save();
115efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam            // The view to draw when sticking to the top
116efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam            final View drawTarget = mStickyContainer != null ? mStickyContainer : mSticky;
117efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam            // The offset to draw the view at when sticky
118efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam            final int drawOffset = mStickyContainer != null ? mSticky.getTop() : 0;
119efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam            // Position of the draw target, relative to the outside of the scrollView
120efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam            final int drawTop = drawTarget.getTop();
121efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam            if (drawTop + drawOffset < mStatusBarInset || !drawTarget.isShown()) {
122efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam                // ListView does not translate the canvas, so we can simply draw at the top
123efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam                mStickyRect.set(0, -drawOffset + mStatusBarInset, drawTarget.getWidth(),
124efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam                        drawTarget.getHeight() - drawOffset + mStatusBarInset);
125efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam                canvas.translate(0, mStickyRect.top);
126efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam                canvas.clipRect(0, 0, drawTarget.getWidth(), drawTarget.getHeight());
127efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam                drawTarget.draw(canvas);
128efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam            } else {
129efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam                mStickyRect.setEmpty();
130efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam            }
131efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam            canvas.restoreToCount(saveCount);
132efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam        }
133efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam    }
134efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam
135efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam    @Override
136efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
137efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam    public WindowInsets onApplyWindowInsets(WindowInsets insets) {
138efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam        if (getFitsSystemWindows()) {
139efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam            mStatusBarInset = insets.getSystemWindowInsetTop();
140efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam            insets.replaceSystemWindowInsets(
141efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam                    insets.getSystemWindowInsetLeft(),
142efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam                    0, /* top */
143efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam                    insets.getSystemWindowInsetRight(),
144efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam                    insets.getSystemWindowInsetBottom()
145efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam            );
146efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam        }
147efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam        return insets;
148efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam    }
1492f6da982127c0fe48d9f4002a94e4212076fadb2Maurice Lam
1502f6da982127c0fe48d9f4002a94e4212076fadb2Maurice Lam    @Override
1512f6da982127c0fe48d9f4002a94e4212076fadb2Maurice Lam    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
1522f6da982127c0fe48d9f4002a94e4212076fadb2Maurice Lam        super.onInitializeAccessibilityEvent(event);
1532f6da982127c0fe48d9f4002a94e4212076fadb2Maurice Lam
1542f6da982127c0fe48d9f4002a94e4212076fadb2Maurice Lam        // Decoration-only headers should not count as an item for accessibility, adjust the
1552f6da982127c0fe48d9f4002a94e4212076fadb2Maurice Lam        // accessibility event to account for that.
1562f6da982127c0fe48d9f4002a94e4212076fadb2Maurice Lam        final int numberOfHeaders = mSticky != null ? 1 : 0;
1572f6da982127c0fe48d9f4002a94e4212076fadb2Maurice Lam        event.setItemCount(event.getItemCount() - numberOfHeaders);
1582f6da982127c0fe48d9f4002a94e4212076fadb2Maurice Lam        event.setFromIndex(Math.max(event.getFromIndex() - numberOfHeaders, 0));
1592f6da982127c0fe48d9f4002a94e4212076fadb2Maurice Lam        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
1602f6da982127c0fe48d9f4002a94e4212076fadb2Maurice Lam            event.setToIndex(Math.max(event.getToIndex() - numberOfHeaders, 0));
1612f6da982127c0fe48d9f4002a94e4212076fadb2Maurice Lam        }
1622f6da982127c0fe48d9f4002a94e4212076fadb2Maurice Lam    }
163efbc38ccde179dbcbb85f9d018bb6d3510105a19Maurice Lam}
164