這幾天做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>