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