Android group listview

這幾天做Android開發,發現android中缺少一個Group ListView的實現

如果僅僅通過Adapter來實現的話,分組和組內位置就需要自己實現,所以就參考android中的ExpandableListView做了一個GroupListView,用來快速放置分組列表.

實現了組標籤的置頂效果,同時修正了在android2.1中出現的組標籤置頂出現的問題


廢話不多說,貼出核心的源代碼以供參考

EarlGroupListView.java

package com.list;

import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ListAdapter;
import android.widget.ListView;

import com.list.EarlGroupConnector.GroupMetadata;
import com.list.EarlGroupConnector.PositionMetadata;

public class EarlGroupListView extends ListView {
	/** 浮動的組View */
	private View mfloatingView;
	/** 是否顯示浮動view */
	private boolean mIsHeaderShow;
	/** 浮動view被裁剪的高度 */
	private int mClipHeaderHeight;

	private EarlGroupConnector mConnector;

	public EarlGroupListView(Context context) {
		super(context);
		init();
	}

	public EarlGroupListView(Context context, AttributeSet attrs) {
		super(context, attrs);
		init();

	}

	public EarlGroupListView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		init();
	}

	private void init() {
		setFadingEdgeLength(0);
	}

	@Override
	protected void dispatchDraw(Canvas canvas) {
		super.dispatchDraw(canvas);
		// 可視區域中的view的個數
		int childCount = getChildCount();
		// 獲取第一個可視的view的位置,絕對位置
		int first = getFirstVisiblePosition();
		// paddingTop
		int paddingTop = getPaddingTop();

		View firstVisibleView = null;
		// 由於在android2.1中出現不準確的情況,我們修正它
		// 當然在android2.2及以後的版本中也可以使用,循環將在一次循環後結束
		int offset = 0;
		for (int i = 0; i < childCount; i++) {
			View v = getChildAt(i);
			// 找到第一個顯示的view了
			if (v.getBottom() > paddingTop) {
				offset = i;
				firstVisibleView = v;
				break;
			}
			// first繼續向後查找
			first++;
		}
		// Log.i("first" + (getCount() - getFooterViewsCount()), "" + first);
		// 這裏的first位置是list中的絕對位置
		// 所以需要線判斷這個位置是否是header或者footer
		final int headerViewsCount = getHeaderViewsCount();
		final int footerViewsStart = getCount() - getFooterViewsCount();
		if (first < headerViewsCount || first >= footerViewsStart) {
			mIsHeaderShow = false;
			return;
		}
		// 然後將絕對位置轉換爲expandableListView可以使用的flat位置
		first -= headerViewsCount;

		PositionMetadata pm = mConnector.getUnflattenedPos(first);
		int type = pm.type;
		int groupPos = pm.groupPos;
		pm.recycle();

		// 第一個看到的是分組,在最上面並且它頂在最上方
		if (type == PositionMetadata.TYPE_GROUP
				&& firstVisibleView.getTop() == paddingTop) {
			mIsHeaderShow = false;
			return;
		}
		// 獲取更新的浮動view
		View floatingView = mConnector.getAdapter().getGroupView(groupPos,
				mfloatingView, this);
		// 當保存的view和新的view不同時,重新佈局及調整大小
		if (floatingView != mfloatingView) {
			mfloatingView = floatingView;
			android.widget.AbsListView.LayoutParams layoutparams = (android.widget.AbsListView.LayoutParams) floatingView
					.getLayoutParams();
			// 佈局參數
			if (layoutparams == null) {
				layoutparams = new android.widget.AbsListView.LayoutParams(
						LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT, 0);
				floatingView.setLayoutParams(layoutparams);
			}
			// 大小和佈局,大小同第一個分組
			int widthMeasureSpec = getChildMeasureSpec(0, 0,
					firstVisibleView.getWidth());
			int heightMeasureSpec = getChildMeasureSpec(0, 0,
					firstVisibleView.getHeight());
			floatingView.measure(widthMeasureSpec, heightMeasureSpec);
			floatingView.layout(getPaddingLeft(), paddingTop,
					firstVisibleView.getRight(), firstVisibleView.getHeight()
							+ paddingTop);
			floatingView.setClickable(true);
		}
		int nextGroupPos = -1;
		// 第一個可視分組不是最後一個分組
		if (groupPos != mConnector.getAdapter().getGroupCount() - 1) {
			nextGroupPos = groupPos + 1;
			GroupMetadata nextPm = mConnector.getGroupMetadatas().get(
					nextGroupPos);
			int nextPos = nextPm.mGroupFlatPos;
			View nextGroupView = getChildAt(nextPos - first + offset);
			// 下一個分組組可以被索引到,它不在屏幕外
			if (nextGroupView != null) {
				int top = nextGroupView.getTop() - getDividerHeight();
				mClipHeaderHeight = mfloatingView.getBottom() > top ? mfloatingView
						.getBottom() - top
						: 0;
			} else {
				mClipHeaderHeight = 0;
			}
		} else {
			mClipHeaderHeight = 0;
		}
		mIsHeaderShow = true;

		canvas.save();
		// 裁剪掉超出padding的部分
		if (mClipHeaderHeight > 0)
			canvas.clipRect(0, paddingTop, getWidth(),
					mfloatingView.getBottom());
		canvas.translate(0, -mClipHeaderHeight);
		drawChild(canvas, mfloatingView, getDrawingTime());
		canvas.restore();
	}

	@Override
	public boolean dispatchTouchEvent(MotionEvent ev) {
		float y = ev.getY();
		if (mIsHeaderShow
				&& y < (mfloatingView.getHeight() - mClipHeaderHeight)) {
			switch (ev.getAction()) {
			case MotionEvent.ACTION_UP:
				if (mfloatingView.isPressed()) {
					mfloatingView.setPressed(false);
					// TODO add click header event
				}
				break;
			}
			return true;
		} else {
			if (mfloatingView != null && mfloatingView.isPressed()) {
				mfloatingView.setPressed(false);
				invalidate();
			}
			return super.dispatchTouchEvent(ev);
		}
	}

	@Override
	public void setAdapter(ListAdapter adapter) {
		throw new IllegalArgumentException(
				"you should use setAdapter(EarlGroupListAdapter adapter),not this");
	}

	public void setAdapter(EarlGroupListAdapter adapter) {
		mConnector = new EarlGroupConnector(adapter);
		super.setAdapter(mConnector);
	}

}


