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