ExpandableListView是ListView控件的延伸,它能够对数据进行分组显示和隐藏,并统计总数量。可进行滚动,对某一内容高亮显示。

<1>编写xml布局文件,用于获取ExpandableListView对象

sf_activity_contact_list.xml

<span style="font-family:Microsoft YaHei;"><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/contacts_list_holder"
style="@style/SF_ActivityBackground"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/sf_activity_background_color"
android:overScrollMode="always"
android:scrollbarStyle="outsideInset"
android:scrollbars="vertical"> <!-- style="@style/SF_ActivityBackground" --> <ExpandableListView
android:id="@+id/contacts_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fastScrollEnabled="true" >
</ExpandableListView> </LinearLayout></span>

<2>编写Activity界面,获取ExpandableListView对象。并设置适配器和对应的属性、事件等等

package com.snapfish.ui;

import java.util.ArrayList;
import java.util.List; import org.json.JSONException;
import org.json.JSONObject; import android.app.AlertDialog;
import android.app.SearchManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.PixelFormat;
import android.os.Bundle;
import android.support.v4.view.MenuItemCompat;
import android.support.v7.widget.SearchView;
import android.support.v7.widget.SearchView.OnQueryTextListener;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.EditText;
import android.widget.ExpandableListView;
import android.widget.ExpandableListView.OnChildClickListener;
import android.widget.ImageView;
import android.widget.TextView; import com.snapfish.R;
import com.snapfish.android.generated.bean.AddressType;
import com.snapfish.android.generated.bean.Entry;
import com.snapfish.android.generated.bean.MrchShippingOption;
import com.snapfish.android.generated.bean.OpenSocialResponseType;
import com.snapfish.android.generated.bean.Person;
import com.snapfish.android.generated.bean.PublisherOrder;
import com.snapfish.android.generated.bean.PublisherOrderAddress;
import com.snapfish.checkout.IUserData;
import com.snapfish.internal.core.SFConstants;
import com.snapfish.internal.database.SFContactsHolderDB;
import com.snapfish.internal.datamodel.SFContact;
import com.snapfish.internal.datamodel.SFContactDBManager;
import com.snapfish.internal.event.SFEventManager;
import com.snapfish.internal.event.SFIEventListener;
import com.snapfish.internal.event.SFLocaleContactsEvent;
import com.snapfish.internal.event.SFRemoteContactsEvent;
import com.snapfish.util.CShippingOptionsAdapter;
import com.snapfish.util.SFActivityUtils;
import com.snapfish.util.SFContactExpandableListAdapter;
import com.snapfish.util.SFLogger; public class SFContactsListActivity extends ABaseActivity { // implements
// LoaderManager.LoaderCallbacks<Cursor> private static final SFLogger sLogger = SFLogger
.getInstance(SFContactsListActivity.class.getName()); private Context m_ctx; private TextView m_contactNameOverlay;
private boolean m_visible; // private static final int CONTACT_DATA_LOADER = 111; OnScrollListener mItemsListScrollListener = new OnScrollListener() { @Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
m_visible = true; // when stop scrolling
if (scrollState == OnScrollListener.SCROLL_STATE_IDLE) {
m_contactNameOverlay.setVisibility(View.INVISIBLE);
m_visible = false;
}
// when scrolling and the finger is on the screen
else if (scrollState == OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) { }
// when scrolling and the finger perform an action above
else if (scrollState == OnScrollListener.SCROLL_STATE_FLING) { }
} @Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
if (visibleItemCount > 0 && m_visible) {
SFContact contact = mPhoneContacts.get(firstVisibleItem);
m_contactNameOverlay.setText(String.valueOf(contact.getName()
.charAt(0)));
m_contactNameOverlay.setVisibility(View.VISIBLE);
}
}
}; /**************************************************
* The event of getting locale contacts
*************************************************/
private SFIEventListener<SFLocaleContactsEvent> m_localeContactListEvent = new SFIEventListener<SFLocaleContactsEvent>() { @Override
public void onEvent(SFLocaleContactsEvent event) {
// TODO The event of getting locale contacts
sLogger.debug("****************The event of getting locale contacts*****************");
hideProgressDialog();
if (null != event && event.getContactsList() != null) {
mPhoneContacts = event.getContactsList(); showProgressDialog(getString(R.string.sf_please_wait),
getString(R.string.sf_progress_remote_contact_list));
// Get all remote contacts
SFContactDBManager.asyncGetAllRemoteContacts(getSession());
}
}
}; /**************************************************
* The event of getting remote contacts
*************************************************/
private SFIEventListener<SFRemoteContactsEvent> m_remoteContactListEvent = new SFIEventListener<SFRemoteContactsEvent>() { @Override
public void onEvent(SFRemoteContactsEvent event) {
// TODO The event of getting remote contacts
sLogger.debug("****************The event of getting remote contacts*****************");
hideProgressDialog();
if (null != event && event.getOpenSocialResponseType() != null) {
OpenSocialResponseType osrt = event.getOpenSocialResponseType();
if (null != osrt) {
List<Entry> entries = osrt.getEntryList();
if (null != entries && !entries.isEmpty()) {
int entrySize = entries.size();
for (int i = 0; i < entrySize; i++) {
try {
Person person = Person.newFromJSON(entries.get(
i).toJSON());
if (null != person) {
AddressType address = person.getAddress();
String displayName = person
.getDisplayName();
if (isValidContact(address)
&& !TextUtils.isEmpty(displayName)) {
SFContact contact = new SFContact(
IUserData.EUserDataType.SNAPFISH,
person.getId(),
displayName,
address.getStreet1(),
address.getCity(),
address.getProvince(),
address.getPostalCode(),
SFContact.ContactAddressType.TYPE_OTHER); contact.setGuessedPrecision(SFContact.GuessedPrecision.ABSOLUTE);
contact.setCountry(address.getCountry() == null ? getResources()
.getConfiguration().locale
.getCountry() : address
.getCountry());
contact.setAddressGuessed(false);
contact.setIsConfirmed(true);
contact.setPhone(address.getPhone());
mPhoneContacts.add(contact);
}
}
} catch (JSONException e) {
sLogger.error(e.getMessage());
}
}
}
}
} onContactsListCreated();
}
}; /*
* carry over from order review activity in case if contact has to be edited
* or order update must happen on this screen
*/
private PublisherOrder m_publisherOrder;
private int m_orderQuantity;
private long m_mrchId; private SFContact mSelectedAddress;
private List<SFContact> mPhoneContacts;
private ExpandableListView mContactsListView;
private SFContactExpandableListAdapter mContactsListAdapter;
private List<MrchShippingOption> m_mrchShippingOptions = new ArrayList<MrchShippingOption>(); private String m_shippingOptionDesc;
private String m_shippingOptionType; /* search */
private MenuItem searchItem;
private SearchView mSearchView;
private ImageView closeBtn;
private EditText searchEditText;
public static boolean isSearchQuery;
private String mSearchString; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.sf_activity_contact_list); m_ctx = this; // getSupportLoaderManager().initLoader(CONTACT_DATA_LOADER, null,
// this); // Display home and finish current activity
SFActivityUtils.displayHomeAsUp(this); getDataFromIntent();
mContactsListView = (ExpandableListView) findViewById(R.id.contacts_list);
/*
* on child click the action that happens will depend whether contact
* selected is confirmed or not, meaning that the end-user has validated
* address and order can be safely sent to selected address
*/
mContactsListView.setOnChildClickListener(new OnChildClickListener() {
@Override
public boolean onChildClick(ExpandableListView parent, View v,
int groupPosition, int childPosition, long id) {
mSelectedAddress = (SFContact) mContactsListAdapter.getChild(
groupPosition, childPosition);
/*
* in the event when address precision is below 100% it must be
* validated by the end-user so the CShippingAddressActivity is
* launched first
*/
Intent exitIntent = null;
if (mSelectedAddress.getGuessedPrecision() != SFContact.GuessedPrecision.ABSOLUTE) {
exitIntent = new Intent(SFContactsListActivity.this,
SFShippingAddressActivity.class);
/* pass contact _id */
exitIntent.putExtra(
SFContactsHolderDB.SFContactColumns._ID,
mSelectedAddress.getId());
/* selected shipping address for edit */
exitIntent.putExtra(SFConstants.SF_SHIPPING_ADDRESS,
handleLocalization(mSelectedAddress));
/* shipping options */
exitIntent.putExtra(
SFConstants.MRCH_SHIP_OPTIONS,
getIntent().getStringArrayExtra(
SFConstants.MRCH_SHIP_OPTIONS));
/* order ID ? */
exitIntent.putExtra(SFConstants.ORDERID,
m_publisherOrder.getOrderId());
/* quantity ? */
exitIntent.putExtra(SFConstants.ORDER_QUANTITY, getIntent()
.getIntExtra(SFConstants.ORDER_QUANTITY, -1));
/* mrchId */
exitIntent.putExtra(SFConstants.MRCH_ID, getIntent()
.getLongExtra(SFConstants.MRCH_ID, -1));
/* and finally order */
exitIntent.putExtra(SFConstants.SF_ORDER, orderAsString()); startActivityForResult(exitIntent,
SFConstants.SF_REQUEST_CODE_SHIPPING_ADDRESS);
} else {/* adding selected address to order */
m_publisherOrder.setShippingAddress(mSelectedAddress); resetShippingOptions(); if (m_shippingOptionDesc != null
&& m_shippingOptionType != null) {
exitIntent = new Intent();
exitIntent.putExtra(SFConstants.SF_ORDER,
orderAsString());
exitIntent.putExtra(
SFConstants.SF_SHIPPING_OPTION_DESCRIPTION,
m_shippingOptionDesc);
exitIntent.putExtra(
SFConstants.SF_SHIPPING_OPTION_SHIPPING_TYPE,
m_shippingOptionType);
setResult(RESULT_OK, exitIntent);
finish();
}
}
return false;
}
}); } @Override
protected void onResume() {
// register the event of getting locale contacts
SFEventManager.subscribe(m_ctx, SFLocaleContactsEvent.class,
m_localeContactListEvent); // register the event of getting remote contacts
SFEventManager.subscribe(m_ctx, SFRemoteContactsEvent.class,
m_remoteContactListEvent); m_contactNameOverlay = (TextView) LayoutInflater.from(m_ctx).inflate(
R.layout.sf_contact_pop_overlay, null);
m_contactNameOverlay.setVisibility(View.INVISIBLE);
WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.TYPE_APPLICATION,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE,
PixelFormat.TRANSLUCENT);
WindowManager windowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
windowManager.addView(m_contactNameOverlay, lp); showProgressDialog(getString(R.string.sf_please_wait),
getString(R.string.sf_progress_locale_contact_list)); // Get all local contacts
SFContactDBManager.asyncGetAllLocalContacts(getSession()); // initializeData();
// ctx = this;
// mContactsListView.setAdapter(new ContactExpandListAdapter(group,
// child, ctx)); super.onResume();
} @Override
protected void onPause() {
// uninstall the event of getting locale contacts
SFEventManager.unsubscribe(m_ctx, SFLocaleContactsEvent.class,
m_localeContactListEvent); // uninstall the event of getting remote contacts
SFEventManager.unsubscribe(m_ctx, SFRemoteContactsEvent.class,
m_remoteContactListEvent); super.onPause();
} /**
* simply copies street1 into street2 whenever street2 is a mandatory field
* in the address entry
*
* @param contact
* @return
*/
private String handleLocalization(PublisherOrderAddress contact) {
if (getResources().getBoolean(R.bool.sf_address_street2_required)) {
sLogger.debug("not a localized address: " + contact.toString());
if (contact.getStreet2() == null) {
contact.setStreet2(contact.getStreet1());
contact.setStreet1(null);
}
} String convertedAddr = "";
try {
convertedAddr = contact.toJSON().toString();
sLogger.debug("localized address: " + convertedAddr);
} catch (JSONException e) {
sLogger.error("cannot convert selected address: " + e);
} return convertedAddr;
} @Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data); if (requestCode == SFConstants.SF_REQUEST_CODE_SHIPPING_ADDRESS) {
if (resultCode == RESULT_OK) {
/*
* after a contact had been verified by end-user it's precision
* level is moved to ABSOLUTE
*/
mSelectedAddress
.setGuessedPrecision(SFContact.GuessedPrecision.ABSOLUTE);
setResult(RESULT_OK, data);
finish();
}
} } /**
* carry over the data required by CShippingAddressActivity
*/
private void getDataFromIntent() {
Intent intent = getIntent();
String order = intent.getStringExtra(SFConstants.SF_ORDER);
String[] mrchShippingOptions = getIntent().getStringArrayExtra(
SFConstants.MRCH_SHIP_OPTIONS); if (order != null) {
try {
m_publisherOrder = PublisherOrder.newFromJSON(new JSONObject(
order));
if (null != mrchShippingOptions) {
for (String mrchShippingOption : mrchShippingOptions) {
m_mrchShippingOptions
.add(MrchShippingOption
.newFromJSON(new JSONObject(
mrchShippingOption)));
}
}
} catch (JSONException e) {
sLogger.error("cannot parse order: " + order);
sLogger.error("cannot parse order: " + e);
}
} else {
showWarningDialog("Order failed", "order not found in intent");
}
} /**
* initializes the contacts list adapter
*/
private void onContactsListCreated() {
/* set adapter */
mContactsListAdapter = new SFContactExpandableListAdapter(this,
mPhoneContacts);
mContactsListView.setAdapter(mContactsListAdapter);
/* hide the divider */
mContactsListView.setDivider(null);
/*
* hide indicator, by design it's the first letter of a contact that
* takes place of the indicator
*/
mContactsListView.setGroupIndicator(null);
/*
* make list state expanded - the first time it should display expanded
* list of contacts
*/
for (int i = 0; i < mContactsListAdapter.getGroupCount(); i++) {
mContactsListView.expandGroup(i);
} /*
* onScrollListener
*/
mContactsListView.setOnScrollListener(mItemsListScrollListener);
} // private List<String> group;// 组列表
// private List<List<String>> child;// 子列表
// private Context ctx;
//
// private void initializeData() {
// group = new ArrayList<String>();
// child = new ArrayList<List<String>>();
//
// addInfo("Andy", new String[] { "male", "138123***", "GuangZhou" });
// addInfo("Fairy", new String[] { "female", "138123***", "GuangZhou" });
// addInfo("Jerry", new String[] { "male", "138123***", "ShenZhen" });
// addInfo("Tom", new String[] { "female", "138123***", "ShangHai" });
// addInfo("Bill", new String[] { "male", "138231***", "ZhanJiang" });
// }
//
// private void addInfo(String group, String[] child) {
// this.group.add(group);
// List<String> childItem = new ArrayList<String>();
// for (int i = 0; i < child.length; i++) {
// childItem.add(child[i]);
// }
//
// this.child.add(childItem);
// } /**
* converts order to string
*
* @return
*/
private String orderAsString() {
String orderAsString = "";
try {
orderAsString = m_publisherOrder.toJSON().toString();
} catch (JSONException e) {
e.printStackTrace();
}
return orderAsString;
} private void resetShippingOptions() {
PublisherOrder order = m_publisherOrder;
PublisherOrderAddress cnt = null;
try {
cnt = PublisherOrderAddress.newFromJSON(new JSONObject(
handleLocalization(order.getShippingAddress())));
sLogger.debug("order address: " + cnt.toJSON());
} catch (JSONException e) {
e.printStackTrace();
} if (m_mrchShippingOptions.size() > 0) {
CShippingOptionsAdapter soa = new CShippingOptionsAdapter(
getApplicationContext(), getSession().getAppCredentials()
.getLocale(), m_mrchShippingOptions, m_mrchId,
m_orderQuantity, cnt); String shipping = getApplicationContext().getResources().getString(
R.string.sf_order_shipping_opt_no_colon); sLogger.debug("getSession().getAppCredentials().getLocale(): "
+ getSession().getAppCredentials().getLocale());
sLogger.debug("soa.getAvailableShipOptions().size(): "
+ soa.getAvailableShipOptions().size()); if (soa.getAvailableShipOptions().size() > 0) {
MrchShippingOption item = soa.getAvailableShipOptions().get(0);
String shippingDescription = item.getDescription();
String shippingType = Character.toString(
shippingDescription.charAt(0)).toUpperCase()
+ shippingDescription.substring(1);
order.setShippingOption(item.getShippingCode());
order.setShipping(0.0f);
m_shippingOptionDesc = soa.estimatedDeliveryDays(item
.getShippingCode());
m_shippingOptionType = shippingType + shipping; showProgressDialog(
getResources().getString(R.string.sf_please_wait),
getResources().getString(
R.string.sf_progress_update_order)); } else {
showErrorAlertDialog();
}
}
} private void showErrorAlertDialog() {
AlertDialog.Builder alertDialog = new AlertDialog.Builder(
SFContactsListActivity.this);
alertDialog.setTitle(R.string.sf_warning_dialog_title);
alertDialog.setMessage(R.string.sf_error_no_shipping_options)
.setPositiveButton("OK", new DialogInterface.OnClickListener() { @Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
}).show();
} @Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.sf_menu_p2r, menu);
searchItem = menu.findItem(R.id.p2r_action_search);
mSearchView = (SearchView) MenuItemCompat.getActionView(searchItem); searchEditText = (EditText) mSearchView
.findViewById(R.id.search_src_text);
searchEditText.setHintTextColor(getResources().getColor(
R.color.sf_button_normal));
searchEditText.setHint(R.string.sf_contacts_search_hint); SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
ComponentName searchComponent = new ComponentName(this,
SFContactsListActivity.class); mSearchView.setSearchableInfo(searchManager
.getSearchableInfo(searchComponent));
mSearchView.setIconifiedByDefault(true);
mSearchView.setSubmitButtonEnabled(false); closeBtn = (ImageView) mSearchView.findViewById(R.id.search_close_btn); MenuItemCompat.setOnActionExpandListener(searchItem,
new MenuItemCompat.OnActionExpandListener() { @Override
public boolean onMenuItemActionCollapse(MenuItem arg0) {
sLogger.debug("search field collapsed");
return true;
} @Override
public boolean onMenuItemActionExpand(MenuItem arg0) {
sLogger.debug("search field expanded");
return true;
} }); closeBtn.setOnClickListener(new View.OnClickListener() { @Override
public void onClick(View v) {
searchEditText.setText("");
}
}); mSearchView.setOnQueryTextListener(new OnQueryTextListener() { @Override
public boolean onQueryTextSubmit(String arg0) {
return false;
} @Override
public boolean onQueryTextChange(String searchText) {
if (searchText.length() > 0) {
mSearchString = searchText;
sLogger.debug("search text is: " + searchText);
} else {
mSearchString = null;
sLogger.debug("reset list");
}
return true;
}
}); return super.onCreateOptionsMenu(menu);
} /**
* Invoking this method when the item option of menu selected
*/
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// when the top|left icon selected,finish current activity
if (item.getItemId() == android.R.id.home) {
finish();
overridePendingTransition(R.anim.fade_in, R.anim.fade_out);
return true;
}
return super.onOptionsItemSelected(item);
} private boolean isValidContact(AddressType addressType) {
if (addressType == null)
return false; if (!TextUtils.isEmpty(addressType.getStreet1())
&& !TextUtils.isEmpty(addressType.getCity())
&& !TextUtils.isEmpty(addressType.getProvince())
&& !TextUtils.isEmpty(addressType.getPhone()))
return true; return false;
} }