EarlGroupConnector.java,主要用於處理分組問題

package com.list;

import java.util.ArrayList;

import android.database.DataSetObserver;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;

public class EarlGroupConnector extends BaseAdapter {
	private ArrayList<GroupMetadata> mGroupMetadatas;
	private EarlGroupListAdapter mEarlGroupListAdapter;
	private final MyDataSetObserver myDataSetObserver = new MyDataSetObserver();
	private int mTotalCount;

	EarlGroupConnector(EarlGroupListAdapter adapter) {
		mGroupMetadatas = new ArrayList<EarlGroupConnector.GroupMetadata>();
		setEarlGroupListAdapter(adapter);
	}

	public int getCount() {
		return mTotalCount;
	}

	public Object getItem(int flatListPos) {
		final PositionMetadata posMetadata = getUnflattenedPos(flatListPos);

		Object retValue;
		if (posMetadata.type == PositionMetadata.TYPE_GROUP) {
			retValue = mEarlGroupListAdapter.getGroup(posMetadata.groupPos);
		} else if (posMetadata.type == PositionMetadata.TYPE_CHILD) {
			retValue = mEarlGroupListAdapter.getChild(posMetadata.groupPos,
					posMetadata.childPos);
		} else {
			// TODO: clean exit
			throw new RuntimeException("Flat list position is of unknown type");
		}

		posMetadata.recycle();

		return retValue;
	}

