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.widget; 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 73 /** 74 * Sets the contacts count in the pinned header. 75 */ setPinnedHeaderContactsCount(View header)76 protected abstract void setPinnedHeaderContactsCount(View header); 77 78 /** 79 * clears the contacts count in the pinned header and makes the view invisible. 80 */ clearPinnedHeaderContactsCount(View header)81 protected abstract void clearPinnedHeaderContactsCount(View header); 82 isSectionHeaderDisplayEnabled()83 public boolean isSectionHeaderDisplayEnabled() { 84 return mSectionHeaderDisplayEnabled; 85 } 86 setSectionHeaderDisplayEnabled(boolean flag)87 public void setSectionHeaderDisplayEnabled(boolean flag) { 88 this.mSectionHeaderDisplayEnabled = flag; 89 } 90 getIndexedPartition()91 public int getIndexedPartition() { 92 return mIndexedPartition; 93 } 94 setIndexedPartition(int partition)95 public void setIndexedPartition(int partition) { 96 this.mIndexedPartition = partition; 97 } 98 getIndexer()99 public SectionIndexer getIndexer() { 100 return mIndexer; 101 } 102 setIndexer(SectionIndexer indexer)103 public void setIndexer(SectionIndexer indexer) { 104 mIndexer = indexer; 105 mPlacementCache.invalidate(); 106 } 107 getSections()108 public Object[] getSections() { 109 if (mIndexer == null) { 110 return new String[] { " " }; 111 } else { 112 return mIndexer.getSections(); 113 } 114 } 115 116 /** 117 * @return relative position of the section in the indexed partition 118 */ getPositionForSection(int sectionIndex)119 public int getPositionForSection(int sectionIndex) { 120 if (mIndexer == null) { 121 return -1; 122 } 123 124 return mIndexer.getPositionForSection(sectionIndex); 125 } 126 127 /** 128 * @param position relative position in the indexed partition 129 */ getSectionForPosition(int position)130 public int getSectionForPosition(int position) { 131 if (mIndexer == null) { 132 return -1; 133 } 134 135 return mIndexer.getSectionForPosition(position); 136 } 137 138 @Override getPinnedHeaderCount()139 public int getPinnedHeaderCount() { 140 if (isSectionHeaderDisplayEnabled()) { 141 return super.getPinnedHeaderCount() + 1; 142 } else { 143 return super.getPinnedHeaderCount(); 144 } 145 } 146 147 @Override getPinnedHeaderView(int viewIndex, View convertView, ViewGroup parent)148 public View getPinnedHeaderView(int viewIndex, View convertView, ViewGroup parent) { 149 if (isSectionHeaderDisplayEnabled() && viewIndex == getPinnedHeaderCount() - 1) { 150 if (mHeader == null) { 151 mHeader = createPinnedSectionHeaderView(mContext, parent); 152 } 153 return mHeader; 154 } else { 155 return super.getPinnedHeaderView(viewIndex, convertView, parent); 156 } 157 } 158 159 @Override configurePinnedHeaders(PinnedHeaderListView listView)160 public void configurePinnedHeaders(PinnedHeaderListView listView) { 161 super.configurePinnedHeaders(listView); 162 163 if (!isSectionHeaderDisplayEnabled()) { 164 return; 165 } 166 167 int index = getPinnedHeaderCount() - 1; 168 if (mIndexer == null || getCount() == 0) { 169 listView.setHeaderInvisible(index, false); 170 } else { 171 int listPosition = listView.getPositionAt(listView.getTotalTopPinnedHeaderHeight()); 172 int position = listPosition - listView.getHeaderViewsCount(); 173 174 int section = -1; 175 int partition = getPartitionForPosition(position); 176 if (partition == mIndexedPartition) { 177 int offset = getOffsetInPartition(position); 178 if (offset != -1) { 179 section = getSectionForPosition(offset); 180 } 181 } 182 183 if (section == -1) { 184 listView.setHeaderInvisible(index, false); 185 } else { 186 setPinnedSectionTitle(mHeader, (String)mIndexer.getSections()[section]); 187 if (section == 0) { 188 setPinnedHeaderContactsCount(mHeader); 189 } else { 190 clearPinnedHeaderContactsCount(mHeader); 191 } 192 // Compute the item position where the current partition begins 193 int partitionStart = getPositionForPartition(mIndexedPartition); 194 if (hasHeader(mIndexedPartition)) { 195 partitionStart++; 196 } 197 198 // Compute the item position where the next section begins 199 int nextSectionPosition = partitionStart + getPositionForSection(section + 1); 200 boolean isLastInSection = position == nextSectionPosition - 1; 201 listView.setFadingHeader(index, listPosition, isLastInSection); 202 } 203 } 204 } 205 206 /** 207 * Computes the item's placement within its section and populates the {@code placement} 208 * object accordingly. Please note that the returned object is volatile and should be 209 * copied if the result needs to be used later. 210 */ getItemPlacementInSection(int position)211 public Placement getItemPlacementInSection(int position) { 212 if (mPlacementCache.position == position) { 213 return mPlacementCache; 214 } 215 216 mPlacementCache.position = position; 217 if (isSectionHeaderDisplayEnabled()) { 218 int section = getSectionForPosition(position); 219 if (section != -1 && getPositionForSection(section) == position) { 220 mPlacementCache.firstInSection = true; 221 mPlacementCache.sectionHeader = (String)getSections()[section]; 222 } else { 223 mPlacementCache.firstInSection = false; 224 mPlacementCache.sectionHeader = null; 225 } 226 227 mPlacementCache.lastInSection = (getPositionForSection(section + 1) - 1 == position); 228 } else { 229 mPlacementCache.firstInSection = false; 230 mPlacementCache.lastInSection = false; 231 mPlacementCache.sectionHeader = null; 232 } 233 return mPlacementCache; 234 } 235 } 236