<3>编写布局文件sf_view_contact_sort_header.xml。用于展示租名和记录数量。

<?

xml version="1.0" encoding="utf-8"?

>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:paddingTop="5dp"
android:paddingBottom="5dp"
android:paddingLeft="@dimen/sf_standard_layout_padding"
android:paddingRight="@dimen/sf_standard_layout_padding"
android:layout_width="match_parent"
android:layout_height="wrap_content" > <TextView
android:id="@+id/sf_contact_sort_by_name"
style="@style/SF_MediumBlueBoldTxt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="5dp"
android:layout_marginLeft="5dp"
android:text="@string/sf_contacts_sorting_txt" /> <TextView
android:id="@+id/sf_contact_sort_counter"
style="@style/SF_SmallLightGreyTxt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_alignParentTop="true"
android:gravity="right"
android:text="@string/sf_contacts_counter_txt"
android:visibility="gone" />
</RelativeLayout>

<4>编写布局文件sf_view_contact_item.xml,用于展示数据的内容:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
style="@style/SF_SelectableSectionBackground"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/sf_activity_background_color"
android:paddingBottom="5dp"
android:paddingLeft="@dimen/sf_contact_item_layout_padding"
android:paddingRight="@dimen/sf_contact_item_layout_padding" > <LinearLayout
android:id="@+id/sf_ll_contact_item_divider"
style="@style/SF_HorizontalDivider"
android:layout_width="match_parent"
android:layout_marginBottom="5dp"
android:orientation="horizontal" /> <include
android:id="@+id/sf_include_contact_item_photo"
android:layout_width="65dp"
android:layout_height="65dp"
android:layout_alignLeft="@+id/sf_ll_contact_item_divider"
android:layout_alignTop="@+id/sf_ll_contact_item_divider"
android:layout_centerHorizontal="true"
android:layout_centerInParent="true"
android:layout_centerVertical="true"
android:layout_marginTop="5dp"
layout="@layout/sf_view_contact_graphics"
android:visibility="visible" /> <LinearLayout
android:id="@+id/sf_ll_contact_item_details"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="5dp"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:layout_marginTop="5dp"
android:layout_toRightOf="@id/sf_include_contact_item_photo"
android:gravity="left|center_horizontal"
android:orientation="vertical" > <!-- name --> <TextView
android:id="@+id/sf_tv_contact_item_name"
style="@style/SF_MediumBlackTxt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left"
android:ellipsize="end"
android:singleLine="true"
android:text="@string/sf_name_placeholder" />
<!-- name end --> <!-- address --> <TextView
android:id="@+id/sf_tv_contact_item_address"
style="@style/SF_SmallGreyTxt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left"
android:ellipsize="end"
android:singleLine="false"
android:text="@string/sf_address_placeholder"
android:textAlignment="center" />
<!-- address end --> <TextView
android:id="@+id/sf_tv_contact_item_phone"
style="@style/SF_MediumLightGreyTxt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:minLines="1"
android:scrollHorizontally="false"
android:singleLine="true"
android:text="@string/sf_adress_type_txt"
android:visibility="gone" />
</LinearLayout> </RelativeLayout>

