1 /*
2  * Copyright (C) 2010 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 package com.android.contacts.common.list;
17 
18 import android.content.Context;
19 import android.view.View;
20 import android.view.ViewGroup;
21 import android.widget.ListView;
22 import android.widget.SectionIndexer;
23 
24 /**
25  * A list adapter that supports section indexer and a pinned header.
26  */
27 public abstract class IndexerListAdapter extends PinnedHeaderListAdapter implements SectionIndexer {
28 
29     protected Context mContext;
30     private SectionIndexer mIndexer;
31     private int mIndexedPartition = 0;
32     private boolean mSectionHeaderDisplayEnabled;
33     private View mHeader;
34 
35     /**
36      * An item view is displayed differently depending on whether it is placed
37      * at the beginning, middle or end of a section. It also needs to know the
38      * section header when it is at the beginning of a section. This object
39      * captures all this configuration.
40      */
41     public static final class Placement {
42         private int position = ListView.INVALID_POSITION;
43         public boolean firstInSection;
44         public boolean lastInSection;
45         public String sectionHeader;
46 
invalidate()47         public void invalidate() {
48             position = ListView.INVALID_POSITION;
49         }
50     }
51 
52     private Placement mPlacementCache = new Placement();
53 
54     /**
55      * Constructor.
56      */
IndexerListAdapter(Context context)57     public IndexerListAdapter(Context context) {
58         super(context);
59         mContext = context;
60     }
61 
62     /**
63      * Creates a section header view that will be pinned at the top of the list
64      * as the user scrolls.
65      */
createPinnedSectionHeaderView(Context context, ViewGroup parent)66     protected abstract View createPinnedSectionHeaderView(Context context, ViewGroup parent);
67 
68     /**
69      * Sets the title in the pinned header as the user scrolls.
70      */
setPinnedSectionTitle(View pinnedHeaderView, String title)71     protected abstract void setPinnedSectionTitle(View pinnedHeaderView, String title);
72 
isSectionHeaderDisplayEnabled()73     public boolean isSectionHeaderDisplayEnabled() {
74         return mSectionHeaderDisplayEnabled;
75     }
76 
setSectionHeaderDisplayEnabled(boolean flag)77     public void setSectionHeaderDisplayEnabled(boolean flag) {
78         this.mSectionHeaderDisplayEnabled = flag;
79     }
80 
getIndexedPartition()81     public int getIndexedPartition() {
82         return mIndexedPartition;
83     }
84 
setIndexedPartition(int partition)85     public void setIndexedPartition(int partition) {
86         this.mIndexedPartition = partition;
87     }
88 
getIndexer()89     public SectionIndexer getIndexer() {
90         return mIndexer;
91     }
92 
setIndexer(SectionIndexer indexer)93     public void setIndexer(SectionIndexer indexer) {
94         mIndexer = indexer;
95         mPlacementCache.invalidate();
96     }
97 
getSections()98     public Object[] getSections() {
99         if (mIndexer == null) {
100             return new String[] { " " };
101         } else {
102             return mIndexer.getSections();
103         }
104     }
105 
106     /**
107      * @return relative position of the section in the indexed partition
108      */
getPositionForSection(int sectionIndex)109     public int getPositionForSection(int sectionIndex) {
110         if (mIndexer == null) {
111             return -1;
112         }
113 
114         return mIndexer.getPositionForSection(sectionIndex);
115     }
116 
117     /**
118      * @param position relative position in the indexed partition
119      */
getSectionForPosition(int position)120     public int getSectionForPosition(int position) {
121         if (mIndexer == null) {
122             return -1;
123         }
124 
125         return mIndexer.getSectionForPosition(position);
126     }
127 
128     @Override
getPinnedHeaderCount()129     public int getPinnedHeaderCount() {
130         if (isSectionHeaderDisplayEnabled()) {
131             return super.getPinnedHeaderCount() + 1;
132         } else {
133             return super.getPinnedHeaderCount();
134         }
135     }
136 
137     @Override
getPinnedHeaderView(int viewIndex, View convertView, ViewGroup parent)138     public View getPinnedHeaderView(int viewIndex, View convertView, ViewGroup parent) {
139         if (isSectionHeaderDisplayEnabled() && viewIndex == getPinnedHeaderCount() - 1) {
140             if (mHeader == null) {
141                 mHeader = createPinnedSectionHeaderView(mContext, parent);
142             }
143             return mHeader;
144         } else {
145             return super.getPinnedHeaderView(viewIndex, convertView, parent);
146         }
147     }
148 
149     @Override
configurePinnedHeaders(PinnedHeaderListView listView)150     public void configurePinnedHeaders(PinnedHeaderListView listView) {
151         super.configurePinnedHeaders(listView);
152 
153         if (!isSectionHeaderDisplayEnabled()) {
154             return;
155         }
156 
157         int index = getPinnedHeaderCount() - 1;
158         if (mIndexer == null || getCount() == 0) {
159             listView.setHeaderInvisible(index, false);
160         } else {
161             int listPosition = listView.getPositionAt(listView.getTotalTopPinnedHeaderHeight());
162             int position = listPosition - listView.getHeaderViewsCount();
163 
164             int section = -1;
165             int partition = getPartitionForPosition(position);
166             if (partition == mIndexedPartition) {
167                 int offset = getOffsetInPartition(position);
168                 if (offset != -1) {
169                     section = getSectionForPosition(offset);
170                 }
171             }
172 
173             if (section == -1) {
174                 listView.setHeaderInvisible(index, false);
175             } else {
176                 View topChild = listView.getChildAt(listPosition);
177                 if (topChild != null) {
178                     // Match the pinned header's height to the height of the list item.
179                     mHeader.setMinimumHeight(topChild.getMeasuredHeight());
180                 }
181                 setPinnedSectionTitle(mHeader, (String)mIndexer.getSections()[section]);
182 
183                 // Compute the item position where the current partition begins
184                 int partitionStart = getPositionForPartition(mIndexedPartition);
185                 if (hasHeader(mIndexedPartition)) {
186                     partitionStart++;
187                 }
188 
189                 // Compute the item position where the next section begins
190                 int nextSectionPosition = partitionStart + getPositionForSection(section + 1);
191                 boolean isLastInSection = position == nextSectionPosition - 1;
192                 listView.setFadingHeader(index, listPosition, isLastInSection);
193             }
194         }
195     }
196 
197     /**
198      * Computes the item's placement within its section and populates the {@code placement}
199      * object accordingly.  Please note that the returned object is volatile and should be
200      * copied if the result needs to be used later.
201      */
getItemPlacementInSection(int position)202     public Placement getItemPlacementInSection(int position) {
203         if (mPlacementCache.position == position) {
204             return mPlacementCache;
205         }
206 
207         mPlacementCache.position = position;
208         if (isSectionHeaderDisplayEnabled()) {
209             int section = getSectionForPosition(position);
210             if (section != -1 && getPositionForSection(section) == position) {
211                 mPlacementCache.firstInSection = true;
212                 mPlacementCache.sectionHeader = (String)getSections()[section];
213             } else {
214                 mPlacementCache.firstInSection = false;
215                 mPlacementCache.sectionHeader = null;
216             }
217 
218             mPlacementCache.lastInSection = (getPositionForSection(section + 1) - 1 == position);
219         } else {
220             mPlacementCache.firstInSection = false;
221             mPlacementCache.lastInSection = false;
222             mPlacementCache.sectionHeader = null;
223         }
224         return mPlacementCache;
225     }
226 }
227