	public long getItemId(int flatListPos) {
		final PositionMetadata posMetadata = getUnflattenedPos(flatListPos);
		final long groupId = mEarlGroupListAdapter
				.getGroupId(posMetadata.groupPos);

		long retValue;
		if (posMetadata.type == PositionMetadata.TYPE_GROUP) {
			retValue = mEarlGroupListAdapter.getCombinedGroupId(groupId);
		} else if (posMetadata.type == PositionMetadata.TYPE_CHILD) {
			final long childId = mEarlGroupListAdapter.getChildId(
					posMetadata.groupPos, posMetadata.childPos);
			retValue = mEarlGroupListAdapter.getCombinedChildId(groupId,
					childId);
		} else {
			// TODO: clean exit
			throw new RuntimeException("Flat list position is of unknown type");
		}

		posMetadata.recycle();

		return retValue;
	}

	public View getView(int flatListPos, View convertView, ViewGroup parent) {
		final PositionMetadata posMetadata = getUnflattenedPos(flatListPos);

		View retValue;
		if (posMetadata.type == PositionMetadata.TYPE_GROUP) {
			retValue = mEarlGroupListAdapter.getGroupView(posMetadata.groupPos,
					convertView, parent);
		} else if (posMetadata.type == PositionMetadata.TYPE_CHILD) {

			retValue = mEarlGroupListAdapter.getChildView(posMetadata.groupPos,
					posMetadata.childPos, convertView, parent);
		} else {
			// TODO: clean exit
			throw new RuntimeException("Flat list position is of unknown type");
		}

		posMetadata.recycle();

		return retValue;
	}

	@Override
	public int getViewTypeCount() {
		return 2;
	}

	@Override
	public int getItemViewType(int flatListPos) {
		final PositionMetadata pos = getUnflattenedPos(flatListPos);

		int retValue;
		if (pos.type == PositionMetadata.TYPE_GROUP) {
			retValue = 0;
		} else {
			retValue = 1;
		}
		pos.recycle();

		return retValue;
	}

	@Override
	public boolean isEmpty() {
		return mEarlGroupListAdapter == null || mEarlGroupListAdapter.isEmpty();
	}

	@Override
	public boolean areAllItemsEnabled() {
		return mEarlGroupListAdapter.areAllItemsEnabled();
	}

	@Override
	public boolean isEnabled(int flatListPos) {
		final PositionMetadata pos = getUnflattenedPos(flatListPos);

		boolean retValue = pos.type == PositionMetadata.TYPE_CHILD;
		pos.recycle();

		return retValue;
	};

	PositionMetadata getUnflattenedPos(int flatListPos) {
		int type = 0;
		int groupPos = -1;
		int childPos = -1;
		GroupMetadata groupMetadata = null;

		int start = 0;
		int end = mGroupMetadatas.size() - 1;
		int mid = 0;

		while (start <= end) {
			mid = (start + end) / 2;
			GroupMetadata midMetadata = mGroupMetadatas.get(mid);
			/**
			 * the current item is group item, we stop search
			 */
			if (flatListPos == midMetadata.mGroupFlatPos) {
				type = PositionMetadata.TYPE_GROUP;
				groupPos = mid;
				groupMetadata = midMetadata;
				break;
			}
			/**
			 * the current flatPos item is in the back of middle group
			 */
			else if (flatListPos > midMetadata.mGroupFlatPos
					+ midMetadata.mChildCount) {
				start = mid + 1;
			}
			/**
			 * the current flatPos item is in the fromt of middle group
			 */
			else if (flatListPos < midMetadata.mGroupFlatPos) {
				end = mid - 1;
			}
			/**
			 * the current item is a child
			 */
			else if (flatListPos <= midMetadata.mGroupFlatPos
					+ midMetadata.mChildCount
			/* &&flatListPos<midMetadata.mGroupFlatPos */) {
				type = PositionMetadata.TYPE_CHILD;
				groupPos = mid;
				childPos = flatListPos - midMetadata.mGroupFlatPos - 1;
				groupMetadata = midMetadata;
				break;
			}
		}
		return PositionMetadata.obtain(flatListPos, type, groupPos, childPos,
				groupMetadata);
	}