<5>编写SFContactExpandableListAdapter.java。继承BaseExpandableListAdapter.java

package com.snapfish.util;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap; import android.annotation.SuppressLint;
import android.content.ContentUris;
import android.content.Context;
import android.content.res.Resources;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.net.Uri;
import android.provider.ContactsContract.Contacts;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseExpandableListAdapter;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView; import com.snapfish.R;
import com.snapfish.checkout.IUserData;
import com.snapfish.internal.datamodel.SFContact;
import com.snapfish.ui.SFContactsListActivity; public class SFContactExpandableListAdapter extends BaseExpandableListAdapter { // implements
// LoaderManager.LoaderCallbacks<Cursor>
private static final SFLogger sLogger = SFLogger
.getInstance(SFContactsListActivity.class.getName()); private Context m_ctx;
private ViewHolder m_viewHolder;
private List<SFContact> mCombinedContacts;
private Map<String, List<SFContact>> mSortedContacts; public SFContactExpandableListAdapter(Context mContext,
List<SFContact> mCombinedContacts) {
this.m_ctx = mContext;
this.mCombinedContacts = mCombinedContacts;
this.mSortedContacts = getSortedContacts();
m_viewHolder = new ViewHolder();
} @Override
public int getGroupCount() {
return mSortedContacts.size();
} @Override
public int getChildrenCount(int groupPosition) {
List<SFContact> children = mSortedContacts
.get(getKeyByPosition(groupPosition));
return children.size();
} @Override
public Object getGroup(int groupPosition) {
List<SFContact> group = mSortedContacts
.get(getKeyByPosition(groupPosition));
return group;
} @Override
public Object getChild(int groupPosition, int childPosition) {
@SuppressWarnings("unchecked")
List<SFContact> selectedGroup = (List<SFContact>) getGroup(groupPosition);
if (selectedGroup.size() < childPosition)
return null; return selectedGroup.get(childPosition);
} @Override
public long getGroupId(int groupPosition) {
return groupPosition;
} @SuppressWarnings("unchecked")
@Override
public long getChildId(int groupPosition, int childPosition) {
List<SFContact> childGroup = (List<SFContact>) getGroup(groupPosition);
if (null != childGroup && childGroup.get(childPosition) != null)
return childPosition; return 0;
} @Override
public boolean hasStableIds() {
return false;
} @Override
public View getGroupView(int groupPosition, boolean isExpanded,
View convertView, ViewGroup parent) {
View row = LayoutInflater.from(m_ctx).inflate(
R.layout.sf_view_contact_sort_header, parent, false);
TextView sortTextView = (TextView) row
.findViewById(R.id.sf_contact_sort_by_name);
TextView countTextView = (TextView) row
.findViewById(R.id.sf_contact_sort_counter);
String letter = getKeyByPosition(groupPosition); if (groupPosition == 0) {
countTextView.setVisibility(View.VISIBLE);
countTextView.setText("" + mCombinedContacts.size() + " "
+ m_ctx.getString(R.string.sf_contacts_counter_txt));
} sortTextView.setText(" " + letter);
return row;
} @Override
public View getChildView(int groupPosition, int childPosition,
boolean isLastChild, View convertView, ViewGroup parent) {
SFContact contact = (SFContact) getChild(groupPosition, childPosition);
if (contact != null) {
// SFContact.ContactAddressType displayType = contact
// .getAddressType();
String displayName = contact.getName();
String displayAddress = getContactAddress(contact); View row = LayoutInflater.from(m_ctx).inflate(
R.layout.sf_view_contact_item, parent, false); // contact name
m_viewHolder.m_name = (TextView) row
.findViewById(R.id.sf_tv_contact_item_name);
m_viewHolder.m_name.setText(displayName); // contact address
m_viewHolder.m_address = (TextView) row
.findViewById(R.id.sf_tv_contact_item_address);
m_viewHolder.m_address.setText(displayAddress); /* contact address type */
m_viewHolder.m_phone = (TextView) row
.findViewById(R.id.sf_tv_contact_item_phone); // contact photo
setContactIcon(row, contact, getKeyByPosition(groupPosition)); // if (isMultipleAddress(groupPosition, childPosition)
// || contact.getGuessedPrecision() ==
// SFContact.GuessedPrecision.LOW) {
// showAddressType(m_viewHolder.m_phone,
// contact.getGuessedPrecision(), displayType);
// } row.setTag(m_viewHolder);
return row;
} return convertView;
} /**
* to avoid displaying address type when we do not have matching contacts
*
* @param groupPosition
* @param childPosition
* @return
*/
private boolean isMultipleAddress(int groupPosition, int childPosition) {
@SuppressWarnings("unchecked")
List<SFContact> group = (List<SFContact>) getGroup(groupPosition);
SFContact reference = (SFContact) getChild(groupPosition, childPosition);
String referenceName = reference.getName();
int i = group.size(); for (int a = 0; a < i; a++) {
if (a != childPosition)
if (referenceName.equalsIgnoreCase(group.get(a).getName()))
return true;
}
return false;
} @Override
public boolean isChildSelectable(int groupPosition, int childPosition) {
return true;
} /**
* contact icons are of three kinds: image, icon, or warning message
*
* @param aContact
* @param letter
* @param addrPositiveMatch
*/
private void setContactIcon(View row, SFContact aContact, String letter) {
// TODO guessed precision was not set in the DB - set it up
SFContact.GuessedPrecision addrPositiveMatch = aContact
.getGuessedPrecision(); ViewGroup contactImageView = (ViewGroup) row
.findViewById(R.id.sf_include_contact_item_photo); if (contactImageView != null) {
ImageView unverifiedAddress = (ImageView) contactImageView
.findViewById(R.id.sf_iv_contact_need_confirmation);
LinearLayout noCntPhoto = (LinearLayout) contactImageView
.findViewById(R.id.sf_ll_contact_no_photo);
TextView cntFirstLetter = (TextView) noCntPhoto
.findViewById(R.id.sf_tv_contact_name_letter);
ImageView defaultCntIcon = (ImageView) contactImageView
.findViewById(R.id.sf_iv_contact_default_photo);
defaultCntIcon.setVisibility(View.GONE);
cntFirstLetter.setVisibility(View.GONE);
noCntPhoto.setVisibility(View.GONE); /* get profile photo bitmap to load into ImageView */
Bitmap bmp = getProfilePhoto(aContact); switch (addrPositiveMatch) {
case ABSOLUTE:
if (bmp != null) {
defaultCntIcon.setVisibility(View.VISIBLE);
defaultCntIcon.setImageBitmap(bmp);
} else {
noCntPhoto.setVisibility(View.VISIBLE);
noCntPhoto.setBackgroundColor(Color
.parseColor(getRandomColor()));
cntFirstLetter.setVisibility(View.VISIBLE);
cntFirstLetter.setText(letter);
}
break;
case HIGH:
unverifiedAddress.setVisibility(View.VISIBLE);
break;
case MEDIUM:
unverifiedAddress.setVisibility(View.VISIBLE);
break;
case LOW:
break;
default:
unverifiedAddress.setVisibility(View.VISIBLE);
break;
}
}
} private void showAddressType(TextView typeView,
SFContact.GuessedPrecision precisionLevel,
SFContact.ContactAddressType displayType) {
Resources r = m_ctx.getResources();
// typeView.setVisibility(View.VISIBLE);
typeView.setVisibility(View.GONE); if (precisionLevel != SFContact.GuessedPrecision.ABSOLUTE) {// not
// verified
// (guessed)
// address
typeView.setTextColor(r
.getColor(R.color.sf_error_message_txt_color));
typeView.setText(r.getString(R.string.sf_verify_address_label));
} else if (displayType != null) { // verified address
typeView.setText(getAddressType(displayType));
}
} private int colorsUsed; /**
* using preset swatches to set background colors in the layout
*
* @return
*/
private String getRandomColor() {
String[] colorChoises = m_ctx.getResources().getStringArray(
R.array.sf_swatches_sp_set); if (colorsUsed == colorChoises.length)
colorsUsed = 0; String colorOut = colorChoises[colorsUsed]; colorsUsed++;
return colorOut;
} private Bitmap getProfilePhoto(SFContact aContact) {
if (aContact.getContactSource().equals(
IUserData.EUserDataType.PHONEBOOK)) {
InputStream photoStream = getPhotoAsInputSteam(aContact);
if (photoStream != null)
return BitmapFactory.decodeStream(photoStream);
}
return null;
} private String getKeyByPosition(int groupPosition) {
int keyPosition = 0;
Iterator<String> iter = mSortedContacts.keySet().iterator();
while (iter.hasNext()) {
String key = iter.next();
if (keyPosition == groupPosition) {
return key;
}
keyPosition++;
}
return null;
} /**
* extracting first letter of display name
*
* @return
*/
private Collection<String> extractFirstLetter() {
Collection<String> sortByLetter = new HashSet<String>();
int index = mCombinedContacts.size();
Collections.sort(mCombinedContacts, new Comparator<SFContact>() { @Override
public int compare(SFContact lhs, SFContact rhs) {
return lhs.getName().compareTo(rhs.getName());
}
});
for (int i = 0; i < index; i++) {
char letter = mCombinedContacts.get(i).getName().charAt(0);
sortByLetter.add(String.valueOf(letter).toUpperCase());
}
return sortByLetter;
} private Map<String, List<SFContact>> getSortedContacts() {
List<SFContact> selectedContact = null;
mSortedContacts = new TreeMap<String, List<SFContact>>(); Iterator<String> firstLetter = extractFirstLetter().iterator();
while (firstLetter.hasNext()) {
String s = firstLetter.next();
int index = mCombinedContacts.size();
selectedContact = new ArrayList<SFContact>();
for (int i = 0; i < index; i++) {
String initChar = String.valueOf(mCombinedContacts.get(i)
.getName().charAt(0));
if (s.equalsIgnoreCase(initChar)) {
selectedContact.add(mCombinedContacts.get(i));
}
}
mSortedContacts.put(s, selectedContact);
} return mSortedContacts;
} /**
* retrieves address type string from arrays.xml
*
* @param addressType
* @return
*/
private String getAddressType(SFContact.ContactAddressType addressType) {
String addrTypeOut = "";
int arrSize = m_ctx.getResources().getStringArray(
R.array.sf_address_type).length;
if (arrSize != 0 && arrSize == 3) {
switch (addressType) {
case TYPE_HOME:
addrTypeOut = m_ctx.getResources().getStringArray(
R.array.sf_address_type)[0];
break;
case TYPE_WORK:
addrTypeOut = m_ctx.getResources().getStringArray(
R.array.sf_address_type)[1];
break;
case TYPE_OTHER:
default:
addrTypeOut = m_ctx.getResources().getStringArray(
R.array.sf_address_type)[2];
break;
}
} else
addrTypeOut = "UNKNOWN"; return addrTypeOut;
} private InputStream getPhotoAsInputSteam(SFContact contact) {
byte[] data = contact.getContactPhotoData(); if (data != null) {
return new ByteArrayInputStream(contact.getContactPhotoData());
} return null;
} @SuppressLint("InlinedApi")
private InputStream openPhoto(long contactId) {
Uri contactUri = ContentUris.withAppendedId(Contacts.CONTENT_URI,
contactId);
Uri photoUri = Uri.withAppendedPath(contactUri,
Contacts.Photo.CONTENT_DIRECTORY);
Cursor cursor = m_ctx.getContentResolver().query(photoUri,
new String[] { Contacts.Photo.PHOTO }, null, null, null);
if (cursor == null) {
return null;
}
try {
if (cursor.moveToFirst()) {
byte[] data = cursor.getBlob(0);
if (data != null) {
return new ByteArrayInputStream(data);
}
}
} finally {
cursor.close();
}
return null;
} private String getContactAddress(SFContact contact) {
StringBuilder sb = new StringBuilder();
sb.append(contact.getCountry() + " ");
sb.append(contact.getProvince() + " ");
sb.append(contact.getCity() + " ");
sb.append(TextUtils.isEmpty(contact.getStreet1()) ? (TextUtils
.isEmpty(contact.getStreet2()) ? (TextUtils.isEmpty(contact
.getStreet3()) ? "" : contact.getStreet3()) : contact
.getStreet2()) : contact.getStreet1() + " ");
sb.append(contact.getPostalCode() + "\n");
sb.append(contact.getPhone()); return sb.toString();
} private static class ViewHolder {
TextView m_name;
TextView m_address;
TextView m_phone;
} }

