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/**
25 * A list adapter that supports section indexer and a pinned header.
26 */
27public 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 = listView.getChildAt(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     * Computes the item's placement within its section and populates the {@code placement}
199     * object accordingly.  Please note that the returned object is volatile and should be
200     * copied if the result needs to be used later.
201     */
202    public Placement getItemPlacementInSection(int position) {
203        if (mPlacementCache.position == position) {
204            return mPlacementCache;
205        }
206
207        mPlacementCache.position = position;
208        if (isSectionHeaderDisplayEnabled()) {
209            int section = getSectionForPosition(position);
210            if (section != -1 && getPositionForSection(section) == position) {
211                mPlacementCache.firstInSection = true;
212                mPlacementCache.sectionHeader = (String)getSections()[section];
213            } else {
214                mPlacementCache.firstInSection = false;
215                mPlacementCache.sectionHeader = null;
216            }
217
218            mPlacementCache.lastInSection = (getPositionForSection(section + 1) - 1 == position);
219        } else {
220            mPlacementCache.firstInSection = false;
221            mPlacementCache.lastInSection = false;
222            mPlacementCache.sectionHeader = null;
223        }
224        return mPlacementCache;
225    }
226}
227