	public void setEarlGroupListAdapter(EarlGroupListAdapter adapter) {
		if (mEarlGroupListAdapter != null) {
			mEarlGroupListAdapter.unregisterDataSetObserver(myDataSetObserver);
		}
		mEarlGroupListAdapter = adapter;
		adapter.registerDataSetObserver(myDataSetObserver);
		buildGroupData();
	}

	public EarlGroupListAdapter getAdapter() {
		return mEarlGroupListAdapter;
	}

	void buildGroupData() {
		// TODO reuse the arrayList data
		if (mEarlGroupListAdapter != null) {
			int count = mEarlGroupListAdapter.getGroupCount();
			int flatPos = 0;
			for (int i = 0; i < count; i++) {
				GroupMetadata metadata = new GroupMetadata();
				metadata.mGroupPos = i;
				metadata.mChildCount = mEarlGroupListAdapter
						.getChildrenCount(i);
				metadata.mGroupFlatPos = flatPos;
				flatPos += (metadata.mChildCount + 1);
				mGroupMetadatas.add(metadata);
			}
			mTotalCount = flatPos;
		}
	}

	public ArrayList<GroupMetadata> getGroupMetadatas() {
		return mGroupMetadatas;
	}

	public void setGroupMetadata(ArrayList<GroupMetadata> groupMetadatas) {
		if (groupMetadatas == null || mEarlGroupListAdapter == null)
			return;
		mGroupMetadatas = groupMetadatas;
	}

	/**
	 * the metadata of EarlGroupListView to hold the group information
	 * 
	 * @author ashyearl
	 * 
	 */
	static class GroupMetadata implements Parcelable {
		/** this group's group position */
		int mGroupPos;
		/** This group's flat list position */
		int mGroupFlatPos;
		/** this group's child count */
		int mChildCount;

		public static GroupMetadata obtain(int groupPos, int groupFlatPos,
				int childCount) {
			GroupMetadata metadata = new GroupMetadata();
			metadata.mGroupPos = groupPos;
			metadata.mGroupFlatPos = groupFlatPos;
			metadata.mChildCount = childCount;
			return metadata;
		}

		public int describeContents() {
			return 0;
		}

		/**
		 * save single GroupMetadata to Parcel
		 */
		public void writeToParcel(Parcel dest, int flags) {
			Log.i("writeToParcel", "" + dest);
			dest.writeInt(mGroupPos);
			dest.writeInt(mGroupFlatPos);
			dest.writeInt(mChildCount);
		}

		public static final Parcelable.Creator<GroupMetadata> CREATOR = new Parcelable.Creator<GroupMetadata>() {

			public GroupMetadata createFromParcel(Parcel in) {
				GroupMetadata gm = GroupMetadata.obtain(in.readInt(),
						in.readInt(), in.readInt());
				return gm;
			}

			public GroupMetadata[] newArray(int size) {
				return new GroupMetadata[size];
			}
		};
	}

	protected class MyDataSetObserver extends DataSetObserver {
		@Override
		public void onChanged() {
			buildGroupData();

			notifyDataSetChanged();
		}

		@Override
		public void onInvalidated() {
			buildGroupData();

			notifyDataSetInvalidated();
		}
	}

	/**
	 * Data type that contains an group list position (can refer to either a
	 * group or child)
	 */
	static public class PositionMetadata {

		private static final int MAX_POOL_SIZE = 5;
		public static final int TYPE_CHILD = 1;
		public static final int TYPE_GROUP = 2;
		private static ArrayList<PositionMetadata> sPool = new ArrayList<PositionMetadata>(
				MAX_POOL_SIZE);

		/** the flat position */
		public int flatPosition;

		/** the type of current flat position */
		public int type;

		public int groupPos;

		public int childPos;