注意

继承BaseExpandableListAdapter需实现对应的方法:

<1>getGroupId():获取组号

<2>getChildId():获取子元素编号(位于组中)

<3>getGroup():获取指定组的组内容

<4>getChild():依据组和子元素的位置获取子元素内容

<5>getGroupCount():获取组的数目

<6>getChildrenCount():获取相应组的子元素的数目

<7>getGroupView():获取View对象用于显示组内容

<8>getChildView():依据组和子元素的位置获取View对象用于显示子元素的内容

<9>hasStableIds():组和子元素是否持有稳定的ID,也就是底层数据的改变不会影响到它们

<10>isChildSelectable():是否可选择指定位置的子元素

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaTExNGdib3g=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="">

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvaTExNGdib3g=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center" alt="">

ExpandableListView的使用以及信息的高亮显示的更多相关文章

  1. ArcGIS API for JS4.7加载FeatureLayer,点击弹出信息并高亮显示

    我加载的是ArcGIS Server本地发布的FeatureService,ArcGIS API for JS4.7记载FeatureLayer时,在二维需要通过代码启用WebGL渲染,在三维模式下, ...

  2. tsql语句分析工具 转

    一款好用且免费的语句分析工具 在调优过程中的查询语句优化阶段,分析语句的执行计划是必经之路,一款好的执行计划分析工具确实可以帮助我们事半功倍 一款名为“Plan Explorer“,自己用的挺爽,不私 ...

  3. 一款好用且免费的语句分析工具Plan Explorer

    在调优过程中的查询语句优化阶段,分析语句的执行计划是必经之路,一款好的执行计划分析工具确实可以帮助我们事半功倍 小贴士:Plan Explorer是将Plan Explorer 专业版与免费版整合在一 ...

  4. Git 详解

    1. Git 1.1. Git是何方神圣? Git是用C语言开发的分布版本控制系统.版本控制系统可以保留一个文件集合的历史记录,并能回滚文件集合到另一个状态(历史记录状态).另一个状 态可以是不同的文 ...

  5. git详细教程

    Table of Contents 1 Git详细教程 1.1 Git简介 1.1.1 Git是何方神圣? 1.1.2 重要的术语 1.1.3 索引 1.2 Git安装 1.3 Git配置 1.3.1 ...

  6. GitHub详细教程(转载)

    1 Git详细教程 1.1 Git简介 1.1.1 Git是何方神圣? 1.1.2 重要的术语 1.1.3 索引 1.2 Git安装 1.3 Git配置 1.3.1 用户信息 1.3.2 高亮显示 1 ...

  7. GitHub具体教程

    GitHub具体教程 Table of Contents 1 Git具体教程 1.1 Git简单介绍 1.1.1 Git是何方神圣? 1.1.2 重要的术语 1.1.3 索引 1.2 Git安装 1. ...

  8. GitHub详细教程

    GitHub详细教程 Table of Contents 1 Git详细教程 1.1 Git简介 1.1.1 Git是何方神圣? 1.1.2 重要的术语 1.1.3 索引 1.2 Git安装 1.3 ...

  9. 转:git教程 ~~非常好的入门教程

    --------------------------------------- notes: cdmkdir dirnametouchls > filenamelsecho "this ...

