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.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 = getViewAtVisiblePosition(listView, 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 * Returns the view used for the specified list position assuming it is visible or null if 199 * it isn't. 200 * 201 * <p>This makes some assumptions about the implementation of ListView (child views are the 202 * item views and are ordered in the same way as the adapter items they are displaying) 203 * but they are probably safe given that the API is stable.</p> 204 */ getViewAtVisiblePosition(ListView list, int position)205 private View getViewAtVisiblePosition(ListView list, int position) { 206 final int firstVisiblePosition = list.getFirstVisiblePosition(); 207 final int childCount = list.getChildCount(); 208 final int index = position - firstVisiblePosition; 209 if (index >= 0 && index < childCount) { 210 // Position is on-screen, use existing view. 211 return list.getChildAt(index); 212 } else { 213 return null; 214 } 215 } 216 217 /** 218 * Computes the item's placement within its section and populates the {@code placement} 219 * object accordingly. Please note that the returned object is volatile and should be 220 * copied if the result needs to be used later. 221 */ getItemPlacementInSection(int position)222 public Placement getItemPlacementInSection(int position) { 223 if (mPlacementCache.position == position) { 224 return mPlacementCache; 225 } 226 227 mPlacementCache.position = position; 228 if (isSectionHeaderDisplayEnabled()) { 229 int section = getSectionForPosition(position); 230 if (section != -1 && getPositionForSection(section) == position) { 231 mPlacementCache.firstInSection = true; 232 mPlacementCache.sectionHeader = (String)getSections()[section]; 233 } else { 234 mPlacementCache.firstInSection = false; 235 mPlacementCache.sectionHeader = null; 236 } 237 238 mPlacementCache.lastInSection = (getPositionForSection(section + 1) - 1 == position); 239 } else { 240 mPlacementCache.firstInSection = false; 241 mPlacementCache.lastInSection = false; 242 mPlacementCache.sectionHeader = null; 243 } 244 return mPlacementCache; 245 } 246 } 247