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