		/**
		 * Link back to the group GroupMetadata for this group. Useful for
		 * removing the group from the list of expanded groups inside the
		 * connector when we collapse the group, and also as a check to see if
		 * the group was expanded or collapsed (this will be null if the group
		 * is collapsed since we don't keep that group's metadata)
		 */
		public GroupMetadata groupMetadata;

		private void resetState() {
			flatPosition = 0;
			groupMetadata = null;
			type = TYPE_CHILD;
		}

		/**
		 * Use {@link #obtain(int, int, int, int, GroupMetadata, int)}
		 */
		private PositionMetadata() {
		}

		static PositionMetadata obtain(int flatListPos, int type, int groupPos,
				int childPos, GroupMetadata groupMetadata) {
			PositionMetadata pm = getRecycledOrCreate();
			pm.flatPosition = flatListPos;
			pm.type = type;
			pm.groupPos = groupPos;
			pm.childPos = childPos;
			pm.groupMetadata = groupMetadata;
			return pm;
		}

		private static PositionMetadata getRecycledOrCreate() {
			PositionMetadata pm;
			synchronized (sPool) {
				if (sPool.size() > 0) {
					pm = sPool.remove(0);
				} else {
					return new PositionMetadata();
				}
			}
			pm.resetState();
			return pm;
		}

		public void recycle() {
			synchronized (sPool) {
				if (sPool.size() < MAX_POOL_SIZE) {
					sPool.add(this);
				}
			}
		}
	}
}
EarlGroupListAdapter.java,適配器

package com.list;

import android.database.DataSetObservable;
import android.database.DataSetObserver;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Adapter;
import android.widget.ListAdapter;

public abstract class EarlGroupListAdapter {
    private final DataSetObservable mDataSetObservable = new DataSetObservable();
    
    public void registerDataSetObserver(DataSetObserver observer) {
        mDataSetObservable.registerObserver(observer);
    }

    public void unregisterDataSetObserver(DataSetObserver observer) {
        mDataSetObservable.unregisterObserver(observer);
    }

    /**
     * @see DataSetObservable#notifyInvalidated()
     */
    public void notifyDataSetInvalidated() {
        mDataSetObservable.notifyInvalidated();
    }
    
    /**
     * @see DataSetObservable#notifyChanged()
     */
    public void notifyDataSetChanged() {
        mDataSetObservable.notifyChanged();
    }
	/**
	 * Gets the number of groups.
	 * 
	 * @return the number of groups
	 */
	abstract int getGroupCount();

	/**
	 * Gets the number of children in a specified group.
	 * 
	 * @param groupPosition
	 *            the position of the group for which the children count should
	 *            be returned
	 * @return the children count in the specified group
	 */
	abstract int getChildrenCount(int groupPosition);

	/**
	 * Gets the data associated with the given group.
	 * 
	 * @param groupPosition
	 *            the position of the group
	 * @return the data child for the specified group
	 */
	abstract Object getGroup(int groupPosition);

	/**
	 * Gets the data associated with the given child within the given group.
	 * 
	 * @param groupPosition
	 *            the position of the group that the child resides in
	 * @param childPosition
	 *            the position of the child with respect to other children in
	 *            the group
	 * @return the data of the child
	 */
	abstract Object getChild(int groupPosition, int childPosition);

	/**
	 * Gets the ID for the group at the given position. This group ID must be
	 * unique across groups. The combined ID (see
	 * {@link #getCombinedGroupId(long)}) must be unique across ALL items
	 * (groups and all children).
	 * 
	 * @param groupPosition
	 *            the position of the group for which the ID is wanted
	 * @return the ID associated with the group
	 */
	abstract long getGroupId(int groupPosition);

	/**
	 * Gets the ID for the given child within the given group. This ID must be
	 * unique across all children within the group. The combined ID (see
	 * {@link #getCombinedChildId(long, long)}) must be unique across ALL items
	 * (groups and all children).
	 * 
	 * @param groupPosition
	 *            the position of the group that contains the child
	 * @param childPosition
	 *            the position of the child within the group for which the ID is
	 *            wanted
	 * @return the ID associated with the child
	 */
	abstract long getChildId(int groupPosition, int childPosition);

