Reworking contacts list adapters for the new CompositeCursorAdapter API
Also redesigning PinnedHeaderListAdapter
Change-Id: Ia4a2e7bb449fc82e1c3ac9b7a3f0c54a8e4d9d38
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 762d66d..4caf5c7 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -509,5 +509,13 @@
<activity android:name=".ExportVCardActivity"
android:theme="@style/BackgroundOnly" />
+
+ <!-- Pinned header list demo -->
+ <activity android:name=".widget.PinnedHeaderListDemoActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
</application>
</manifest>
diff --git a/res/layout-finger/pinned_header_list_demo.xml b/res/layout-finger/pinned_header_list_demo.xml
new file mode 100644
index 0000000..9a26e98
--- /dev/null
+++ b/res/layout-finger/pinned_header_list_demo.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/pinned_header_list_layout"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ >
+
+ <view
+ class="com.android.contacts.widget.PinnedHeaderListView"
+ android:id="@android:id/list"
+ android:layout_width="match_parent"
+ android:layout_height="0dip"
+ android:fastScrollEnabled="true"
+ android:layout_weight="1"
+ />
+</LinearLayout>
diff --git a/src/com/android/contacts/ContactEntryListView.java b/src/com/android/contacts/ContactEntryListView.java
index 795be43..21ff25e 100644
--- a/src/com/android/contacts/ContactEntryListView.java
+++ b/src/com/android/contacts/ContactEntryListView.java
@@ -39,15 +39,17 @@
private boolean mHighlightNamesWhenScrolling;
public ContactEntryListView(Context context) {
- super(context);
+ this(context, null);
}
public ContactEntryListView(Context context, AttributeSet attrs) {
- super(context, attrs);
+ this(context, attrs, com.android.internal.R.attr.listViewStyle);
}
public ContactEntryListView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
+ setPinnedHeaderBackgroundColor(
+ context.getResources().getColor(R.color.pinned_header_background));
}
public TextHighlightingAnimation getTextHighlightingAnimation() {
diff --git a/src/com/android/contacts/list/ContactBrowseListContextMenuAdapter.java b/src/com/android/contacts/list/ContactBrowseListContextMenuAdapter.java
index 617c855..1529c1d 100644
--- a/src/com/android/contacts/list/ContactBrowseListContextMenuAdapter.java
+++ b/src/com/android/contacts/list/ContactBrowseListContextMenuAdapter.java
@@ -57,15 +57,15 @@
ContactListAdapter adapter = mContactListFragment.getAdapter();
int headerViewsCount = mContactListFragment.getListView().getHeaderViewsCount();
- adapter.moveToPosition(info.position - headerViewsCount);
+ int position = info.position - headerViewsCount;
// Setup the menu header
- menu.setHeaderTitle(adapter.getContactDisplayName());
+ menu.setHeaderTitle(adapter.getContactDisplayName(position));
// View contact details
menu.add(0, MENU_ITEM_VIEW_CONTACT, 0, R.string.menu_viewContact);
- if (adapter.getHasPhoneNumber()) {
+ if (adapter.getHasPhoneNumber(position)) {
// Calling contact
menu.add(0, MENU_ITEM_CALL, 0, R.string.menu_call);
// Send SMS item
@@ -73,7 +73,7 @@
}
// Star toggling
- if (!adapter.isContactStarred()) {
+ if (!adapter.isContactStarred(position)) {
menu.add(0, MENU_ITEM_TOGGLE_STAR, 0, R.string.menu_addStar);
} else {
menu.add(0, MENU_ITEM_TOGGLE_STAR, 0, R.string.menu_removeStar);
@@ -95,9 +95,9 @@
ContactListAdapter adapter = mContactListFragment.getAdapter();
int headerViewsCount = mContactListFragment.getListView().getHeaderViewsCount();
- adapter.moveToPosition(info.position - headerViewsCount);
+ int position = info.position - headerViewsCount;
- final Uri contactUri = adapter.getContactUri();
+ final Uri contactUri = adapter.getContactUri(position);
switch (item.getItemId()) {
case MENU_ITEM_VIEW_CONTACT: {
mContactListFragment.viewContact(contactUri);
@@ -105,7 +105,7 @@
}
case MENU_ITEM_TOGGLE_STAR: {
- if (adapter.isContactStarred()) {
+ if (adapter.isContactStarred(position)) {
mContactListFragment.removeFromFavorites(contactUri);
} else {
mContactListFragment.addToFavorites(contactUri);
diff --git a/src/com/android/contacts/list/ContactEntryListAdapter.java b/src/com/android/contacts/list/ContactEntryListAdapter.java
index 7c2a16a..f3d406d 100644
--- a/src/com/android/contacts/list/ContactEntryListAdapter.java
+++ b/src/com/android/contacts/list/ContactEntryListAdapter.java
@@ -18,7 +18,7 @@
import com.android.contacts.ContactPhotoLoader;
import com.android.contacts.ContactsSectionIndexer;
import com.android.contacts.R;
-import com.android.contacts.widget.PinnedHeaderListAdapter;
+import com.android.contacts.widget.IndexerListAdapter;
import com.android.contacts.widget.TextWithHighlightingFactory;
import android.content.Context;
@@ -35,17 +35,15 @@
* Common base class for various contact-related lists, e.g. contact list, phone number list
* etc.
*/
-public abstract class ContactEntryListAdapter extends PinnedHeaderListAdapter {
+public abstract class ContactEntryListAdapter extends IndexerListAdapter {
/**
* The animation is used here to allocate animated name text views.
*/
private TextWithHighlightingFactory mTextWithHighlightingFactory;
-
private int mDisplayOrder;
private int mSortOrder;
private boolean mNameHighlightingEnabled;
- private boolean mSectionHeaderDisplayEnabled;
private boolean mDisplayPhotos;
private ContactPhotoLoader mPhotoLoader;
@@ -58,15 +56,19 @@
private boolean mEmptyListEnabled = true;
public ContactEntryListAdapter(Context context) {
- super(context, R.layout.list_section, R.id.header_text,
- context.getResources().getColor(R.color.pinned_header_background));
+ super(context, R.layout.list_section, R.id.header_text);
+ addPartitions();
}
- public Context getContext() {
- return mContext;
+ /**
+ * Adds all partitions this adapter will handle. The default implementation
+ * creates one partition with no header.
+ */
+ protected void addPartitions() {
+ addPartition(false, false);
}
- public abstract String getContactDisplayName();
+ public abstract String getContactDisplayName(int position);
public abstract void configureLoader(CursorLoader loader);
public boolean isSearchMode() {
@@ -93,14 +95,6 @@
mQueryString = queryString;
}
- public boolean isSectionHeaderDisplayEnabled() {
- return mSectionHeaderDisplayEnabled;
- }
-
- public void setSectionHeaderDisplayEnabled(boolean flag) {
- mSectionHeaderDisplayEnabled = flag;
- }
-
public int getContactNameDisplayOrder() {
return mDisplayOrder;
}
@@ -158,25 +152,20 @@
mEmptyListEnabled = flag;
}
- /*
- * TODO change this method when loaders are introduced.
- */
@Override
- @Deprecated
- public void onContentChanged() {
- super.onContentChanged();
- }
-
- @Override
- public void changeCursor(Cursor cursor) {
+ public void changeCursor(int partition, Cursor cursor) {
mLoading = false;
- super.changeCursor(cursor);
+ super.changeCursor(partition, cursor);
- if (isSectionHeaderDisplayEnabled()) {
+ if (isSectionHeaderDisplayEnabled() && partition == getIndexedPartition()) {
updateIndexer(cursor);
}
}
+ public void changeCursor(Cursor cursor) {
+ changeCursor(0, cursor);
+ }
+
/**
* Updates the indexer, which is used to produce section headers.
*/
@@ -223,10 +212,6 @@
@Override
public int getCount() {
- if (!mDataValid) {
- return 0;
- }
-
int count = super.getCount();
if (mSearchMode) {
@@ -247,15 +232,16 @@
return IGNORE_ITEM_VIEW_TYPE;
}
- return 0;
+ return super.getItemViewType(position);
}
@Override
- public View getView(int position, View convertView, ViewGroup parent) {
+ protected View getView(int partition, Cursor cursor, int position, View convertView,
+ ViewGroup parent) {
if (isSearchAllContactsItemPosition(position)) {
return LayoutInflater.from(getContext()).inflate(
R.layout.contacts_list_search_all_item, parent, false);
}
- return super.getView(position, convertView, parent);
+ return super.getView(partition, cursor, position, convertView, parent);
}
}
diff --git a/src/com/android/contacts/list/ContactListAdapter.java b/src/com/android/contacts/list/ContactListAdapter.java
index 3dfde7d..b2c9dbc 100644
--- a/src/com/android/contacts/list/ContactListAdapter.java
+++ b/src/com/android/contacts/list/ContactListAdapter.java
@@ -95,17 +95,17 @@
.appendQueryParameter(ContactCounts.ADDRESS_BOOK_INDEX_EXTRAS, "true").build();
}
- public boolean getHasPhoneNumber() {
- return getCursor().getInt(CONTACT_HAS_PHONE_COLUMN_INDEX) != 0;
+ public boolean getHasPhoneNumber(int position) {
+ return ((Cursor)getItem(position)).getInt(CONTACT_HAS_PHONE_COLUMN_INDEX) != 0;
}
- public boolean isContactStarred() {
- return getCursor().getInt(CONTACT_STARRED_COLUMN_INDEX) != 0;
+ public boolean isContactStarred(int position) {
+ return ((Cursor)getItem(position)).getInt(CONTACT_STARRED_COLUMN_INDEX) != 0;
}
@Override
- public String getContactDisplayName() {
- return getCursor().getString(mDisplayNameColumnIndex);
+ public String getContactDisplayName(int position) {
+ return ((Cursor)getItem(position)).getString(mDisplayNameColumnIndex);
}
public boolean isQuickContactEnabled() {
@@ -132,25 +132,26 @@
* Builds the {@link Contacts#CONTENT_LOOKUP_URI} for the given
* {@link ListView} position.
*/
- public Uri getContactUri() {
- Cursor cursor = getCursor();
+ public Uri getContactUri(int position) {
+ Cursor item = (Cursor)getItem(position);
+ return item != null ? getContactUri(item) : null;
+ }
+
+ public Uri getContactUri(Cursor cursor) {
long contactId = cursor.getLong(CONTACT_ID_COLUMN_INDEX);
String lookupKey = cursor.getString(CONTACT_LOOKUP_KEY_COLUMN_INDEX);
return Contacts.getLookupUri(contactId, lookupKey);
}
@Override
- public View newView(Context context, Cursor cursor, ViewGroup parent) {
+ protected View newView(Context context, int partition, Cursor cursor, int position,
+ ViewGroup parent) {
final ContactListItemView view = new ContactListItemView(context, null);
view.setUnknownNameText(mUnknownNameText);
view.setTextWithHighlightingFactory(getTextWithHighlightingFactory());
return view;
}
- protected void bindSectionHeaderAndDivider(ContactListItemView view, Cursor cursor) {
- bindSectionHeaderAndDivider(view, cursor.getPosition());
- }
-
protected void bindSectionHeaderAndDivider(ContactListItemView view, int position) {
final int section = getSectionForPosition(position);
if (section != -1 && getPositionForSection(section) == position) {
@@ -186,7 +187,7 @@
}
QuickContactBadge quickContact = view.getQuickContact();
- quickContact.assignContactUri(getContactUri());
+ quickContact.assignContactUri(getContactUri(cursor));
getPhotoLoader().loadPhoto(quickContact, photoId);
}
diff --git a/src/com/android/contacts/list/ContactPickerFragment.java b/src/com/android/contacts/list/ContactPickerFragment.java
index d2b550a..27cadfa 100644
--- a/src/com/android/contacts/list/ContactPickerFragment.java
+++ b/src/com/android/contacts/list/ContactPickerFragment.java
@@ -84,12 +84,10 @@
mListener.onSearchAllContactsAction((String)null);
} else {
Uri uri;
- ContactEntryListAdapter adapter = getAdapter();
- adapter.moveToPosition(position);
if (isLegacyCompatibilityMode()) {
- uri = ((LegacyContactListAdapter)adapter).getPersonUri();
+ uri = ((LegacyContactListAdapter)getAdapter()).getPersonUri(position);
} else {
- uri = ((ContactListAdapter)adapter).getContactUri();
+ uri = ((ContactListAdapter)getAdapter()).getContactUri(position);
}
if (mShortcutRequested) {
ShortcutIntentBuilder builder = new ShortcutIntentBuilder(getActivity(), this);
diff --git a/src/com/android/contacts/list/DefaultContactBrowseListFragment.java b/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
index 179d25a..e923466 100644
--- a/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
+++ b/src/com/android/contacts/list/DefaultContactBrowseListFragment.java
@@ -87,11 +87,11 @@
createNewContact();
} else {
adapter.moveToPosition(position);
- editContact(adapter.getContactUri());
+ editContact(adapter.getContactUri(position));
}
} else {
adapter.moveToPosition(position);
- viewContact(adapter.getContactUri());
+ viewContact(adapter.getContactUri(position));
}
}
}
diff --git a/src/com/android/contacts/list/DefaultContactListAdapter.java b/src/com/android/contacts/list/DefaultContactListAdapter.java
index 6e0daf5..24c493a 100644
--- a/src/com/android/contacts/list/DefaultContactListAdapter.java
+++ b/src/com/android/contacts/list/DefaultContactListAdapter.java
@@ -81,10 +81,10 @@
}
@Override
- public void bindView(View itemView, Context context, Cursor cursor) {
+ protected void bindView(View itemView, int partition, Cursor cursor, int position) {
final ContactListItemView view = (ContactListItemView)itemView;
- bindSectionHeaderAndDivider(view, cursor);
+ bindSectionHeaderAndDivider(view, position);
if (isQuickContactEnabled()) {
bindQuickContact(view, cursor);
diff --git a/src/com/android/contacts/list/JoinContactListAdapter.java b/src/com/android/contacts/list/JoinContactListAdapter.java
index f1c9a83..4228b0c 100644
--- a/src/com/android/contacts/list/JoinContactListAdapter.java
+++ b/src/com/android/contacts/list/JoinContactListAdapter.java
@@ -37,19 +37,40 @@
/** Maximum number of suggestions shown for joining aggregates */
private static final int MAX_SUGGESTIONS = 4;
- private Cursor mSuggestionsCursor;
- private int mSuggestionsCursorCount;
+ public static final int PARTITION_SUGGESTIONS = 0;
+ public static final int PARTITION_SHOW_ALL_CONTACTS = 1;
+ public static final int PARTITION_ALL_CONTACTS = 2;
+
private long mTargetContactId;
+ private int mShowAllContactsViewType;
+
/**
* Determines whether we display a list item with the label
* "Show all contacts" or actually show all contacts
*/
private boolean mAllContactsListShown;
+
public JoinContactListAdapter(Context context) {
super(context);
+ setPinnedPartitionHeadersEnabled(true);
setSectionHeaderDisplayEnabled(true);
+ setIndexedPartition(PARTITION_ALL_CONTACTS);
+ mShowAllContactsViewType = super.getViewTypeCount();
+ }
+
+ @Override
+ protected void addPartitions() {
+
+ // Partition 0: suggestions
+ addPartition(false, true);
+
+ // Partition 1: "Show all contacts"
+ addPartition(false, false);
+
+ // Partition 2: All contacts
+ addPartition(false, true);
}
public void setTargetContactId(long targetContactId) {
@@ -76,16 +97,13 @@
// TODO simplify projection
loader.setProjection(PROJECTION);
-
- if (mAllContactsListShown) {
- loader.setUri(buildSectionIndexerUri(Contacts.CONTENT_URI));
- loader.setSelection(Contacts.IN_VISIBLE_GROUP + "=1 AND " + Contacts._ID + "!=?");
- loader.setSelectionArgs(new String[]{String.valueOf(mTargetContactId)});
- if (getSortOrder() == ContactsContract.Preferences.SORT_ORDER_PRIMARY) {
- loader.setSortOrder(Contacts.SORT_KEY_PRIMARY);
- } else {
- loader.setSortOrder(Contacts.SORT_KEY_ALTERNATIVE);
- }
+ loader.setUri(buildSectionIndexerUri(Contacts.CONTENT_URI));
+ loader.setSelection(Contacts.IN_VISIBLE_GROUP + "=1 AND " + Contacts._ID + "!=?");
+ loader.setSelectionArgs(new String[]{String.valueOf(mTargetContactId)});
+ if (getSortOrder() == ContactsContract.Preferences.SORT_ORDER_PRIMARY) {
+ loader.setSortOrder(Contacts.SORT_KEY_PRIMARY);
+ } else {
+ loader.setSortOrder(Contacts.SORT_KEY_ALTERNATIVE);
}
}
@@ -94,10 +112,6 @@
return false;
}
- private boolean hasSuggestions() {
- return mSuggestionsCursorCount != 0;
- }
-
public boolean isAllContactsListShown() {
return mAllContactsListShown;
}
@@ -107,58 +121,62 @@
}
public void setSuggestionsCursor(Cursor cursor) {
- mSuggestionsCursor = cursor;
- mSuggestionsCursorCount = cursor == null ? 0 : cursor.getCount();
- }
-
- public boolean isShowAllContactsItemPosition(int position) {
- return !mAllContactsListShown
- && hasSuggestions() && position == mSuggestionsCursorCount + 2;
+ changeCursor(PARTITION_SUGGESTIONS, cursor);
+ if (cursor != null && cursor.getCount() != 0 && !mAllContactsListShown) {
+ changeCursor(PARTITION_SHOW_ALL_CONTACTS, getShowAllContactsLabelCursor());
+ } else {
+ changeCursor(PARTITION_SHOW_ALL_CONTACTS, null);
+ }
}
@Override
- public View getView(int position, View convertView, ViewGroup parent) {
- if (!mDataValid) {
- throw new IllegalStateException(
- "this should only be called when the cursor is valid");
- }
+ public void changeCursor(Cursor cursor) {
+ changeCursor(PARTITION_ALL_CONTACTS, cursor);
+ }
- Cursor cursor;
- boolean showingSuggestion = false;
- if (hasSuggestions()) {
- if (position == 0) {
- // First section: "suggestions"
- TextView view = (TextView) inflate(R.layout.list_separator, parent);
- view.setText(R.string.separatorJoinAggregateSuggestions);
- return view;
- } else if (position < mSuggestionsCursorCount + 1) {
- showingSuggestion = true;
- cursor = mSuggestionsCursor;
- cursor.moveToPosition(position - 1);
- } else if (position == mSuggestionsCursorCount + 1) {
- // Second section: "all contacts"
- TextView view = (TextView) inflate(R.layout.list_separator, parent);
- view.setText(R.string.separatorJoinAggregateAll);
- return view;
- } else if (!mAllContactsListShown && position == mSuggestionsCursorCount + 2) {
- return inflate(R.layout.contacts_list_show_all_item, parent);
- } else {
- cursor = mCursor;
- cursor.moveToPosition(position - mSuggestionsCursorCount - 2);
+ @Override
+ public int getViewTypeCount() {
+ return super.getViewTypeCount() + 1;
+ }
+
+ @Override
+ protected int getItemViewType(int partition, int position) {
+ if (partition == PARTITION_SHOW_ALL_CONTACTS) {
+ return mShowAllContactsViewType;
+ }
+ return super.getItemViewType(partition, position);
+ }
+
+ @Override
+ protected View newHeaderView(Context context, int partition, Cursor cursor,
+ ViewGroup parent) {
+ switch (partition) {
+ case PARTITION_SUGGESTIONS: {
+ TextView view = (TextView) inflate(R.layout.list_separator, parent);
+ view.setText(R.string.separatorJoinAggregateSuggestions);
+ return view;
}
- } else {
- cursor = mCursor;
- cursor.moveToPosition(position);
+ case PARTITION_ALL_CONTACTS: {
+ TextView view = (TextView) inflate(R.layout.list_separator, parent);
+ view.setText(R.string.separatorJoinAggregateAll);
+ return view;
+ }
}
- View v;
- if (convertView == null || convertView.getTag() == null) {
- v = newView(getContext(), cursor, parent);
- } else {
- v = convertView;
+ return null;
+ }
+
+ @Override
+ protected View newView(Context context, int partition, Cursor cursor, int position,
+ ViewGroup parent) {
+ switch (partition) {
+ case PARTITION_SUGGESTIONS:
+ case PARTITION_ALL_CONTACTS:
+ return super.newView(context, partition, cursor, position, parent);
+ case PARTITION_SHOW_ALL_CONTACTS:
+ return inflate(R.layout.contacts_list_show_all_item, parent);
}
- bindView(position, v, cursor, showingSuggestion);
- return v;
+ return null;
}
private View inflate(int layoutId, ViewGroup parent) {
@@ -166,17 +184,25 @@
}
@Override
- public void bindView(View view, Context context, Cursor cursor) {
- // not used
- }
-
- public void bindView(int position, View itemView, Cursor cursor, boolean showingSuggestion) {
- final ContactListItemView view = (ContactListItemView)itemView;
- if (!showingSuggestion) {
- bindSectionHeaderAndDivider(view, position);
+ protected void bindView(View itemView, int partition, Cursor cursor, int position) {
+ switch (partition) {
+ case PARTITION_SUGGESTIONS: {
+ final ContactListItemView view = (ContactListItemView)itemView;
+ bindPhoto(view, cursor);
+ bindName(view, cursor);
+ break;
+ }
+ case PARTITION_SHOW_ALL_CONTACTS: {
+ break;
+ }
+ case PARTITION_ALL_CONTACTS: {
+ final ContactListItemView view = (ContactListItemView)itemView;
+ bindSectionHeaderAndDivider(view, position);
+ bindPhoto(view, cursor);
+ bindName(view, cursor);
+ break;
+ }
}
- bindPhoto(view, cursor);
- bindName(view, cursor);
}
public Cursor getShowAllContactsLabelCursor() {
@@ -187,128 +213,9 @@
}
@Override
- public void changeCursor(Cursor cursor) {
- if (cursor == null) {
- setSuggestionsCursor(null);
- }
-
- super.changeCursor(cursor);
- }
-
- @Override
- public int getItemViewType(int position) {
- if (isShowAllContactsItemPosition(position)) {
- return IGNORE_ITEM_VIEW_TYPE;
- }
-
- return super.getItemViewType(position);
- }
-
- @Override
- public int getPositionForSection(int sectionIndex) {
- if (mSuggestionsCursorCount == 0) {
- return super.getPositionForSection(sectionIndex);
- }
-
- // Get section position in the full list
- int position = super.getPositionForSection(sectionIndex);
- return position + mSuggestionsCursorCount + 2;
- }
-
- @Override
- public int getSectionForPosition(int position) {
- if (mSuggestionsCursorCount == 0) {
- return super.getSectionForPosition(position);
- }
-
- if (position < mSuggestionsCursorCount + 2) {
- return -1;
- }
-
- return super.getSectionForPosition(position - mSuggestionsCursorCount - 2);
- }
-
- @Override
- public boolean areAllItemsEnabled() {
- return super.areAllItemsEnabled() && mSuggestionsCursorCount == 0;
- }
-
- @Override
- public boolean isEnabled(int position) {
- if (position == 0) {
- return false;
- }
-
- if (mSuggestionsCursorCount > 0) {
- return position != 0 && position != mSuggestionsCursorCount + 1;
- }
- return true;
- }
-
- @Override
- public int getCount() {
- if (!mDataValid) {
- return 0;
- }
- int superCount = super.getCount();
- if (hasSuggestions()) {
- // When showing suggestions, we have 2 additional list items: the "Suggestions"
- // and "All contacts" headers.
- return mSuggestionsCursorCount + superCount + 2;
- }
- return superCount;
- }
-
- public int getSuggestionsCursorCount() {
- return mSuggestionsCursorCount;
- }
-
- @Override
- public Object getItem(int pos) {
- if (hasSuggestions()) {
- // When showing suggestions, we have 2 additional list items: the "Suggestions"
- // and "All contacts" separators.
- if (pos == 0) {
- return null;
- }
- else if (pos < mSuggestionsCursorCount + 1) {
- // We are in the upper partition (Suggestions). Adjusting for the "Suggestions"
- // separator.
- mSuggestionsCursor.moveToPosition(pos - 1);
- return mSuggestionsCursor;
- } else if (pos == mSuggestionsCursorCount + 1) {
- // This is the "All contacts" separator
- return null;
- } else {
- if (!isAllContactsListShown()) {
- // This is the "Show all contacts" item
- return null;
- } else {
- // We are in the lower partition (All contacts). Adjusting for the size
- // of the upper partition plus the two separators.
- mCursor.moveToPosition(pos - mSuggestionsCursorCount - 2);
- return mCursor;
- }
- }
- } else if (mCursor != null) {
- // No separators
- mCursor.moveToPosition(pos);
- return mCursor;
- } else {
- return null;
- }
- }
-
- @Override
- public long getItemId(int pos) {
- Cursor cursor = (Cursor)getItem(pos);
- return cursor == null ? 0 : cursor.getLong(mRowIDColumn);
- }
-
- public Uri getContactUri(int position) {
- Cursor cursor = (Cursor)getItem(position);
+ public Uri getContactUri(Cursor cursor) {
long contactId = cursor.getLong(CONTACT_ID_COLUMN_INDEX);
String lookupKey = cursor.getString(CONTACT_LOOKUP_KEY_COLUMN_INDEX);
return Contacts.getLookupUri(contactId, lookupKey);
}
-}
\ No newline at end of file
+}
diff --git a/src/com/android/contacts/list/JoinContactListFragment.java b/src/com/android/contacts/list/JoinContactListFragment.java
index ebad6f2..12b4ae4 100644
--- a/src/com/android/contacts/list/JoinContactListFragment.java
+++ b/src/com/android/contacts/list/JoinContactListFragment.java
@@ -75,14 +75,9 @@
}
} else {
JoinContactListAdapter adapter = getAdapter();
- if (adapter.isAllContactsListShown()) {
- Cursor suggestionsCursor = ((JoinContactLoader)loader).getSuggestionsCursor();
- adapter.setSuggestionsCursor(suggestionsCursor);
- super.onLoadFinished(loader, data);
- } else {
- adapter.setSuggestionsCursor(data);
- super.onLoadFinished(loader, adapter.getShowAllContactsLabelCursor());
- }
+ Cursor suggestionsCursor = ((JoinContactLoader)loader).getSuggestionsCursor();
+ adapter.setSuggestionsCursor(suggestionsCursor);
+ super.onLoadFinished(loader, data);
}
}
@@ -118,7 +113,8 @@
@Override
protected void onItemClick(int position, long id) {
JoinContactListAdapter adapter = getAdapter();
- if (adapter.isShowAllContactsItemPosition(position)) {
+ int partition = adapter.getPartitionForPosition(position);
+ if (partition == JoinContactListAdapter.PARTITION_SHOW_ALL_CONTACTS) {
mAllContactsListShown = true;
reloadData();
} else {
diff --git a/src/com/android/contacts/list/JoinContactLoader.java b/src/com/android/contacts/list/JoinContactLoader.java
index dc04165..25c9ab4 100644
--- a/src/com/android/contacts/list/JoinContactLoader.java
+++ b/src/com/android/contacts/list/JoinContactLoader.java
@@ -22,7 +22,7 @@
import android.net.Uri;
/**
- * A specialized loader for the Join Contacts UI. It executes up to two queries:
+ * A specialized loader for the Join Contacts UI. It executes two queries:
* join suggestions and (optionally) the full contact list.
*/
public class JoinContactLoader extends CursorLoader {
@@ -30,12 +30,16 @@
private boolean mLoadSuggestionsAndAllContact;
private String[] mProjection;
private Uri mSuggestionUri;
- private MatrixCursor mCachedCursor;
+ private MatrixCursor mSuggestionsCursor;
public JoinContactLoader(Context context) {
super(context, null, null, null, null, null);
}
+ public void setLoadSuggestionsAndAllContacts(boolean flag) {
+ mLoadSuggestionsAndAllContact = flag;
+ }
+
public void setSuggestionUri(Uri uri) {
this.mSuggestionUri = uri;
}
@@ -47,25 +51,22 @@
}
public Cursor getSuggestionsCursor() {
- return mCachedCursor;
+ return mSuggestionsCursor;
}
@Override
public Cursor loadInBackground() {
- if (mLoadSuggestionsAndAllContact) {
- // First execute the suggestions query, then call super.loadInBackground
- // to load the entire list
- mCachedCursor = loadSuggestions();
- return super.loadInBackground();
- } else {
- // Use the default behavior of the super.loadInBackground to load join
- // suggestions only
- setUri(mSuggestionUri);
- setSelection(null);
+ // First execute the suggestions query, then call super.loadInBackground
+ // to load the entire list
+ mSuggestionsCursor = loadSuggestions();
+ if (!mLoadSuggestionsAndAllContact && mSuggestionsCursor.getCount() != 0) {
+ // In case we only need suggestions, send "0" as the search query, which
+ // will always return an empty cursor (but we can still register to
+ // listen for changes on it).
+ setSelection("0");
setSelectionArgs(null);
- setSortOrder(null);
- return super.loadInBackground();
}
+ return super.loadInBackground();
}
/**
@@ -88,8 +89,4 @@
cursor.close();
}
}
-
- public void setLoadSuggestionsAndAllContacts(boolean flag) {
- mLoadSuggestionsAndAllContact = flag;
- }
}
\ No newline at end of file
diff --git a/src/com/android/contacts/list/LegacyContactListAdapter.java b/src/com/android/contacts/list/LegacyContactListAdapter.java
index fb23e4a..34ae708 100644
--- a/src/com/android/contacts/list/LegacyContactListAdapter.java
+++ b/src/com/android/contacts/list/LegacyContactListAdapter.java
@@ -21,10 +21,8 @@
import android.database.Cursor;
import android.net.Uri;
import android.provider.Contacts.People;
-import android.provider.ContactsContract.Contacts;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.ListView;
/**
* A cursor adapter for the People.CONTENT_TYPE content type.
@@ -60,30 +58,27 @@
loader.setSortOrder(People.DISPLAY_NAME);
}
- public boolean isContactStarred() {
- return getCursor().getInt(PERSON_STARRED_COLUMN_INDEX) != 0;
- }
-
@Override
- public String getContactDisplayName() {
- return getCursor().getString(PERSON_DISPLAY_NAME_COLUMN_INDEX);
+ public String getContactDisplayName(int position) {
+ return ((Cursor)getItem(position)).getString(PERSON_DISPLAY_NAME_COLUMN_INDEX);
}
- public Uri getPersonUri() {
- Cursor cursor = getCursor();
+ public Uri getPersonUri(int position) {
+ Cursor cursor = ((Cursor)getItem(position));
long personId = cursor.getLong(PERSON_ID_COLUMN_INDEX);
return ContentUris.withAppendedId(People.CONTENT_URI, personId);
}
@Override
- public View newView(Context context, Cursor cursor, ViewGroup parent) {
+ protected View newView(Context context, int partition, Cursor cursor, int position,
+ ViewGroup parent) {
final ContactListItemView view = new ContactListItemView(context, null);
view.setUnknownNameText(mUnknownNameText);
return view;
}
@Override
- public void bindView(View itemView, Context context, Cursor cursor) {
+ protected void bindView(View itemView, int partition, Cursor cursor, int position) {
ContactListItemView view = (ContactListItemView)itemView;
bindName(view, cursor);
bindPresence(view, cursor);
diff --git a/src/com/android/contacts/list/LegacyPhoneNumberListAdapter.java b/src/com/android/contacts/list/LegacyPhoneNumberListAdapter.java
index a2b9aa5..ed53ef8 100644
--- a/src/com/android/contacts/list/LegacyPhoneNumberListAdapter.java
+++ b/src/com/android/contacts/list/LegacyPhoneNumberListAdapter.java
@@ -63,25 +63,26 @@
}
@Override
- public String getContactDisplayName() {
- return getCursor().getString(PHONE_DISPLAY_NAME_COLUMN_INDEX);
+ public String getContactDisplayName(int position) {
+ return ((Cursor)getItem(position)).getString(PHONE_DISPLAY_NAME_COLUMN_INDEX);
}
- public Uri getPhoneUri() {
- Cursor cursor = getCursor();
+ public Uri getPhoneUri(int position) {
+ Cursor cursor = ((Cursor)getItem(position));
long id = cursor.getLong(PHONE_ID_COLUMN_INDEX);
return ContentUris.withAppendedId(Phones.CONTENT_URI, id);
}
@Override
- public View newView(Context context, Cursor cursor, ViewGroup parent) {
+ protected View newView(Context context, int partition, Cursor cursor, int position,
+ ViewGroup parent) {
final ContactListItemView view = new ContactListItemView(context, null);
view.setUnknownNameText(mUnknownNameText);
return view;
}
@Override
- public void bindView(View itemView, Context context, Cursor cursor) {
+ protected void bindView(View itemView, int partition, Cursor cursor, int position) {
ContactListItemView view = (ContactListItemView)itemView;
bindName(view, cursor);
bindPhoneNumber(view, cursor);
diff --git a/src/com/android/contacts/list/LegacyPostalAddressListAdapter.java b/src/com/android/contacts/list/LegacyPostalAddressListAdapter.java
index f90c564..36df18b 100644
--- a/src/com/android/contacts/list/LegacyPostalAddressListAdapter.java
+++ b/src/com/android/contacts/list/LegacyPostalAddressListAdapter.java
@@ -64,25 +64,26 @@
}
@Override
- public String getContactDisplayName() {
- return getCursor().getString(POSTAL_DISPLAY_NAME_COLUMN_INDEX);
+ public String getContactDisplayName(int position) {
+ return ((Cursor)getItem(position)).getString(POSTAL_DISPLAY_NAME_COLUMN_INDEX);
}
- public Uri getContactMethodUri() {
- Cursor cursor = getCursor();
+ public Uri getContactMethodUri(int position) {
+ Cursor cursor = ((Cursor)getItem(position));
long id = cursor.getLong(POSTAL_ID_COLUMN_INDEX);
return ContentUris.withAppendedId(ContactMethods.CONTENT_URI, id);
}
@Override
- public View newView(Context context, Cursor cursor, ViewGroup parent) {
+ protected View newView(Context context, int partition, Cursor cursor, int position,
+ ViewGroup parent) {
final ContactListItemView view = new ContactListItemView(context, null);
view.setUnknownNameText(mUnknownNameText);
return view;
}
@Override
- public void bindView(View itemView, Context context, Cursor cursor) {
+ protected void bindView(View itemView, int partition, Cursor cursor, int position) {
ContactListItemView view = (ContactListItemView)itemView;
bindName(view, cursor);
bindPostalAddress(view, cursor);
diff --git a/src/com/android/contacts/list/MultiplePhonePickerAdapter.java b/src/com/android/contacts/list/MultiplePhonePickerAdapter.java
index 16ec183..2b1196b 100644
--- a/src/com/android/contacts/list/MultiplePhonePickerAdapter.java
+++ b/src/com/android/contacts/list/MultiplePhonePickerAdapter.java
@@ -148,7 +148,7 @@
String phoneNumber = mPhoneNumbers.get(position);
setPhoneSelected(phoneNumber, !isSelected(phoneNumber));
} else {
- Cursor cursor = getCursor();
+ Cursor cursor = ((Cursor)getItem(position));
cursor.moveToPosition(position - mFilteredPhoneNumbers.size());
long phoneId = cursor.getLong(PHONE_ID_COLUMN_INDEX);
setPhoneSelected(phoneId, !isSelected(phoneId));
@@ -238,29 +238,31 @@
return position < mPhoneNumbers.size() ? 0 : 1;
}
+ // TODO redo as two separate partitions
@Override
public View getView(int position, View convertView, ViewGroup parent) {
- View view;
- if (convertView == null || convertView.getTag() == null) {
- view = newView(getContext(), null, parent);
- } else {
- view = convertView;
- }
-
- boolean showingSuggestion = false;
-
- if (position < mFilteredPhoneNumbers.size()) {
- bindExtraPhoneView(view, position);
- } else {
- Cursor cursor = getCursor();
- cursor.moveToPosition(position - mFilteredPhoneNumbers.size());
- bindView(view, getContext(), cursor);
- }
+ View view = null;
+// if (convertView == null || convertView.getTag() == null) {
+// view = newView(getContext(), null, parent);
+// } else {
+// view = convertView;
+// }
+//
+// boolean showingSuggestion = false;
+//
+// if (position < mFilteredPhoneNumbers.size()) {
+// bindExtraPhoneView(view, position);
+// } else {
+// Cursor cursor = ((Cursor)getItem(position));
+// cursor.moveToPosition(position - mFilteredPhoneNumbers.size());
+// bindView(view, getContext(), cursor);
+// }
return view;
}
@Override
- public View newView(Context context, Cursor cursor, ViewGroup parent) {
+ protected View newView(Context context, int partition, Cursor cursor, int position,
+ ViewGroup parent) {
final MultiplePhonePickerItemView view = new MultiplePhonePickerItemView(context, null);
view.setUnknownNameText(getUnknownNameText());
view.setTextWithHighlightingFactory(getTextWithHighlightingFactory());
@@ -278,8 +280,8 @@
}
@Override
- public void bindView(View itemView, Context context, Cursor cursor) {
- super.bindView(itemView, context, cursor);
+ protected void bindView(View itemView, int partition, Cursor cursor, int position) {
+ super.bindView(itemView, partition, cursor, position);
final MultiplePhonePickerItemView view = (MultiplePhonePickerItemView)itemView;
view.phoneId = Long.valueOf(cursor.getLong(PHONE_ID_COLUMN_INDEX));
@@ -321,10 +323,6 @@
@Override
public int getCount() {
- if (!mDataValid) {
- return 0;
- }
-
return super.getCount() + mFilteredPhoneNumbers.size();
}
diff --git a/src/com/android/contacts/list/PhoneNumberListAdapter.java b/src/com/android/contacts/list/PhoneNumberListAdapter.java
index 0d8975e..2894ed6 100644
--- a/src/com/android/contacts/list/PhoneNumberListAdapter.java
+++ b/src/com/android/contacts/list/PhoneNumberListAdapter.java
@@ -86,8 +86,8 @@
}
@Override
- public String getContactDisplayName() {
- return getCursor().getString(mDisplayNameColumnIndex);
+ public String getContactDisplayName(int position) {
+ return ((Cursor)getItem(position)).getString(mDisplayNameColumnIndex);
}
@Override
@@ -103,17 +103,17 @@
}
/**
- * Builds a {@link Data#CONTENT_URI} for the current cursor
- * position.
+ * Builds a {@link Data#CONTENT_URI} for the given cursor position.
*/
- public Uri getDataUri() {
- Cursor cursor = getCursor();
+ public Uri getDataUri(int position) {
+ Cursor cursor = ((Cursor)getItem(position));
long id = cursor.getLong(PHONE_ID_COLUMN_INDEX);
return ContentUris.withAppendedId(Data.CONTENT_URI, id);
}
@Override
- public View newView(Context context, Cursor cursor, ViewGroup parent) {
+ protected View newView(Context context, int partition, Cursor cursor, int position,
+ ViewGroup parent) {
final ContactListItemView view = new ContactListItemView(context, null);
view.setUnknownNameText(mUnknownNameText);
view.setTextWithHighlightingFactory(getTextWithHighlightingFactory());
@@ -121,9 +121,9 @@
}
@Override
- public void bindView(View itemView, Context context, Cursor cursor) {
+ protected void bindView(View itemView, int partition, Cursor cursor, int position) {
ContactListItemView view = (ContactListItemView)itemView;
- bindSectionHeaderAndDivider(view, cursor);
+ bindSectionHeaderAndDivider(view, position);
bindName(view, cursor);
bindPhoto(view, cursor);
bindPhoneNumber(view, cursor);
@@ -142,8 +142,7 @@
view.showData(cursor, PHONE_NUMBER_COLUMN_INDEX);
}
- protected void bindSectionHeaderAndDivider(final ContactListItemView view, Cursor cursor) {
- final int position = cursor.getPosition();
+ protected void bindSectionHeaderAndDivider(final ContactListItemView view, int position) {
final int section = getSectionForPosition(position);
if (getPositionForSection(section) == position) {
String title = (String)getSections()[section];
diff --git a/src/com/android/contacts/list/PhoneNumberPickerFragment.java b/src/com/android/contacts/list/PhoneNumberPickerFragment.java
index 6e2bb31..4e8ebe9 100644
--- a/src/com/android/contacts/list/PhoneNumberPickerFragment.java
+++ b/src/com/android/contacts/list/PhoneNumberPickerFragment.java
@@ -56,13 +56,11 @@
// if (adapter.isSearchAllContactsItemPosition(position)) {
// searchAllContacts();
// } else {
- adapter.moveToPosition(position);
- pickPhoneNumber(adapter.getDataUri());
+ pickPhoneNumber(adapter.getDataUri(position));
// }
} else {
LegacyPhoneNumberListAdapter adapter = (LegacyPhoneNumberListAdapter)getAdapter();
- adapter.moveToPosition(position);
- pickPhoneNumber(adapter.getPhoneUri());
+ pickPhoneNumber(adapter.getPhoneUri(position));
}
}
diff --git a/src/com/android/contacts/list/PostalAddressListAdapter.java b/src/com/android/contacts/list/PostalAddressListAdapter.java
index 7c86c75..b6f8fc4 100644
--- a/src/com/android/contacts/list/PostalAddressListAdapter.java
+++ b/src/com/android/contacts/list/PostalAddressListAdapter.java
@@ -23,7 +23,6 @@
import android.provider.ContactsContract;
import android.provider.ContactsContract.ContactCounts;
import android.provider.ContactsContract.Data;
-import android.provider.ContactsContract.CommonDataKinds.Phone;
import android.provider.ContactsContract.CommonDataKinds.StructuredPostal;
import android.view.View;
import android.view.ViewGroup;
@@ -79,8 +78,8 @@
}
@Override
- public String getContactDisplayName() {
- return getCursor().getString(mDisplayNameColumnIndex);
+ public String getContactDisplayName(int position) {
+ return ((Cursor)getItem(position)).getString(mDisplayNameColumnIndex);
}
@Override
@@ -99,14 +98,14 @@
* Builds a {@link Data#CONTENT_URI} for the current cursor
* position.
*/
- public Uri getDataUri() {
- Cursor cursor = getCursor();
- long id = cursor.getLong(POSTAL_ID_COLUMN_INDEX);
+ public Uri getDataUri(int position) {
+ long id = ((Cursor)getItem(position)).getLong(POSTAL_ID_COLUMN_INDEX);
return ContentUris.withAppendedId(Data.CONTENT_URI, id);
}
@Override
- public View newView(Context context, Cursor cursor, ViewGroup parent) {
+ protected View newView(Context context, int partition, Cursor cursor, int position,
+ ViewGroup parent) {
final ContactListItemView view = new ContactListItemView(context, null);
view.setUnknownNameText(mUnknownNameText);
view.setTextWithHighlightingFactory(getTextWithHighlightingFactory());
@@ -114,9 +113,9 @@
}
@Override
- public void bindView(View itemView, Context context, Cursor cursor) {
+ protected void bindView(View itemView, int partition, Cursor cursor, int position) {
ContactListItemView view = (ContactListItemView)itemView;
- bindSectionHeaderAndDivider(view, cursor);
+ bindSectionHeaderAndDivider(view, position);
bindName(view, cursor);
bindPhoto(view, cursor);
bindPostalAddress(view, cursor);
@@ -135,8 +134,7 @@
view.showData(cursor, POSTAL_ADDRESS_COLUMN_INDEX);
}
- protected void bindSectionHeaderAndDivider(final ContactListItemView view, Cursor cursor) {
- final int position = cursor.getPosition();
+ protected void bindSectionHeaderAndDivider(final ContactListItemView view, int position) {
final int section = getSectionForPosition(position);
if (getPositionForSection(section) == position) {
String title = (String)getSections()[section];
diff --git a/src/com/android/contacts/list/PostalAddressPickerFragment.java b/src/com/android/contacts/list/PostalAddressPickerFragment.java
index 734873b..95ed7f8 100644
--- a/src/com/android/contacts/list/PostalAddressPickerFragment.java
+++ b/src/com/android/contacts/list/PostalAddressPickerFragment.java
@@ -47,12 +47,12 @@
// searchAllContacts();
// } else {
adapter.moveToPosition(position);
- pickPostalAddress(adapter.getDataUri());
+ pickPostalAddress(adapter.getDataUri(position));
// }
} else {
LegacyPostalAddressListAdapter adapter = (LegacyPostalAddressListAdapter)getAdapter();
adapter.moveToPosition(position);
- pickPostalAddress(adapter.getContactMethodUri());
+ pickPostalAddress(adapter.getContactMethodUri(position));
}
}
diff --git a/src/com/android/contacts/list/StrequentContactListAdapter.java b/src/com/android/contacts/list/StrequentContactListAdapter.java
index 78bd51a..c6ad061 100644
--- a/src/com/android/contacts/list/StrequentContactListAdapter.java
+++ b/src/com/android/contacts/list/StrequentContactListAdapter.java
@@ -174,14 +174,16 @@
}
@Override
- public View newView(Context context, Cursor cursor, ViewGroup parent) {
- ContactListItemView view = (ContactListItemView)super.newView(context, cursor, parent);
+ protected View newView(Context context, int partition, Cursor cursor, int position,
+ ViewGroup parent) {
+ ContactListItemView view = (ContactListItemView)super.newView(context, partition, cursor,
+ position, parent);
view.setOnCallButtonClickListener(mCallButtonListener);
return view;
}
@Override
- public void bindView(View itemView, Context context, Cursor cursor) {
+ protected void bindView(View itemView, int partition, Cursor cursor, int position) {
final ContactListItemView view = (ContactListItemView)itemView;
bindName(view, cursor);
@@ -189,8 +191,7 @@
bindPresence(view, cursor);
// Make the call button visible if requested.
- if (getHasPhoneNumber()) {
- int position = cursor.getPosition();
+ if (getHasPhoneNumber(position)) {
view.showCallButton(mCallButtonId, position);
} else {
view.hideCallButton();
diff --git a/src/com/android/contacts/list/StrequentContactListFragment.java b/src/com/android/contacts/list/StrequentContactListFragment.java
index 9779d10..4d2fd7f 100644
--- a/src/com/android/contacts/list/StrequentContactListFragment.java
+++ b/src/com/android/contacts/list/StrequentContactListFragment.java
@@ -52,7 +52,7 @@
protected void onItemClick(int position, long id) {
ContactListAdapter adapter = getAdapter();
adapter.moveToPosition(position);
- viewContact(adapter.getContactUri());
+ viewContact(adapter.getContactUri(position));
}
@Override
@@ -95,7 +95,7 @@
final int position = (Integer)v.getTag();
ContactListAdapter adapter = getAdapter();
adapter.moveToPosition(position);
- callContact(adapter.getContactUri());
+ callContact(adapter.getContactUri(position));
break;
}
}
diff --git a/src/com/android/contacts/widget/IndexerListAdapter.java b/src/com/android/contacts/widget/IndexerListAdapter.java
new file mode 100644
index 0000000..783ef8d
--- /dev/null
+++ b/src/com/android/contacts/widget/IndexerListAdapter.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.contacts.widget;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.SectionIndexer;
+import android.widget.TextView;
+
+/**
+ * A list adapter that supports section indexer and a pinned header.
+ */
+public abstract class IndexerListAdapter extends PinnedHeaderListAdapter implements SectionIndexer {
+
+ private final int mSectionHeaderTextViewId;
+ private final int mSectionHeaderLayoutResId;
+
+ protected Context mContext;
+ private SectionIndexer mIndexer;
+ private int mIndexedPartition = 0;
+ private boolean mSectionHeaderDisplayEnabled;
+ private View mHeader;
+
+ /**
+ * Constructor.
+ *
+ * @param context
+ * @param sectionHeaderLayoutResourceId section header layout resource ID
+ * @param sectionHeaderTextViewId section header text view ID
+ */
+ public IndexerListAdapter(Context context, int sectionHeaderLayoutResourceId,
+ int sectionHeaderTextViewId) {
+ super(context);
+ mContext = context;
+ mSectionHeaderLayoutResId = sectionHeaderLayoutResourceId;
+ mSectionHeaderTextViewId = sectionHeaderTextViewId;
+ }
+
+ public boolean isSectionHeaderDisplayEnabled() {
+ return mSectionHeaderDisplayEnabled;
+ }
+
+ public void setSectionHeaderDisplayEnabled(boolean flag) {
+ this.mSectionHeaderDisplayEnabled = flag;
+ }
+
+ public int getIndexedPartition() {
+ return mIndexedPartition;
+ }
+
+ public void setIndexedPartition(int partition) {
+ this.mIndexedPartition = partition;
+ }
+
+ public void setIndexer(SectionIndexer indexer) {
+ mIndexer = indexer;
+ }
+
+ public Object[] getSections() {
+ if (mIndexer == null) {
+ return new String[] { " " };
+ } else {
+ return mIndexer.getSections();
+ }
+ }
+
+ /**
+ * @return relative position of the section in the indexed partition
+ */
+ public int getPositionForSection(int sectionIndex) {
+ if (mIndexer == null) {
+ return -1;
+ }
+
+ return mIndexer.getPositionForSection(sectionIndex);
+ }
+
+ /**
+ * @param position relative position in the indexed partition
+ */
+ public int getSectionForPosition(int position) {
+ if (mIndexer == null) {
+ return -1;
+ }
+
+ return mIndexer.getSectionForPosition(position);
+ }
+
+ @Override
+ public int getPinnedHeaderCount() {
+ if (isSectionHeaderDisplayEnabled()) {
+ return super.getPinnedHeaderCount() + 1;
+ } else {
+ return super.getPinnedHeaderCount();
+ }
+ }
+
+ @Override
+ public View createPinnedHeaderView(int viewIndex, ViewGroup parent) {
+ if (isSectionHeaderDisplayEnabled() && viewIndex == getPinnedHeaderCount() - 1) {
+ mHeader = LayoutInflater.from(mContext).
+ inflate(mSectionHeaderLayoutResId, parent, false);
+ return mHeader;
+ } else {
+ return super.createPinnedHeaderView(viewIndex, parent);
+ }
+ }
+
+ @Override
+ public void configurePinnedHeaders(PinnedHeaderListView listView) {
+ super.configurePinnedHeaders(listView);
+
+ if (!isSectionHeaderDisplayEnabled()) {
+ return;
+ }
+
+ int index = getPinnedHeaderCount() - 1;
+ if (mIndexer == null || getCount() == 0) {
+ listView.setHeaderInvisible(index);
+ } else {
+ int listPosition = listView.getPositionAt(listView.getTotalTopPinnedHeaderHeight());
+ int position = listPosition - listView.getHeaderViewsCount();
+
+ int section = -1;
+ int partition = getPartitionForPosition(position);
+ if (partition == mIndexedPartition) {
+ int offset = getOffsetInPartition(position);
+ if (offset != -1) {
+ section = getSectionForPosition(offset);
+ }
+ }
+
+ if (section == -1) {
+ listView.setHeaderInvisible(index);
+ } else {
+ String title = (String)mIndexer.getSections()[section];
+ TextView titleView = (TextView)mHeader.getTag();
+ if (titleView == null) {
+ titleView = (TextView)mHeader.findViewById(mSectionHeaderTextViewId);
+ mHeader.setTag(titleView);
+ }
+ titleView.setText(title);
+
+ // Compute the item position where the current partition begins
+ int partitionStart = getPositionForPartition(mIndexedPartition);
+ if (hasHeader(mIndexedPartition)) {
+ partitionStart++;
+ }
+
+ // Compute the item position where the next section begins
+ int nextSectionPosition = partitionStart + getPositionForSection(section + 1);
+ boolean isLastInSection = position == nextSectionPosition - 1;
+ listView.setFadingHeader(index, listPosition, isLastInSection);
+ }
+ }
+ }
+}
diff --git a/src/com/android/contacts/widget/PinnedHeaderListAdapter.java b/src/com/android/contacts/widget/PinnedHeaderListAdapter.java
index 7492de1..88ab3db 100644
--- a/src/com/android/contacts/widget/PinnedHeaderListAdapter.java
+++ b/src/com/android/contacts/widget/PinnedHeaderListAdapter.java
@@ -16,147 +16,127 @@
package com.android.contacts.widget;
import android.content.Context;
-import android.content.res.ColorStateList;
-import android.graphics.Color;
-import android.graphics.drawable.Drawable;
-import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.CursorAdapter;
-import android.widget.SectionIndexer;
-import android.widget.TextView;
/**
- * A list adapter that supports section indexer and a pinned header.
+ * A subclass of {@link CompositeCursorAdapter} that manages pinned partition headers.
*/
-public abstract class PinnedHeaderListAdapter extends CursorAdapter
- implements SectionIndexer, PinnedHeaderListView.PinnedHeaderAdapter {
+public abstract class PinnedHeaderListAdapter extends CompositeCursorAdapter
+ implements PinnedHeaderListView.PinnedHeaderAdapter {
- private final int mPinnedHeaderBackgroundColor;
- private final int mSectionHeaderTextViewId;
- private final int mSectionHeaderLayoutResId;
+ private boolean mPinnedPartitionHeadersEnabled;
+ private boolean mHeaderVisibility[];
- private SectionIndexer mIndexer;
-
- /**
- * Constructor.
- *
- * @param context
- * @param sectionHeaderLayoutResourceId section header layout resource ID
- * @param sectionHeaderTextViewId section header text view ID
- * @param backgroundColor An approximation of the background color of the
- * pinned header. This color is used when the pinned header is
- * being pushed up. At that point the header "fades away". Rather
- * than computing a faded bitmap based on the 9-patch normally
- * used for the background, we will use a solid color, which will
- * provide better performance and reduced complexity.
- */
- public PinnedHeaderListAdapter(Context context, int sectionHeaderLayoutResourceId,
- int sectionHeaderTextViewId, int backgroundColor) {
- super(context, null, false);
- this.mContext = context;
- mPinnedHeaderBackgroundColor = backgroundColor;
- mSectionHeaderLayoutResId = sectionHeaderLayoutResourceId;
- mSectionHeaderTextViewId = sectionHeaderTextViewId;
+ public PinnedHeaderListAdapter(Context context) {
+ super(context);
}
- public void setIndexer(SectionIndexer indexer) {
- mIndexer = indexer;
+ public PinnedHeaderListAdapter(Context context, int initialCapacity) {
+ super(context, initialCapacity);
}
- public Object [] getSections() {
- if (mIndexer == null) {
- return new String[] { " " };
+ public boolean getPinnedPartitionHeadersEnabled() {
+ return mPinnedPartitionHeadersEnabled;
+ }
+
+ public void setPinnedPartitionHeadersEnabled(boolean flag) {
+ this.mPinnedPartitionHeadersEnabled = flag;
+ }
+
+ public int getPinnedHeaderCount() {
+ if (mPinnedPartitionHeadersEnabled) {
+ return getPartitionCount();
} else {
- return mIndexer.getSections();
+ return 0;
}
}
- public int getPositionForSection(int sectionIndex) {
- if (mIndexer == null) {
- return -1;
- }
-
- return mIndexer.getPositionForSection(sectionIndex);
- }
-
- public int getSectionForPosition(int position) {
- if (mIndexer == null) {
- return -1;
- }
-
- return mIndexer.getSectionForPosition(position);
+ protected boolean isPinnedPartitionHeaderVisible(int partition) {
+ return mPinnedPartitionHeadersEnabled && hasHeader(partition)
+ && !isPartitionEmpty(partition);
}
/**
- * Computes the state of the pinned header. It can be invisible, fully
- * visible or partially pushed up out of the view.
+ * The default implementation creates the same type of view as a normal
+ * partition header.
*/
- public int getPinnedHeaderState(int position) {
- if (mIndexer == null || mCursor == null || mCursor.getCount() == 0) {
- return PINNED_HEADER_GONE;
- }
-
- // The header should get pushed up if the top item shown
- // is the last item in a section for a particular letter.
- int section = getSectionForPosition(position);
- if (section == -1) {
- return PINNED_HEADER_GONE;
- }
-
- int nextSectionPosition = getPositionForSection(section + 1);
- if (nextSectionPosition != -1 && position == nextSectionPosition - 1) {
- return PINNED_HEADER_PUSHED_UP;
- }
-
- return PINNED_HEADER_VISIBLE;
- }
-
- final static class PinnedHeaderCache {
- public TextView titleView;
- public ColorStateList textColor;
- public Drawable background;
- }
-
- /**
- * Configures the pinned header by setting the appropriate text label
- * and also adjusting color if necessary. The color needs to be
- * adjusted when the pinned header is being pushed up from the view.
- */
- public void configurePinnedHeader(View header, int position, int alpha) {
- PinnedHeaderCache cache = (PinnedHeaderCache)header.getTag();
- if (cache == null) {
- cache = new PinnedHeaderCache();
- cache.titleView = (TextView)header.findViewById(mSectionHeaderTextViewId);
- cache.textColor = cache.titleView.getTextColors();
- cache.background = header.getBackground();
- header.setTag(cache);
- }
-
- int section = getSectionForPosition(position);
-
- String title = (String)mIndexer.getSections()[section];
- cache.titleView.setText(title);
-
- if (alpha == 255) {
- // Opaque: use the default background, and the original text color
- header.setBackgroundDrawable(cache.background);
- cache.titleView.setTextColor(cache.textColor);
+ public View createPinnedHeaderView(int partition, ViewGroup parent) {
+ if (hasHeader(partition)) {
+ View view = newHeaderView(getContext(), partition, null, parent);
+ view.setFocusable(false);
+ view.setEnabled(false);
+ bindHeaderView(view, partition, null);
+ return view;
} else {
- // Faded: use a solid color approximation of the background, and
- // a translucent text color
- header.setBackgroundColor(Color.rgb(
- Color.red(mPinnedHeaderBackgroundColor) * alpha / 255,
- Color.green(mPinnedHeaderBackgroundColor) * alpha / 255,
- Color.blue(mPinnedHeaderBackgroundColor) * alpha / 255));
-
- int textColor = cache.textColor.getDefaultColor();
- cache.titleView.setTextColor(Color.argb(alpha,
- Color.red(textColor), Color.green(textColor), Color.blue(textColor)));
+ return null;
}
}
- public View createPinnedHeaderView(ViewGroup parent) {
- return LayoutInflater.from(mContext).inflate(mSectionHeaderLayoutResId, parent, false);
+ public void configurePinnedHeaders(PinnedHeaderListView listView) {
+ if (!mPinnedPartitionHeadersEnabled) {
+ return;
+ }
+
+ int size = getPartitionCount();
+
+ // Cache visibility bits, because we will need them several times later on
+ if (mHeaderVisibility == null || mHeaderVisibility.length != size) {
+ mHeaderVisibility = new boolean[size];
+ }
+ for (int i = 0; i < size; i++) {
+ boolean visible = isPinnedPartitionHeaderVisible(i);
+ mHeaderVisibility[i] = visible;
+ if (!visible) {
+ listView.setHeaderInvisible(i);
+ }
+ }
+
+ // Starting at the top, find and pin headers for partitions preceding the visible one(s)
+ int maxTopHeader = -1;
+ int topHeaderHeight = 0;
+ for (int i = 0; i < size; i++) {
+ if (mHeaderVisibility[i]) {
+ int position = listView.getPositionAt(topHeaderHeight);
+ int partition = getPartitionForPosition(position);
+ if (i > partition) {
+ break;
+ }
+
+ listView.setHeaderPinnedAtTop(i, topHeaderHeight);
+ topHeaderHeight += listView.getPinnedHeaderHeight(i);
+ maxTopHeader = i;
+ }
+ }
+
+ // Starting at the bottom, find and pin headers for partitions following the visible one(s)
+ int maxBottomHeader = size;
+ int bottomHeaderHeight = 0;
+ int listHeight = listView.getHeight();
+ for (int i = size; --i > maxTopHeader;) {
+ if (mHeaderVisibility[i]) {
+ int position = listView.getPositionAt(listHeight - bottomHeaderHeight);
+ if (position < 0) {
+ break;
+ }
+
+ int partition = getPartitionForPosition(position - 1);
+ if (partition == -1 || i <= partition) {
+ break;
+ }
+
+ int height = listView.getPinnedHeaderHeight(i);
+ bottomHeaderHeight += height;
+ listView.setHeaderPinnedAtBottom(i, listHeight - bottomHeaderHeight);
+ maxBottomHeader = i;
+ }
+ }
+
+ // Headers in between the top-pinned and bottom-pinned should be hidden
+ for (int i = maxTopHeader + 1; i < maxBottomHeader; i++) {
+ if (mHeaderVisibility[i]) {
+ listView.setHeaderInvisible(i);
+ }
+ }
}
}
diff --git a/src/com/android/contacts/widget/PinnedHeaderListDemoActivity.java b/src/com/android/contacts/widget/PinnedHeaderListDemoActivity.java
new file mode 100644
index 0000000..66b70f3
--- /dev/null
+++ b/src/com/android/contacts/widget/PinnedHeaderListDemoActivity.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.contacts.widget;
+
+import com.android.contacts.R;
+
+import android.app.ListActivity;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.os.Bundle;
+import android.os.Handler;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+/**
+ * An activity that demonstrates various use cases for the {@link PinnedHeaderListView}.
+ * If we decide to move PinnedHeaderListView to the framework, this class could go
+ * to API demos.
+ */
+public class PinnedHeaderListDemoActivity extends ListActivity {
+
+ public final static class TestPinnedHeaderListAdapter extends PinnedHeaderListAdapter {
+
+ public TestPinnedHeaderListAdapter(Context context) {
+ super(context);
+ setPinnedPartitionHeadersEnabled(true);
+ }
+
+ private String[] mHeaders;
+ private int mPinnedHeaderCount;
+
+ public void setHeaders(String[] headers) {
+ this.mHeaders = headers;
+ }
+
+ @Override
+ protected View newHeaderView(Context context, int partition, Cursor cursor,
+ ViewGroup parent) {
+ LayoutInflater inflater = LayoutInflater.from(context);
+ return inflater.inflate(R.layout.list_section, null);
+ }
+
+ @Override
+ protected void bindHeaderView(View view, int parition, Cursor cursor) {
+ TextView headerText = (TextView)view.findViewById(R.id.header_text);
+ headerText.setText(mHeaders[parition]);
+ }
+
+ @Override
+ protected View newView(Context context, int partition, Cursor cursor, int position,
+ ViewGroup parent) {
+ LayoutInflater inflater = LayoutInflater.from(context);
+ return inflater.inflate(android.R.layout.simple_list_item_1, null);
+ }
+
+ @Override
+ protected void bindView(View v, int partition, Cursor cursor, int position) {
+ TextView text = (TextView)v.findViewById(android.R.id.text1);
+ text.setText(cursor.getString(1));
+ }
+
+ @Override
+ public View createPinnedHeaderView(int viewIndex, ViewGroup parent) {
+ LayoutInflater inflater = LayoutInflater.from(getContext());
+ View view = inflater.inflate(R.layout.list_section, parent, false);
+ view.setFocusable(false);
+ view.setEnabled(false);
+ bindHeaderView(view, viewIndex, null);
+ return view;
+ }
+
+ @Override
+ public int getPinnedHeaderCount() {
+ return mPinnedHeaderCount;
+ }
+ }
+
+ private Handler mHandler = new Handler();
+
+ @Override
+ protected void onCreate(Bundle bundle) {
+ super.onCreate(bundle);
+
+ setContentView(R.layout.pinned_header_list_demo);
+
+ final TestPinnedHeaderListAdapter adapter = new TestPinnedHeaderListAdapter(this);
+
+ Bundle extras = getIntent().getExtras();
+ int[] counts = extras.getIntArray("counts");
+ String[] names = extras.getStringArray("names");
+ boolean[] showIfEmpty = extras.getBooleanArray("showIfEmpty");
+ boolean[] hasHeader = extras.getBooleanArray("headers");
+ int[] delays = extras.getIntArray("delays");
+
+ if (counts == null || names == null || showIfEmpty == null || delays == null) {
+ throw new IllegalArgumentException("Missing required extras");
+ }
+
+ adapter.setHeaders(names);
+ for (int i = 0; i < counts.length; i++) {
+ adapter.addPartition(showIfEmpty[i], names[i] != null);
+ adapter.mPinnedHeaderCount = names.length;
+ }
+ setListAdapter(adapter);
+ for (int i = 0; i < counts.length; i++) {
+ final int sectionId = i;
+ final Cursor cursor = makeCursor(names[i], counts[i]);
+ mHandler.postDelayed(new Runnable() {
+
+ public void run() {
+ adapter.changeCursor(sectionId, cursor);
+
+ }
+ }, delays[i]);
+ }
+ }
+
+ private Cursor makeCursor(String name, int count) {
+ MatrixCursor cursor = new MatrixCursor(new String[]{"_id", name});
+ for (int i = 0; i < count; i++) {
+ cursor.addRow(new Object[]{i, name + "[" + i + "]"});
+ }
+ return cursor;
+ }
+}
diff --git a/src/com/android/contacts/widget/PinnedHeaderListView.java b/src/com/android/contacts/widget/PinnedHeaderListView.java
index 5893ac6..9ed51d8 100644
--- a/src/com/android/contacts/widget/PinnedHeaderListView.java
+++ b/src/com/android/contacts/widget/PinnedHeaderListView.java
@@ -18,19 +18,24 @@
import android.content.Context;
import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
+import android.widget.AdapterView;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.AbsListView.OnScrollListener;
+import android.widget.AdapterView.OnItemSelectedListener;
/**
* A ListView that maintains a header pinned at the top of the list. The
* pinned header can be pushed up and dissolved as needed.
*/
-public class PinnedHeaderListView extends ListView implements OnScrollListener {
+public class PinnedHeaderListView extends ListView
+ implements OnScrollListener, OnItemSelectedListener {
/**
* Adapter interface. The list adapter must implement this interface.
@@ -38,90 +43,94 @@
public interface PinnedHeaderAdapter {
/**
- * Pinned header state: don't show the header.
+ * Returns the overall number of pinned headers, visible or not.
*/
- public static final int PINNED_HEADER_GONE = 0;
-
- /**
- * Pinned header state: show the header at the top of the list.
- */
- public static final int PINNED_HEADER_VISIBLE = 1;
-
- /**
- * Pinned header state: show the header. If the header extends beyond
- * the bottom of the first shown element, push it up and clip.
- */
- public static final int PINNED_HEADER_PUSHED_UP = 2;
+ int getPinnedHeaderCount();
/**
* Creates the pinned header view.
*/
- View createPinnedHeaderView(ViewGroup parent);
+ View createPinnedHeaderView(int viewIndex, ViewGroup parent);
/**
- * Computes the desired state of the pinned header for the given
- * position of the first visible list item. Allowed return values are
- * {@link #PINNED_HEADER_GONE}, {@link #PINNED_HEADER_VISIBLE} or
- * {@link #PINNED_HEADER_PUSHED_UP}.
+ * Configures the pinned headers to match the visible list items. The
+ * adapter should call {@link PinnedHeaderListView#setHeaderPinnedAtTop},
+ * {@link PinnedHeaderListView#setHeaderPinnedAtBottom},
+ * {@link PinnedHeaderListView#setFadingHeader} or
+ * {@link PinnedHeaderListView#setHeaderInvisible}, for each header that
+ * needs to change its position or visibility.
*/
- int getPinnedHeaderState(int position);
-
- /**
- * Configures the pinned header view to match the first visible list item.
- *
- * @param header pinned header view.
- * @param position position of the first visible list item.
- * @param alpha fading of the header view, between 0 and 255.
- */
- void configurePinnedHeader(View header, int position, int alpha);
+ void configurePinnedHeaders(PinnedHeaderListView listView);
}
private static final int MAX_ALPHA = 255;
+ private static final int TOP = 0;
+ private static final int BOTTOM = 1;
+ private static final int FADING = 2;
+
+ private static final class PinnedHeader {
+ View view;
+ boolean visible;
+ int y;
+ int height;
+ int alpha;
+ int state;
+ }
private PinnedHeaderAdapter mAdapter;
- private View mHeaderView;
- private boolean mHeaderViewVisible;
- private float mHeaderOffset;
- private int mHeaderViewWidth;
- private int mHeaderViewHeight;
-
+ private PinnedHeader[] mHeaders;
+ private int mPinnedHeaderBackgroundColor;
+ private RectF mBounds = new RectF();
+ private Paint mPaint = new Paint();
private OnScrollListener mOnScrollListener;
-
- private boolean mHeaderViewConfigured;
+ private OnItemSelectedListener mOnItemSelectedListener;
public PinnedHeaderListView(Context context) {
- super(context);
+ this(context, null, com.android.internal.R.attr.listViewStyle);
}
public PinnedHeaderListView(Context context, AttributeSet attrs) {
- super(context, attrs);
- super.setOnScrollListener(this);
+ this(context, attrs, com.android.internal.R.attr.listViewStyle);
}
public PinnedHeaderListView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
super.setOnScrollListener(this);
+ super.setOnItemSelectedListener(this);
}
- public void setPinnedHeaderView(View view) {
- mHeaderView = view;
-
- // Disable vertical fading when the pinned header is present
- // TODO change ListView to allow separate measures for top and bottom fading edge;
- // in this particular case we would like to disable the top, but not the bottom edge.
- if (mHeaderView != null) {
- setFadingEdgeLength(0);
- }
- mHeaderViewConfigured = false;
- requestLayout();
+ /**
+ * An approximation of the background color of the pinned header. This color
+ * is used when the pinned header is being pushed up. At that point the
+ * header "fades away". Rather than computing a faded bitmap based on the
+ * 9-patch normally used for the background, we will use a solid color,
+ * which will provide better performance and reduced complexity.
+ */
+ public void setPinnedHeaderBackgroundColor(int color) {
+ mPinnedHeaderBackgroundColor = color;
+ mPaint.setColor(mPinnedHeaderBackgroundColor);
}
@Override
public void setAdapter(ListAdapter adapter) {
super.setAdapter(adapter);
mAdapter = (PinnedHeaderAdapter)adapter;
- View headerView = mAdapter.createPinnedHeaderView(this);
- setPinnedHeaderView(headerView);
+ int count = mAdapter.getPinnedHeaderCount();
+ mHeaders = new PinnedHeader[count];
+ for (int i = 0; i < count; i++) {
+ PinnedHeader header = new PinnedHeader();
+ header.view = mAdapter.createPinnedHeaderView(i, this);
+ mHeaders[i] = header;
+ }
+
+ // Disable vertical fading when the pinned header is present
+ // TODO change ListView to allow separate measures for top and bottom fading edge;
+ // in this particular case we would like to disable the top, but not the bottom edge.
+ if (count > 0) {
+ setFadingEdgeLength(0);
+ }
+
+ requestLayout();
}
@Override
@@ -131,32 +140,16 @@
}
@Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- if (mHeaderView != null) {
- if (!mHeaderViewConfigured) {
- configureHeaderView(getFirstVisiblePosition() - getHeaderViewsCount());
- }
- measureChild(mHeaderView, widthMeasureSpec, heightMeasureSpec);
- mHeaderViewWidth = mHeaderView.getMeasuredWidth();
- mHeaderViewHeight = mHeaderView.getMeasuredHeight();
- }
- }
-
- @Override
- protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
- super.onLayout(changed, left, top, right, bottom);
- if (mHeaderView != null) {
- if (!mHeaderViewConfigured) {
- configureHeaderView(getFirstVisiblePosition() - getHeaderViewsCount());
- }
- mHeaderView.layout(0, 0, mHeaderViewWidth, mHeaderViewHeight);
- }
+ public void setOnItemSelectedListener(OnItemSelectedListener listener) {
+ mOnItemSelectedListener = listener;
+ super.setOnItemSelectedListener(this);
}
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
int totalItemCount) {
- configureHeaderView(firstVisibleItem - getHeaderViewsCount());
+ if (mAdapter != null) {
+ mAdapter.configurePinnedHeaders(this);
+ }
if (mOnScrollListener != null) {
mOnScrollListener.onScroll(this, firstVisibleItem, visibleItemCount, totalItemCount);
}
@@ -168,54 +161,175 @@
}
}
- public void configureHeaderView(int position) {
- if (mHeaderView == null) {
- return;
- }
+ /**
+ * Ensures that the selected item is positioned below the top-pinned headers
+ * and above the bottom-pinned ones.
+ */
+ public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
+ int height = getHeight();
- int state = mAdapter.getPinnedHeaderState(position);
- switch (state) {
- case PinnedHeaderAdapter.PINNED_HEADER_GONE: {
- mHeaderViewVisible = false;
- break;
- }
+ int windowTop = 0;
+ int windowBottom = height;
- case PinnedHeaderAdapter.PINNED_HEADER_VISIBLE: {
- mAdapter.configurePinnedHeader(mHeaderView, position, MAX_ALPHA);
- mHeaderOffset = 0;
- mHeaderViewVisible = true;
- break;
- }
-
- case PinnedHeaderAdapter.PINNED_HEADER_PUSHED_UP: {
- int y = 0;
- int alpha = MAX_ALPHA;
- if (getChildCount() != 0) {
- View firstView = getChildAt(0);
- int bottom = firstView.getBottom();
- int headerHeight = mHeaderViewHeight;
- if (bottom < headerHeight) {
- y = (bottom - headerHeight);
- alpha = MAX_ALPHA * (headerHeight + y) / headerHeight;
- }
+ int prevHeaderBottom = 0;
+ for (int i = 0; i < mHeaders.length; i++) {
+ PinnedHeader header = mHeaders[i];
+ if (header.visible) {
+ if (header.state == TOP) {
+ windowTop = header.y + header.height;
+ } else if (header.state == BOTTOM) {
+ windowBottom = header.y;
+ break;
}
- mAdapter.configurePinnedHeader(mHeaderView, position, alpha);
- mHeaderOffset = y;
- mHeaderViewVisible = true;
- break;
}
}
- mHeaderViewConfigured = true;
+
+ View selectedView = getSelectedView();
+ if (selectedView.getTop() < windowTop) {
+ setSelectionFromTop(position, windowTop);
+ } else if (selectedView.getBottom() > windowBottom) {
+ setSelectionFromTop(position, windowBottom - selectedView.getHeight());
+ }
+
+ if (mOnItemSelectedListener != null) {
+ mOnItemSelectedListener.onItemSelected(parent, view, position, id);
+ }
+ }
+
+ public void onNothingSelected(AdapterView<?> parent) {
+ if (mOnItemSelectedListener != null) {
+ mOnItemSelectedListener.onNothingSelected(parent);
+ }
+ }
+
+ public int getPinnedHeaderHeight(int viewIndex) {
+ ensurePinnedHeaderLayout(viewIndex);
+ return mHeaders[viewIndex].view.getHeight();
+ }
+
+ /**
+ * Set header to be pinned at the top.
+ *
+ * @param viewIndex index of the header view
+ * @param y is position of the header in pixels.
+ */
+ public void setHeaderPinnedAtTop(int viewIndex, int y) {
+ ensurePinnedHeaderLayout(viewIndex);
+ PinnedHeader header = mHeaders[viewIndex];
+ header.visible = true;
+ header.y = y;
+ header.state = TOP;
+ }
+
+ /**
+ * Set header to be pinned at the bottom.
+ *
+ * @param viewIndex index of the header view
+ * @param y is position of the header in pixels.
+ */
+ public void setHeaderPinnedAtBottom(int viewIndex, int y) {
+ ensurePinnedHeaderLayout(viewIndex);
+ PinnedHeader header = mHeaders[viewIndex];
+ header.visible = true;
+ header.y = y;
+ header.state = BOTTOM;
+ }
+
+ /**
+ * Set header to be pinned at the top of the first visible item.
+ *
+ * @param viewIndex index of the header view
+ * @param position is position of the header in pixels.
+ */
+ public void setFadingHeader(int viewIndex, int position, boolean fade) {
+ ensurePinnedHeaderLayout(viewIndex);
+
+ View child = getChildAt(position - getFirstVisiblePosition());
+
+ PinnedHeader header = mHeaders[viewIndex];
+ header.visible = true;
+ header.state = FADING;
+ header.alpha = MAX_ALPHA;
+
+ int top = getTotalTopPinnedHeaderHeight();
+ header.y = top;
+ if (fade) {
+ int bottom = child.getBottom() - top;
+ int headerHeight = header.height;
+ if (bottom < headerHeight) {
+ int portion = bottom - headerHeight;
+ header.alpha = MAX_ALPHA * (headerHeight + portion) / headerHeight;
+ header.y = top + portion;
+ }
+ }
+ }
+
+ public void setHeaderInvisible(int viewIndex) {
+ mHeaders[viewIndex].visible = false;
+ }
+
+ private void ensurePinnedHeaderLayout(int viewIndex) {
+ View view = mHeaders[viewIndex].view;
+ if (view.isLayoutRequested()) {
+ view.measure(MeasureSpec.makeMeasureSpec(getWidth(), MeasureSpec.EXACTLY),
+ MeasureSpec.UNSPECIFIED);
+ int height = view.getMeasuredHeight();
+ mHeaders[viewIndex].height = height;
+ view.layout(0, 0, view.getMeasuredWidth(), height);
+ }
+ }
+
+ /**
+ * Returns the sum of heights of headers pinned to the top.
+ */
+ public int getTotalTopPinnedHeaderHeight() {
+ for (int i = mHeaders.length; --i >= 0;) {
+ PinnedHeader header = mHeaders[i];
+ if (header.visible && header.state == TOP) {
+ return header.y + header.height;
+ }
+ }
+ return 0;
+ }
+
+ /**
+ * Returns the list item position at the specified y coordinate.
+ */
+ public int getPositionAt(int y) {
+ do {
+ int position = pointToPosition(0, y);
+ if (position != -1) {
+ return position;
+ }
+ // If position == -1, we must have hit a separator. Let's examine
+ // a nearby pixel
+ y--;
+ } while (y > 0);
+ return 0;
}
@Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
- if (mHeaderViewVisible) {
- canvas.save();
- canvas.translate(0, mHeaderOffset);
- drawChild(canvas, mHeaderView, getDrawingTime());
- canvas.restore();
+ for (int i = mHeaders.length; --i >= 0;) {
+ PinnedHeader header = mHeaders[i];
+ if (header.visible) {
+ View view = header.view;
+ if (header.state == FADING) {
+ int saveCount = canvas.save();
+ canvas.translate(0, header.y);
+ mBounds.set(0, 0, view.getWidth(), view.getHeight());
+ canvas.drawRect(mBounds, mPaint);
+ canvas.saveLayerAlpha(mBounds, header.alpha, Canvas.ALL_SAVE_FLAG);
+ view.draw(canvas);
+ canvas.restoreToCount(saveCount);
+ } else {
+ canvas.save();
+ canvas.translate(0, header.y);
+ view.draw(canvas);
+ canvas.restore();
+ }
+ }
}
}
}
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
index 17ad3f7..0c5ee70 100644
--- a/tests/AndroidManifest.xml
+++ b/tests/AndroidManifest.xml
@@ -43,6 +43,16 @@
</intent-filter>
</activity>
+ <activity android:name=".widget.PinnedHeaderUseCaseActivity"
+ android:label="@string/pinnedHeaderList"
+ >
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.DEFAULT" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+
</application>
<instrumentation android:name="android.test.InstrumentationTestRunner"
@@ -55,7 +65,6 @@
android:label="Contacts launch performance">
</instrumentation>
-
<instrumentation android:name="com.android.contacts.DialerLaunchPerformance"
android:targetPackage="com.android.contacts"
android:label="Dialer launch performance">
diff --git a/tests/res/values/donottranslate_strings.xml b/tests/res/values/donottranslate_strings.xml
index 745b351..93095e3 100644
--- a/tests/res/values/donottranslate_strings.xml
+++ b/tests/res/values/donottranslate_strings.xml
@@ -73,4 +73,12 @@
<item>EDIT (create new raw contact)</item>
<item>EDIT (create new legacy)</item>
</string-array>
+
+ <string name="pinnedHeaderList">Pinned Headers</string>
+
+ <string-array name="pinnedHeaderUseCases">
+ <item>One short section - no headers</item>
+ <item>Two short sections with headers</item>
+ <item>Five short sections with headers</item>
+ </string-array>
</resources>
diff --git a/tests/src/com/android/contacts/tests/widget/PinnedHeaderUseCaseActivity.java b/tests/src/com/android/contacts/tests/widget/PinnedHeaderUseCaseActivity.java
new file mode 100644
index 0000000..b01963f
--- /dev/null
+++ b/tests/src/com/android/contacts/tests/widget/PinnedHeaderUseCaseActivity.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.contacts.tests.widget;
+
+import com.android.contacts.tests.R;
+import com.android.contacts.widget.PinnedHeaderListView;
+
+import android.app.ListActivity;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+
+/**
+ * An activity that demonstrates various use cases for the {@link PinnedHeaderListView}.
+ */
+public class PinnedHeaderUseCaseActivity extends ListActivity {
+
+ private static final int SINGLE_SHORT_SECTION_NO_HEADERS = 0;
+ private static final int TWO_SHORT_SECTIONS_WITH_HEADERS = 1;
+ private static final int FIVE_SHORT_SECTIONS_WITH_HEADERS = 2;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setListAdapter(new ArrayAdapter<String>(this, R.layout.intent_list_item,
+ getResources().getStringArray(R.array.pinnedHeaderUseCases)));
+ }
+
+ @Override
+ protected void onListItemClick(ListView l, View v, int position, long id) {
+ switch (position) {
+ case SINGLE_SHORT_SECTION_NO_HEADERS:
+ startActivity(
+ new int[]{5},
+ new String[]{"Line"},
+ new boolean[]{false},
+ new boolean[]{false},
+ new int[]{0});
+ break;
+ case TWO_SHORT_SECTIONS_WITH_HEADERS:
+ startActivity(
+ new int[]{2, 30},
+ new String[]{"First", "Second"},
+ new boolean[]{true, true},
+ new boolean[]{false, false},
+ new int[]{0, 2000});
+ break;
+ case FIVE_SHORT_SECTIONS_WITH_HEADERS:
+ startActivity(
+ new int[]{1, 5, 5, 5, 5},
+ new String[]{"First", "Second", "Third", "Fourth", "Fifth"},
+ new boolean[]{true, true, true, true, true},
+ new boolean[]{false, false, false, false, false},
+ new int[]{0, 2000, 3000, 4000, 5000});
+ break;
+ }
+ }
+
+ private void startActivity(int[] counts, String[] names, boolean[] headers,
+ boolean[] showIfEmpty, int[] delays) {
+ Intent intent = new Intent();
+ intent.setComponent(new ComponentName("com.android.contacts",
+ "com.android.contacts.widget.PinnedHeaderListDemoActivity"));
+ intent.putExtra("counts", counts);
+ intent.putExtra("names", names);
+ intent.putExtra("headers", headers);
+ intent.putExtra("showIfEmpty", showIfEmpty);
+ intent.putExtra("delays", delays);
+
+ startActivity(intent);
+ }
+}