StickyHeaderListView.java revision 980d530f002b335916e8b31662e50a94b43cae18
137f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson/*
237f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson * Copyright (C) 2011 The Android Open Source Project
337f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson *
437f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson * Licensed under the Apache License, Version 2.0 (the "License");
537f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson * you may not use this file except in compliance with the License.
637f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson * You may obtain a copy of the License at
737f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson *
837f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson *      http://www.apache.org/licenses/LICENSE-2.0
937f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson *
1037f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson * Unless required by applicable law or agreed to in writing, software
1137f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson * distributed under the License is distributed on an "AS IS" BASIS,
1237f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1337f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson * See the License for the specific language governing permissions and
1437f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson * limitations under the License.
1537f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson */
1637f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson
1737f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelsonpackage com.android.calendar;
1837f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson
1937f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelsonimport android.content.Context;
2037f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelsonimport android.database.DataSetObserver;
2137f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelsonimport android.graphics.Canvas;
2237f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelsonimport android.graphics.Color;
2337f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelsonimport android.graphics.Rect;
2437f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelsonimport android.text.format.Time;
2537f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelsonimport android.util.AttributeSet;
2637f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelsonimport android.util.Log;
2737f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelsonimport android.view.Gravity;
2837f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelsonimport android.view.View;
2937f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelsonimport android.view.ViewGroup;
3037f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelsonimport android.widget.AbsListView;
3137f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelsonimport android.widget.AbsListView.OnScrollListener;
3237f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelsonimport android.widget.Adapter;
3337f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelsonimport android.widget.FrameLayout;
3437f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelsonimport android.widget.ListView;
3537f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelsonimport android.widget.TextView;
3637f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson
3737f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson/**
3837f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson * Implements a ListView class with a sticky header at the top. The header is
3937f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson * per section and it is pinned to the top as long as its section is at the top
4037f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson * of the view. If it is not, the header slides up or down (depending on the
4137f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson * scroll movement) and the header of the current section slides to the top.
4237f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson * Notes:
4337f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson * 1. The class uses the first available child ListView as the working
4437f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson *    ListView. If no ListView child exists, the class will create a default one.
4537f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson * 2. The ListView's adapter must be passed to this class using the 'setAdapter'
4637f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson *    method. The adapter must implement the HeaderIndexer interface. If no adapter
4737f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson *    is specified, the class will try to extract it from the ListView
4837f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson * 3. The class registers itself as a listener to scroll events (OnScrollListener), if the
4937f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson *    ListView needs to receive scroll events, it must register its listener using
5037f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson *    this class' setOnScrollListener method.
5137f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson * 4. Headers for the list view must be added before using the StickyHeaderListView
5237f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson * 5. The implementation should register to listen to dataset changes. Right now this is not done
5337f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson *    since a change the dataset in a listview forces a call to OnScroll. The needed code is
5437f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson *    commented out.
5537f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson */
5637f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelsonpublic class StickyHeaderListView extends FrameLayout implements OnScrollListener {
5737f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson
5837f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson    private static final String TAG = "StickyHeaderListView";
5937f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson    protected boolean mChildViewsCreated = false;
6037f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson    protected boolean mDoHeaderReset = false;
6137f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson
6237f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson    protected Context mContext = null;
6337f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson    protected Adapter mAdapter = null;
6437f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson    protected HeaderIndexer mIndexer = null;
6537f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson    protected View mStickyHeader = null;
6637f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson    protected View mDummyHeader = null; // A invisible header used when a section has no header
6737f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson    protected ListView mListView = null;
6837f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson    protected ListView.OnScrollListener mListener = null;
6937f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson
7037f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson    // This code is needed only if dataset changes do not force a call to OnScroll
7137f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson    // protected DataSetObserver mListDataObserver = null;
7237f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson
7337f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson
7437f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson    protected int mCurrentSectionPos = -1; // Position of section that has its header on the
7537f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson                                           // top of the view
7637f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson    protected int mNextSectionPosition = -1; // Position of next section's header
7737f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson    protected int mListViewHeadersCount = 0;
7837f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson
7937f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson    /**
8037f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson     * Interface that must be implemented by the ListView adapter to provide headers locations
8137f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson     * and number of items under each header.
8237f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson     *
8337f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson     */
8437f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson    public interface HeaderIndexer {
8537f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson        /**
8637f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson         * Calculates the position of the header of a specific item in the adapter's data set.
8737f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson         * For example: Assuming you have a list with albums and songs names:
8837f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson         * Album A, song 1, song 2, ...., song 10, Album B, song 1, ..., song 7. A call to
8937f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson         * this method with the position of song 5 in Album B, should return  the position
9037f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson         * of Album B.
9137f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson         * @param position - Position of the item in the ListView dataset
9237f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson         * @return Position of header. -1 if the is no header
9337f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson         */
9437f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson
9537f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson        int getHeaderPositionFromItemPosition(int position);
9637f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson
9737f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson        /**
98980d530f002b335916e8b31662e50a94b43cae18Isaac Katzenelson         * Calculates the number of items in the section defined by the header (not including
9937f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson         * the header).
10037f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson         * For example: A list with albums and songs, the method should return
10137f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson         * the number of songs names (without the album name).
10237f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson         *
10337f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson         * @param headerPosition - the value returned by 'getHeaderPositionFromItemPosition'
10437f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson         * @return Number of items. -1 on error.
10537f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson         */
10637f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson        int getHeaderItemsNumber(int headerPosition);
10737f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson    }
10837f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson
109e2ed3678c5fb9434f5e89d3278dea8daff44de58Isaac Katzenelson    /**
110e2ed3678c5fb9434f5e89d3278dea8daff44de58Isaac Katzenelson     * Sets the adapter to be used by the class to get views of headers
111e2ed3678c5fb9434f5e89d3278dea8daff44de58Isaac Katzenelson     *
112e2ed3678c5fb9434f5e89d3278dea8daff44de58Isaac Katzenelson     * @param adapter - The adapter.
113e2ed3678c5fb9434f5e89d3278dea8daff44de58Isaac Katzenelson     */
11437f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson
115e2ed3678c5fb9434f5e89d3278dea8daff44de58Isaac Katzenelson    public void setAdapter(Adapter adapter) {
116e2ed3678c5fb9434f5e89d3278dea8daff44de58Isaac Katzenelson
117e2ed3678c5fb9434f5e89d3278dea8daff44de58Isaac Katzenelson        // This code is needed only if dataset changes do not force a call to
118e2ed3678c5fb9434f5e89d3278dea8daff44de58Isaac Katzenelson        // OnScroll
11937f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson        // if (mAdapter != null && mListDataObserver != null) {
120e2ed3678c5fb9434f5e89d3278dea8daff44de58Isaac Katzenelson        // mAdapter.unregisterDataSetObserver(mListDataObserver);
12137f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson        // }
12237f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson
123e2ed3678c5fb9434f5e89d3278dea8daff44de58Isaac Katzenelson        if (adapter != null) {
124e2ed3678c5fb9434f5e89d3278dea8daff44de58Isaac Katzenelson            mAdapter = adapter;
125e2ed3678c5fb9434f5e89d3278dea8daff44de58Isaac Katzenelson            // This code is needed only if dataset changes do not force a call
126e2ed3678c5fb9434f5e89d3278dea8daff44de58Isaac Katzenelson            // to OnScroll
127e2ed3678c5fb9434f5e89d3278dea8daff44de58Isaac Katzenelson            // mAdapter.registerDataSetObserver(mListDataObserver);
12837f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson        }
12937f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson    }
13037f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson
131e2ed3678c5fb9434f5e89d3278dea8daff44de58Isaac Katzenelson    /**
132e2ed3678c5fb9434f5e89d3278dea8daff44de58Isaac Katzenelson     * Sets the indexer object (that implements the HeaderIndexer interface).
133e2ed3678c5fb9434f5e89d3278dea8daff44de58Isaac Katzenelson     *
134e2ed3678c5fb9434f5e89d3278dea8daff44de58Isaac Katzenelson     * @param indexer - The indexer.
135e2ed3678c5fb9434f5e89d3278dea8daff44de58Isaac Katzenelson     */
136e2ed3678c5fb9434f5e89d3278dea8daff44de58Isaac Katzenelson
137e2ed3678c5fb9434f5e89d3278dea8daff44de58Isaac Katzenelson    public void setIndexer(HeaderIndexer indexer) {
138e2ed3678c5fb9434f5e89d3278dea8daff44de58Isaac Katzenelson        mIndexer = indexer;
13937f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson    }
14037f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson
141e2ed3678c5fb9434f5e89d3278dea8daff44de58Isaac Katzenelson    /**
142e2ed3678c5fb9434f5e89d3278dea8daff44de58Isaac Katzenelson     * Sets the list view that is displayed
143e2ed3678c5fb9434f5e89d3278dea8daff44de58Isaac Katzenelson     * @param lv - The list view.
144e2ed3678c5fb9434f5e89d3278dea8daff44de58Isaac Katzenelson     */
145e2ed3678c5fb9434f5e89d3278dea8daff44de58Isaac Katzenelson
14637f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson    public void setListView(ListView lv) {
14737f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson        mListView = lv;
14837f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson        mListView.setOnScrollListener(this);
14937f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson        mListViewHeadersCount = mListView.getHeaderViewsCount();
15037f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson    }
15137f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson
152e2ed3678c5fb9434f5e89d3278dea8daff44de58Isaac Katzenelson    /**
153e2ed3678c5fb9434f5e89d3278dea8daff44de58Isaac Katzenelson     * Sets an external OnScroll listener. Since the StickyHeaderListView sets
154e2ed3678c5fb9434f5e89d3278dea8daff44de58Isaac Katzenelson     * itself as the scroll events listener of the listview, this method allows
155e2ed3678c5fb9434f5e89d3278dea8daff44de58Isaac Katzenelson     * the user to register another listener that will be called after this
156e2ed3678c5fb9434f5e89d3278dea8daff44de58Isaac Katzenelson     * class listener is called.
157e2ed3678c5fb9434f5e89d3278dea8daff44de58Isaac Katzenelson     *
158e2ed3678c5fb9434f5e89d3278dea8daff44de58Isaac Katzenelson     * @param listener - The external listener.
159e2ed3678c5fb9434f5e89d3278dea8daff44de58Isaac Katzenelson     */
160e2ed3678c5fb9434f5e89d3278dea8daff44de58Isaac Katzenelson    public void setOnScrollListener(ListView.OnScrollListener listener) {
161e2ed3678c5fb9434f5e89d3278dea8daff44de58Isaac Katzenelson        mListener = listener;
16237f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson    }
16337f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson
16437f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson    // This code is needed only if dataset changes do not force a call to OnScroll
16537f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson    // protected void createDataListener() {
16637f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson    //    mListDataObserver = new DataSetObserver() {
16737f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson    //        @Override
16837f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson    //        public void onChanged() {
16937f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson    //            onDataChanged();
17037f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson    //        }
17137f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson    //    };
17237f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson    // }
17337f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson
174e2ed3678c5fb9434f5e89d3278dea8daff44de58Isaac Katzenelson    /**
175e2ed3678c5fb9434f5e89d3278dea8daff44de58Isaac Katzenelson     * Constructor
176e2ed3678c5fb9434f5e89d3278dea8daff44de58Isaac Katzenelson     *
177e2ed3678c5fb9434f5e89d3278dea8daff44de58Isaac Katzenelson     * @param context - application context.
178e2ed3678c5fb9434f5e89d3278dea8daff44de58Isaac Katzenelson     * @param attrs - layout attributes.
179e2ed3678c5fb9434f5e89d3278dea8daff44de58Isaac Katzenelson     */
18037f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson    public StickyHeaderListView(Context context, AttributeSet attrs) {
18137f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson        super(context, attrs);
18237f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson        mContext = context;
18337f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson        // This code is needed only if dataset changes do not force a call to OnScroll
18437f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson        // createDataListener();
18537f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson     }
18637f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson
187e2ed3678c5fb9434f5e89d3278dea8daff44de58Isaac Katzenelson    /**
188e2ed3678c5fb9434f5e89d3278dea8daff44de58Isaac Katzenelson     * Scroll status changes listener
189e2ed3678c5fb9434f5e89d3278dea8daff44de58Isaac Katzenelson     *
190e2ed3678c5fb9434f5e89d3278dea8daff44de58Isaac Katzenelson     * @param view - the scrolled view
191e2ed3678c5fb9434f5e89d3278dea8daff44de58Isaac Katzenelson     * @param scrollState - new scroll state.
192e2ed3678c5fb9434f5e89d3278dea8daff44de58Isaac Katzenelson     */
193e2ed3678c5fb9434f5e89d3278dea8daff44de58Isaac Katzenelson    @Override
19437f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson    public void onScrollStateChanged(AbsListView view, int scrollState) {
19537f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson        if (mListener != null) {
19637f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson            mListener.onScrollStateChanged(view, scrollState);
19737f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson        }
19837f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson    }
19937f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson
200e2ed3678c5fb9434f5e89d3278dea8daff44de58Isaac Katzenelson    /**
201e2ed3678c5fb9434f5e89d3278dea8daff44de58Isaac Katzenelson     * Scroll events listener
202e2ed3678c5fb9434f5e89d3278dea8daff44de58Isaac Katzenelson     *
203e2ed3678c5fb9434f5e89d3278dea8daff44de58Isaac Katzenelson     * @param view - the scrolled view
204e2ed3678c5fb9434f5e89d3278dea8daff44de58Isaac Katzenelson     * @param firstVisibleItem - the index (in the list's adapter) of the top
205e2ed3678c5fb9434f5e89d3278dea8daff44de58Isaac Katzenelson     *            visible item.
206e2ed3678c5fb9434f5e89d3278dea8daff44de58Isaac Katzenelson     * @param visibleItemCount - the number of visible items in the list
207e2ed3678c5fb9434f5e89d3278dea8daff44de58Isaac Katzenelson     * @param totalItemCount - the total number items in the list
208e2ed3678c5fb9434f5e89d3278dea8daff44de58Isaac Katzenelson     */
209e2ed3678c5fb9434f5e89d3278dea8daff44de58Isaac Katzenelson    @Override
21037f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
21137f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson            int totalItemCount) {
21237f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson
21337f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson        updateStickyHeader(firstVisibleItem);
21437f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson
21537f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson        if (mListener != null) {
21637f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson            mListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount);
21737f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson        }
21837f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson    }
21937f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson
22037f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson    protected void updateStickyHeader(int firstVisibleItem) {
22137f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson
22237f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson        // Try to make sure we have an adapter to work with (may not succeed).
22337f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson        if (mAdapter == null && mListView != null) {
22437f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson            setAdapter(mListView.getAdapter());
22537f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson        }
22637f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson
22737f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson        firstVisibleItem -= mListViewHeadersCount;
22837f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson        if (mAdapter != null && mIndexer != null && mDoHeaderReset) {
22937f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson
23037f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson            // Get the section header position
23137f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson            int sectionSize = 0;
23237f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson            int sectionPos = mIndexer.getHeaderPositionFromItemPosition(firstVisibleItem);
23337f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson
23437f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson            // New section - set it in the header view
23537f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson            boolean newView = false;
23637f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson            if (sectionPos != mCurrentSectionPos) {
23737f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson
23837f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson                // No header for current position , use the dummy invisible one
23937f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson                if (sectionPos == -1) {
24037f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson                    sectionSize = 0;
24137f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson                    this.removeView(mStickyHeader);
24237f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson                    mStickyHeader = mDummyHeader;
24337f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson                    newView = true;
24437f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson                } else {
24537f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson                    // Create a copy of the header view to show on top
24637f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson                    sectionSize = mIndexer.getHeaderItemsNumber(sectionPos);
24737f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson                    View v = mAdapter.getView(sectionPos + mListViewHeadersCount, null, mListView);
24837f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson                    v.measure(MeasureSpec.makeMeasureSpec(mListView.getWidth(),
24937f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson                            MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(mListView.getHeight(),
25037f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson                                    MeasureSpec.AT_MOST));
25137f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson                    this.removeView(mStickyHeader);
25237f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson                    mStickyHeader = v;
25337f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson                    newView = true;
25437f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson                }
25537f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson                mCurrentSectionPos = sectionPos;
25637f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson                mNextSectionPosition = sectionSize + sectionPos + 1;
25737f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson            }
25837f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson
25937f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson
26037f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson            // Do transitions
26137f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson            // If position of bottom of last item in a section is smaller than the height of the
26237f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson            // sticky header - shift drawable of header.
26337f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson            if (mStickyHeader != null) {
26437f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson                int sectionLastItemPosition =  mNextSectionPosition - firstVisibleItem - 1;
26537f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson                int stickyHeaderHeight = mStickyHeader.getHeight();
26637f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson                if (stickyHeaderHeight == 0) {
26737f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson                    stickyHeaderHeight = mStickyHeader.getMeasuredHeight();
26837f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson                }
26937f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson                View SectionLastView = mListView.getChildAt(sectionLastItemPosition);
27037f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson                if (SectionLastView != null && SectionLastView.getBottom() <= stickyHeaderHeight) {
27137f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson                    int lastViewBottom = SectionLastView.getBottom();
27237f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson                    mStickyHeader.setTranslationY(lastViewBottom - stickyHeaderHeight);
27337f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson                } else if (stickyHeaderHeight != 0) {
27437f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson                    mStickyHeader.setTranslationY(0);
27537f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson                }
27637f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson                if (newView) {
27737f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson                    mStickyHeader.setVisibility(View.INVISIBLE);
27837f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson                    this.addView(mStickyHeader);
27937f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson                    mStickyHeader.setVisibility(View.VISIBLE);
28037f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson                }
28137f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson            }
28237f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson        }
28337f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson    }
28437f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson
28537f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson    @Override
28637f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson    protected void onFinishInflate() {
28737f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson        super.onFinishInflate();
28837f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson        if (!mChildViewsCreated) {
28937f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson            setChildViews();
29037f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson        }
29137f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson        mDoHeaderReset = true;
29237f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson    }
29337f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson
29437f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson    @Override
29537f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson    protected void onAttachedToWindow() {
29637f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson        super.onAttachedToWindow();
29737f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson        if (!mChildViewsCreated) {
29837f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson            setChildViews();
29937f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson        }
30037f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson        mDoHeaderReset = true;
30137f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson    }
30237f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson
30337f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson
30437f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson    // Resets the sticky header when the adapter data set was changed
30537f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson    // This code is needed only if dataset changes do not force a call to OnScroll
30637f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson    // protected void onDataChanged() {
30737f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson    // Should do a call to updateStickyHeader if needed
30837f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson    // }
30937f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson
31037f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson    private void setChildViews() {
31137f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson
31237f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson        // Find a child ListView (if any)
31337f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson        int iChildNum = getChildCount();
31437f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson        for (int i = 0; i < iChildNum; i++) {
31537f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson            Object v = getChildAt(i);
31637f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson            if (v instanceof ListView) {
31737f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson                setListView((ListView) v);
31837f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson            }
31937f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson        }
32037f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson
32137f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson        // No child ListView - add one
32237f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson        if (mListView == null) {
32337f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson            setListView(new ListView(mContext));
32437f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson        }
32537f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson
32637f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson        // Create a dummy view , it will be used in case a section has no header
32737f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson        mDummyHeader = new View (mContext);
32837f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson        ViewGroup.LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT,
32937f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson                1, Gravity.TOP);
33037f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson        mDummyHeader.setLayoutParams(params);
33137f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson        mDummyHeader.setBackgroundColor(Color.TRANSPARENT);
33237f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson
33337f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson        mChildViewsCreated = true;
33437f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson    }
33537f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson
33637f12e5cee7ed2d354e9366bd6d8e15d1a934f2aIsaac Katzenelson}
337