ExpandableListView的使用以及信息的高亮显示

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():是否可选择指定位置的子元素

时间: 2024-08-09 19:46:20

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

JavaScript巩固与加强(五)正则

JavaScript巩固与加强(五) 目录 JavaScript巩固与加强(五)... 1 一.正则表达式... 3 1.历史起源... 3 2.什么是正则表达式... 3 3.应用场景... 3 4.快速入门... 4 1)查找一个字符串中是否具有数字"8". 4 2)查找一个字符串中是否具有数字... 4 3)查找一个字符串中是否具有非数字... 4 二.正则对象... 4 1.创建正则对象... 5 2.探讨正则对象从何而来... 5 3.匹配模式... 6 4.使用正则对象..

git 使用详解

1. Git 1.1. Git是何方神圣? Git是用C语言开发的分布版本控制系统.版本控制系统可以保留一个文件集合的历史记录,并能回滚文件 集合到另一个状态(历史记录状态).另一个状态可以是不同的文件,也可以是不同的文件内容.举个例子, 你可以将文件集合转换到两天之前的状态,或者你可以在生产代码和实验性质的代码之间进行切换.文件集合往往被称作是"源代码".在一个分布版本控制系统中,每个人都有一份完整的源代码(包括源代码所有的历史记录信息), 而且可以对这个本地的数据进行操作.分布版本

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 Git配置 1.3.1 用户信息 1.3.2 高亮显示 1.3.3 忽略特定的文件 1.3.4 使用.gitkeep来追踪空的目录 1.4 開始操作Git 1.4.1 创建内容 1.4.2 创建仓库.加入文件和提交更改 1.4.3 diff命令与commit更改 1.4.4 Status, Di

git/github运用

了解git和svn很久了,但是一直没有拿来做过版本控制管理,虽然svn有用到过,但是觉得还是运用git的比较多吧,尤其github. Git术语                                                                       术语 定义 仓库 一个仓库包括了所有的版本信息.所有的分支和标记信息. Repository 在Git中仓库的每份拷贝都是完整的.仓库让你可以从中   取得你的工作副本.   一个分支意味着一个独立的.拥有自己历史

[转]Git教程【译】

http://www.cnblogs.com/zhangjing230/archive/2012/05/09/2489745.html 原文出处:http://www.vogella.com/articles/Git/article.html 翻译说明:个人出于兴趣爱好翻译这篇Git教程(google 搜索git tutorial排名第一的文章).学习git最初的原因是为了了解GitHub,译者水平有限,有不足之处欢迎指正.希望这边译文对你能有帮助.转载请链接出处. Git教程 本教程通过命令行

ThreadingTest延用方法,打破结果,展现测试新理念

现今移动测试业态 作为现今APP最多的平台,移动业务已经渗透到了每个人的生活中,这意味着对移动平台上的测试需求也在逐年递增,各大测试工具开发商根据市场需求也相继推出了各自的测试工具和平台. 移动黑盒方法,测试人员的苦恼 作为整个程序最后一道屏障,测试人员的测试方法起到了决定性的作用.在移动平台中,测试人员往往会通过在真机上的点点点的方式进行,虽然有Testin.DevStore.Android SDK Emulator等云平台或自动工具的帮助,但测试人员还是期盼着想要一套简便.直观.易上手的工具

正则表达式和grep、sed工具

what is 正则表达式 一种处理字符的方法,只要命令工具(例:grep.sed.awk等)支持这种方法,就可以用来处理正则表达式字符串.通过特殊字符的帮助,我们就容易达到查找.删除.替换特定字符串的命令程序. 用途 信息过滤,信息匹配,获取有用信息 常用的表达式含义 ^ 行首标记  #^haha,匹配以haha起始的行 $ 行尾标记 . 匹配任意单个字符 ? 匹配之前的项一次或0次  #blu?e 匹配blue或ble + 匹配之前的项一次或多次 * 匹配之前的项0次或多次 [] 匹配包含在

【转载】GitHub详细教程

1 Git详细教程 1.1 Git简介 1.1.1 Git是何方神圣? Git是用C语言开发的分布版本控制系统.版本控制系统可以保留一个文件集合的历史记录,并能回滚文件集合到另一个状态(历史记录状态).另一个状态可以是不同的文件,也可以是不同的文件内容.举个例子,你可以将文件集合转换到两天之前的状态,或者你可以在生产代码和实验性质的代码之间进行切换.文件集合往往被称作是“源代码”.在一个分布版本控制系统中,每个人都有一份完整的源代码(包括源代码所有的历史记录信息),而且可以对这个本地的数据进行操

AEAI WX微信扩展框架技术手册

1 概述 数通畅联微信公众号申请之后,由于要满足提供网站推广.功能演示.以及公司内部移动办公三方面的需求,所以把最初的订阅号更改为服务号,同时做了实名认证,这样就可以获取微信公众平台绝大部分接口,在完成数通畅联公众号相关功能过程中参考网上大量资料,期间封装AEAI WX微信扩展框架托管于开源中国社区http://git.oschina.net/agileai/aeaiwx. 在这里感谢特别柳峰对微信公众号知识的普及和推广,这是他博客链接http://blog.csdn.net/lyq8479,在