	/**
	 * Indicates whether the child and group IDs are stable across changes to
	 * the underlying data.
	 * 
	 * @return whether or not the same ID always refers to the same object
	 * @see Adapter#hasStableIds()
	 */
	abstract boolean hasStableIds();

	/**
	 * Gets a View that displays the given group. This View is only for the
	 * group--the Views for the group's children will be fetched using
	 * {@link #getChildView(int, int, boolean, View, ViewGroup)}.
	 * 
	 * @param groupPosition
	 *            the position of the group for which the View is returned
	 * @param convertView
	 *            the old view to reuse, if possible. You should check that this
	 *            view is non-null and of an appropriate type before using. If
	 *            it is not possible to convert this view to display the correct
	 *            data, this method can create a new view. It is not guaranteed
	 *            that the convertView will have been previously created by
	 *            {@link #getGroupView(int, boolean, View, ViewGroup)}.
	 * @param parent
	 *            the parent that this view will eventually be attached to
	 * @return the View corresponding to the group at the specified position
	 */
	abstract View getGroupView(int groupPosition, View convertView, ViewGroup parent);

	/**
	 * Gets a View that displays the data for the given child within the given
	 * group.
	 * 
	 * @param groupPosition
	 *            the position of the group that contains the child
	 * @param childPosition
	 *            the position of the child (for which the View is returned)
	 *            within the group
	 * @param convertView
	 *            the old view to reuse, if possible. You should check that this
	 *            view is non-null and of an appropriate type before using. If
	 *            it is not possible to convert this view to display the correct
	 *            data, this method can create a new view. It is not guaranteed
	 *            that the convertView will have been previously created by
	 *            {@link #getChildView(int, int, boolean, View, ViewGroup)}.
	 * @param parent
	 *            the parent that this view will eventually be attached to
	 * @return the View corresponding to the child at the specified position
	 */
	abstract View getChildView(int groupPosition, int childPosition, View convertView,
			ViewGroup parent);

	/**
	 * Whether the child at the specified position is selectable.
	 * 
	 * @param groupPosition
	 *            the position of the group that contains the child
	 * @param childPosition
	 *            the position of the child within the group
	 * @return whether the child is selectable.
	 */
	abstract boolean isChildSelectable(int groupPosition, int childPosition);

	/**
	 * @see ListAdapter#areAllItemsEnabled()
	 */
	boolean areAllItemsEnabled(){
		return false;
	}

	/**
	 * @see ListAdapter#isEmpty()
	 */
	abstract boolean isEmpty();

    /**
     * Override this method if you foresee a clash in IDs based on this scheme:
     * <p>
     * Base implementation returns a long:
     * <li> bit 0: Whether this ID points to a child (unset) or group (set), so for this method
     *             this bit will be 1.
     * <li> bit 1-31: Lower 31 bits of the groupId
     * <li> bit 32-63: Lower 32 bits of the childId.
     * <p> 
     * {@inheritDoc}
     */
    public long getCombinedChildId(long groupId, long childId) {
        return 0x8000000000000000L | ((groupId & 0x7FFFFFFF) << 32) | (childId & 0xFFFFFFFF);
    }

    /**
     * Override this method if you foresee a clash in IDs based on this scheme:
     * <p>
     * Base implementation returns a long:
     * <li> bit 0: Whether this ID points to a child (unset) or group (set), so for this method
     *             this bit will be 0.
     * <li> bit 1-31: Lower 31 bits of the groupId
     * <li> bit 32-63: Lower 32 bits of the childId.
     * <p> 
     * {@inheritDoc}
     */
    public long getCombinedGroupId(long groupId) {
        return (groupId & 0x7FFFFFFF) << 32;
    }
}

EarlSimpleGroupListAdapter.java,一個簡單的適配器,類似SimpleListAdapter