随机推荐

  1. python之bool (布尔值)

    用途: ​ 判断真假 识记: 空的字符串是False,非空的就是True 0 是False,非0的都是True 格式: True False 布尔值转换成字符串: print(type(str(Tru ...

  2. MySQL-03 SQL语句设计

    学习要点 SQL语句分类 DML语句 DML 查询语句 SQL语句分类 数据操纵语言(DML):用来操纵数据库中数据的命令.包括:SELECT.INSERT.UPDATE.DELETE. 数据定义语言 ...

  3. js获取农历

    上一篇我们对upupoo网页壁纸改造时用到了农历,upupoo(网页壁纸)自主修改一:农历,这里记一下笔记: 获取当前农历的js 主js: //农历 var CalendarData=new Arra ...

  4. FileZilla Server安装配置教程

    1. FileZilla官网下载FileZilla Server服务器,目前最新版本为0.9.53. 2. 安装FileZilla服务器.除以下声明的地方外,其它均采用默认模式,如安装路径等. 2.1 ...

  5. 11scrapy

    一. Scrapy基础概念 Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架,我们只需要实现少量的代码,就能够快速的抓取.Scrapy 使用了 Twisted异步网络框架,可以加快我 ...

  6. (2) LVS负载均衡:VS_TUN和VS_DR的arp问题

    1. ARP协议简介 ARP(Address Resolution Protocol)协议称为地址解析协议,用于将主机IP地址解析为主机的MAC地址,即IP-->MAC之间一一映射. RARP协 ...

  7. Oracle的五种约束

    1.非空(NOT NULL)约束:所定义的列不绝对不能为空: 例如:将已经创建好的表BOOK中的bookname字段修改为不为空: 利用 ALTER TABLE.......MODIFY ...... ...

  8. java面试微信交流群-欢迎你的加入

    Java后端技术专注Java相关技术:SSM.Spring全家桶.微服务.MySQL.MyCat.集群.分布式.中间件.Linux.网络.多线程,偶尔讲点运维Jenkins.Nexus.Docker. ...

  9. Python中的函数(3)

    一.包含返回值的函数 下面来看一个函数,它接收名和姓并返回完整的姓名: def get_formatted_name(first_name,last_name): """ ...

  10. [工具]Visual Studio

    1,Tab键的使用: 如不说有这样的代码:public Member member { get; set; } 当我们编辑完Member后,按一下Tab键,就能够将光标锁定到member上,等待键盘输 ...