Home | History | Annotate | Download | only in list
      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 
     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     public boolean isSectionHeaderDisplayEnabled() {
     74         return mSectionHeaderDisplayEnabled;
     75     }
     76 
     77     public void setSectionHeaderDisplayEnabled(boolean flag) {
     78         this.mSectionHeaderDisplayEnabled = flag;
     79     }
     80 
     81     public int getIndexedPartition() {
     82         return mIndexedPartition;
     83     }
     84 
     85     public void setIndexedPartition(int partition) {
     86         this.mIndexedPartition = partition;
     87     }
     88 
     89     public SectionIndexer getIndexer() {
     90         return mIndexer;
     91     }
     92 
     93     public void setIndexer(SectionIndexer indexer) {
     94         mIndexer = indexer;
     95         mPlacementCache.invalidate();
     96     }
     97 
     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      */
    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      */
    120     public int getSectionForPosition(int position) {
    121         if (mIndexer == null) {
    122             return -1;
    123         }
    124 
    125         return mIndexer.getSectionForPosition(position);
    126     }
    127 
    128     @Override
    129     public int getPinnedHeaderCount() {
    130         if (isSectionHeaderDisplayEnabled()) {
    131             return super.getPinnedHeaderCount() + 1;
    132         } else {
    133             return super.getPinnedHeaderCount();
    134         }
    135     }
    136 
    137     @Override
    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
    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      */
    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      */
    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