package com.list;

import java.util.List;
import java.util.Map;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ExpandableListView;
import android.widget.TextView;

/**
 * An easy adapter to map static data to group and child views defined in an XML
 * file. You can separately specify the data backing the group as a List of
 * Maps. Each entry in the ArrayList corresponds to one group in the expandable
 * list. The Maps contain the data for each row. You also specify an XML file
 * that defines the views used to display a group, and a mapping from keys in
 * the Map to specific views. This process is similar for a child, except it is
 * one-level deeper so the data backing is specified as a List<List<Map>>, where
 * the first List corresponds to the group of the child, the second List
 * corresponds to the position of the child within the group, and finally the
 * Map holds the data for that particular child.
 */
public class EarlSimpleGroupListAdapter extends EarlGroupListAdapter {
	private List<? extends Map<String, ?>> mGroupData;
	private int mGroupLayout;
	private String[] mGroupFrom;
	private int[] mGroupTo;

	private List<? extends List<? extends Map<String, ?>>> mChildData;
	private int mChildLayout;
	private String[] mChildFrom;
	private int[] mChildTo;

	private LayoutInflater mInflater;

	/**
	 * Constructor
	 * 
	 * @param context
	 *            The context where the {@link ExpandableListView} associated
	 *            with this {@link EarlSimpleGroupListAdapter} is running
	 * @param groupData
	 *            A List of Maps. Each entry in the List corresponds to one
	 *            group in the list. The Maps contain the data for each group,
	 *            and should include all the entries specified in "groupFrom"
	 * @param groupFrom
	 *            A list of keys that will be fetched from the Map associated
	 *            with each group.
	 * @param groupTo
	 *            The group views that should display column in the "groupFrom"
	 *            parameter. These should all be TextViews. The first N views in
	 *            this list are given the values of the first N columns in the
	 *            groupFrom parameter.
	 * @param groupLayout
	 *            resource identifier of a view layout that defines the views
	 *            for a group. The layout file should include at least those
	 *            named views defined in "groupTo"
	 * @param childData
	 *            A List of List of Maps. Each entry in the outer List
	 *            corresponds to a group (index by group position), each entry
	 *            in the inner List corresponds to a child within the group
	 *            (index by child position), and the Map corresponds to the data
	 *            for a child (index by values in the childFrom array). The Map
	 *            contains the data for each child, and should include all the
	 *            entries specified in "childFrom"
	 * @param childFrom
	 *            A list of keys that will be fetched from the Map associated
	 *            with each child.
	 * @param childTo
	 *            The child views that should display column in the "childFrom"
	 *            parameter. These should all be TextViews. The first N views in
	 *            this list are given the values of the first N columns in the
	 *            childFrom parameter.
	 * @param childLayout
	 *            resource identifier of a view layout that defines the views
	 *            for a child. The layout file should include at least those
	 *            named views defined in "childTo"
	 */
	public EarlSimpleGroupListAdapter(Context context,
			List<? extends Map<String, ?>> groupData, int groupLayout,
			String[] groupFrom, int[] groupTo,
			List<? extends List<? extends Map<String, ?>>> childData,
			int childLayout, String[] childFrom, int[] childTo) {
		mGroupData = groupData;
		mGroupLayout = groupLayout;
		mGroupFrom = groupFrom;
		mGroupTo = groupTo;

		mChildData = childData;
		mChildLayout = childLayout;
		mChildFrom = childFrom;
		mChildTo = childTo;

		mInflater = (LayoutInflater) context
				.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
	}

	public Object getChild(int groupPosition, int childPosition) {
		return mChildData.get(groupPosition).get(childPosition);
	}

	public long getChildId(int groupPosition, int childPosition) {
		return childPosition;
	}

	public View getChildView(int groupPosition, int childPosition,
			View convertView, ViewGroup parent) {
		View v;
		if (convertView == null) {
			v = mInflater.inflate(mChildLayout, parent, false);
		} else {
			v = convertView;
		}
		bindView(v, mChildData.get(groupPosition).get(childPosition),
				mChildFrom, mChildTo);
		return v;
	}

