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