	private void bindView(View view, Map<String, ?> data, String[] from,
			int[] to) {
		int len = to.length;

		for (int i = 0; i < len; i++) {
			TextView v = (TextView) view.findViewById(to[i]);
			if (v != null) {
				v.setText((String) data.get(from[i]));
			}
		}
	}

	public int getChildrenCount(int groupPosition) {
		return mChildData.get(groupPosition).size();
	}

	public Object getGroup(int groupPosition) {
		return mGroupData.get(groupPosition);
	}

	public int getGroupCount() {
		return mGroupData.size();
	}

	public long getGroupId(int groupPosition) {
		return groupPosition;
	}

	public View getGroupView(int groupPosition, View convertView,
			ViewGroup parent) {
		View v;
		if (convertView == null) {
			v = mInflater.inflate(mGroupLayout, parent, false);
		} else {
			v = convertView;
		}
		bindView(v, mGroupData.get(groupPosition), mGroupFrom, mGroupTo);
		return v;
	}

	public boolean isChildSelectable(int groupPosition, int childPosition) {
		return true;
	}

	public boolean hasStableIds() {
		return true;
	}

	@Override
	boolean isEmpty() {
		return getGroupCount() == 0;
	}

}


測試用的Activity,其他的佈局文件就沒啥了

package com.list;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.TextView;

public class ListActivity extends Activity {
	String A = "A";
	String B = "B";

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
		EarlGroupListView expandableListView = (EarlGroupListView) findViewById(R.id.expandableListView1);

		List<Map<String, String>> groupData = new ArrayList<Map<String, String>>();
		List<List<Map<String, String>>> childData = new ArrayList<List<Map<String, String>>>();
		for (int i = 0; i < 10; i++) {
			Map<String, String> curGroupMap = new HashMap<String, String>();
			groupData.add(curGroupMap);
			curGroupMap.put(A, "Group " + i);
			curGroupMap.put(B, (i % 2 == 0) ? "This group is even"
					: "This group is odd");

			List<Map<String, String>> children = new ArrayList<Map<String, String>>();
			for (int j = 0; j < 3; j++) {
				Map<String, String> curChildMap = new HashMap<String, String>();
				children.add(curChildMap);
				curChildMap.put(A, "Child " + j);
				curChildMap.put(B, (j % 2 == 0) ? "This child is even"
						: "This child is odd");
			}
			childData.add(children);
		}
		for (int i = 0; i < 7; i++) {
			TextView textView = new TextView(this);
			textView.setText("我是一個測試" + i);
			expandableListView.addHeaderView(textView);
		}
		for (int i = 0; i < 26; i++) {
			TextView textView = new TextView(this);
			textView.setText("我是footer測試" + i);
			expandableListView.addFooterView(textView);
		}
		expandableListView.addHeaderView(getLayoutInflater().inflate(
				R.layout.header, null));
		expandableListView.setAdapter(new EarlSimpleGroupListAdapter(this,
				groupData, R.layout.group, new String[] { A, B }, new int[] {
						R.id.textView1, R.id.textView2 }, childData,
				R.layout.item, new String[] { A, B }, new int[] {
						R.id.textView1, R.id.textView2 }));
		expandableListView.setOnItemClickListener(new OnItemClickListener() {

			public void onItemClick(AdapterView<?> parent, View view,
					int position, long id) {
				Log.i("onItemClick", "" + position);
			}
		});

	}
}

貼上在佈局文件中的使用

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:padding="10dp"
    android:orientation="vertical" >

    <Button
        android:id="@+id/button2"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="Button" />

    <com.list.EarlGroupListView
        android:id="@+id/expandableListView1"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:background="@drawable/list_single_normal"
        android:cacheColorHint="#00000000" >
    </com.list.EarlGroupListView>

    <Button
        android:id="@+id/button1"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="Button" />

</LinearLayout>


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章