之前實現過一次這種效果的ExpandableListView:http://www.jb51.net/article/38482.htm,帶效果比較挫,最近,在參考聯系人源碼PinnedHeaderListView,以及網上各位大俠的源碼,封裝了一個效果最好,而且使用最簡單的IphoneTreeView,下面先看看效果圖:
首先讓我們看看封裝得比較完善的IphoneTreeView:
復制代碼 代碼如下:
public class IphoneTreeView extends ExpandableListView implements
OnScrollListener, OnGroupClickListener {
public IphoneTreeView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
registerListener();
}
public IphoneTreeView(Context context, AttributeSet attrs) {
super(context, attrs);
registerListener();
}
public IphoneTreeView(Context context) {
super(context);
registerListener();
}
/**
* Adapter 接口 . 列表必須實現此接口 .
*/
public interface IphoneTreeHeaderAdapter {
public static final int PINNED_HEADER_GONE = 0;
public static final int PINNED_HEADER_VISIBLE = 1;
public static final int PINNED_HEADER_PUSHED_UP = 2;
/**
* 獲取 Header 的狀態
*
* @param groupPosition
* @param childPosition
* @return
* PINNED_HEADER_GONE,PINNED_HEADER_VISIBLE,PINNED_HEADER_PUSHED_UP
* 其中之一
*/
int getTreeHeaderState(int groupPosition, int childPosition);
/**
* 配置 QQHeader, 讓 QQHeader 知道顯示的內容
*
* @param header
* @param groupPosition
* @param childPosition
* @param alpha
*/
void configureTreeHeader(View header, int groupPosition,
int childPosition, int alpha);
/**
* 設置組按下的狀態
*
* @param groupPosition
* @param status
*/
void onHeadViewClick(int groupPosition, int status);
/**
* 獲取組按下的狀態
*
* @param groupPosition
* @return
*/
int getHeadViewClickStatus(int groupPosition);
}
private static final int MAX_ALPHA = 255;
private IphoneTreeHeaderAdapter mAdapter;
/**
* 用於在列表頭顯示的 View,mHeaderViewVisible 為 true 才可見
*/
private View mHeaderView;
/**
* 列表頭是否可見
*/
private boolean mHeaderViewVisible;
private int mHeaderViewWidth;
private int mHeaderViewHeight;
public void setHeaderView(View view) {
mHeaderView = view;
AbsListView.LayoutParams lp = new AbsListView.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
view.setLayoutParams(lp);
if (mHeaderView != null) {
setFadingEdgeLength(0);
}
requestLayout();
}
private void registerListener() {
setOnScrollListener(this);
setOnGroupClickListener(this);
}
/**
* 點擊 HeaderView 觸發的事件
*/
private void headerViewClick() {
long packedPosition = getExpandableListPosition(this
.getFirstVisiblePosition());
int groupPosition = ExpandableListView
.getPackedPositionGroup(packedPosition);
if (mAdapter.getHeadViewClickStatus(groupPosition) == 1) {
this.collapseGroup(groupPosition);
mAdapter.onHeadViewClick(groupPosition, 0);
} else {
this.expandGroup(groupPosition);
mAdapter.onHeadViewClick(groupPosition, 1);
}
this.setSelectedGroup(groupPosition);
}
private float mDownX;
private float mDownY;
/**
* 如果 HeaderView 是可見的 , 此函數用於判斷是否點擊了 HeaderView, 並對做相應的處理 , 因為 HeaderView
* 是畫上去的 , 所以設置事件監聽是無效的 , 只有自行控制 .
*/
@Override
public boolean onTouchEvent(MotionEvent ev) {
if (mHeaderViewVisible) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
mDownX = ev.getX();
mDownY = ev.getY();
if (mDownX <= mHeaderViewWidth && mDownY <= mHeaderViewHeight) {
return true;
}
break;
case MotionEvent.ACTION_UP:
float x = ev.getX();
float y = ev.getY();
float offsetX = Math.abs(x - mDownX);
float offsetY = Math.abs(y - mDownY);
// 如果 HeaderView 是可見的 , 點擊在 HeaderView 內 , 那麼觸發 headerClick()
if (x <= mHeaderViewWidth && y <= mHeaderViewHeight
&& offsetX <= mHeaderViewWidth
&& offsetY <= mHeaderViewHeight) {
if (mHeaderView != null) {
headerViewClick();
}
return true;
}
break;
default:
break;
}
}
return super.onTouchEvent(ev);
}
@Override
public void setAdapter(ExpandableListAdapter adapter) {
super.setAdapter(adapter);
mAdapter = (IphoneTreeHeaderAdapter) adapter;
}
/**
*
* 點擊了 Group 觸發的事件 , 要根據根據當前點擊 Group 的狀態來
*/
@Override
public boolean onGroupClick(ExpandableListView parent, View v,
int groupPosition, long id) {
if (mAdapter.getHeadViewClickStatus(groupPosition) == 0) {
mAdapter.onHeadViewClick(groupPosition, 1);
parent.expandGroup(groupPosition);
parent.setSelectedGroup(groupPosition);
} else if (mAdapter.getHeadViewClickStatus(groupPosition) == 1) {
mAdapter.onHeadViewClick(groupPosition, 0);
parent.collapseGroup(groupPosition);
}
// 返回 true 才可以彈回第一行 , 不知道為什麼
return true;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (mHeaderView != null) {
measureChild(mHeaderView, widthMeasureSpec, heightMeasureSpec);
mHeaderViewWidth = mHeaderView.getMeasuredWidth();
mHeaderViewHeight = mHeaderView.getMeasuredHeight();
}
}
private int mOldState = -1;
@Override
protected void onLayout(boolean changed, int left, int top, int right,
int bottom) {
super.onLayout(changed, left, top, right, bottom);
final long flatPostion = getExpandableListPosition(getFirstVisiblePosition());
final int groupPos = ExpandableListView
.getPackedPositionGroup(flatPostion);
final int childPos = ExpandableListView
.getPackedPositionChild(flatPostion);
int state = mAdapter.getTreeHeaderState(groupPos, childPos);
if (mHeaderView != null && mAdapter != null && state != mOldState) {
mOldState = state;
mHeaderView.layout(0, 0, mHeaderViewWidth, mHeaderViewHeight);
}
configureHeaderView(groupPos, childPos);
}
public void configureHeaderView(int groupPosition, int childPosition) {
if (mHeaderView == null || mAdapter == null
|| ((ExpandableListAdapter) mAdapter).getGroupCount() == 0) {
return;
}
int state = mAdapter.getTreeHeaderState(groupPosition, childPosition);
switch (state) {
case IphoneTreeHeaderAdapter.PINNED_HEADER_GONE: {
mHeaderViewVisible = false;
break;
}
case IphoneTreeHeaderAdapter.PINNED_HEADER_VISIBLE: {
mAdapter.configureTreeHeader(mHeaderView, groupPosition,
childPosition, MAX_ALPHA);
if (mHeaderView.getTop() != 0) {
mHeaderView.layout(0, 0, mHeaderViewWidth, mHeaderViewHeight);
}
mHeaderViewVisible = true;
break;
}
case IphoneTreeHeaderAdapter.PINNED_HEADER_PUSHED_UP: {
View firstView = getChildAt(0);
int bottom = firstView.getBottom();
// intitemHeight = firstView.getHeight();
int headerHeight = mHeaderView.getHeight();
int y;
int alpha;
if (bottom < headerHeight) {
y = (bottom - headerHeight);
alpha = MAX_ALPHA * (headerHeight + y) / headerHeight;
} else {
y = 0;
alpha = MAX_ALPHA;
}
mAdapter.configureTreeHeader(mHeaderView, groupPosition,
childPosition, alpha);
if (mHeaderView.getTop() != y) {
mHeaderView.layout(0, y, mHeaderViewWidth, mHeaderViewHeight
+ y);
}
mHeaderViewVisible = true;
break;
}
}
}
@Override
/**
* 列表界面更新時調用該方法(如滾動時)
*/
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
if (mHeaderViewVisible) {
// 分組欄是直接繪制到界面中,而不是加入到ViewGroup中
drawChild(canvas, mHeaderView, getDrawingTime());
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
final long flatPos = getExpandableListPosition(firstVisibleItem);
int groupPosition = ExpandableListView.getPackedPositionGroup(flatPos);
int childPosition = ExpandableListView.getPackedPositionChild(flatPos);
configureHeaderView(groupPosition, childPosition);
}
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
}
使用起來也是比較簡單的,先在布局文件中聲明activity_main.xml:
復制代碼 代碼如下:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity" >
<com.way.iphonetreeview.IphoneTreeView
android:id="@+id/iphone_tree_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/transparent"
android:cacheColorHint="@android:color/transparent"
android:divider="@null"
android:transcriptMode="normal" />
</RelativeLayout>
然後在MainActivity中調用,為了縮減代碼,我把Adapter作為內部類放在MainActivity中了:
復制代碼 代碼如下:
public class MainActivity extends Activity {
private LayoutInflater mInflater;
private IphoneTreeView iphoneTreeView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
}
private void initView() {
// TODO Auto-generated method stub
mInflater = LayoutInflater.from(this);
iphoneTreeView = (IphoneTreeView) findViewById(R.id.iphone_tree_view);
iphoneTreeView.setHeaderView(getLayoutInflater().inflate(
R.layout.list_head_view, iphoneTreeView, false));
iphoneTreeView.setGroupIndicator(null);
iphoneTreeView.setAdapter(new IphoneTreeViewAdapter());
}
public class IphoneTreeViewAdapter extends BaseExpandableListAdapter
implements IphoneTreeHeaderAdapter {
// Sample data set. children[i] contains the children (String[]) for
// groups[i].
private HashMap<Integer, Integer> groupStatusMap;
private String[] groups = { "第一組", "第二組", "第三組", "第四組" };
private String[][] children = {
{ "Way", "Arnold", "Barry", "Chuck", "David", "Afghanistan",
"Albania", "Belgium", "Lily", "Jim", "LiMing", "Jodan" },
{ "Ace", "Bandit", "Cha-Cha", "Deuce", "Bahamas", "China",
"Dominica", "Jim", "LiMing", "Jodan" },
{ "Fluffy", "Snuggles", "Ecuador", "Ecuador", "Jim", "LiMing",
"Jodan" },
{ "Goldy", "Bubbles", "Iceland", "Iran", "Italy", "Jim",
"LiMing", "Jodan" } };
public IphoneTreeViewAdapter() {
// TODO Auto-generated constructor stub
groupStatusMap = new HashMap<Integer, Integer>();
}
public Object getChild(int groupPosition, int childPosition) {
return children[groupPosition][childPosition];
}
public long getChildId(int groupPosition, int childPosition) {
return childPosition;
}
public int getChildrenCount(int groupPosition) {
return children[groupPosition].length;
}
public Object getGroup(int groupPosition) {
return groups[groupPosition];
}
public int getGroupCount() {
return groups.length;
}
public long getGroupId(int groupPosition) {
return groupPosition;
}
public boolean isChildSelectable(int groupPosition, int childPosition) {
return true;
}
public boolean hasStableIds() {
return true;
}
@Override
public View getChildView(int groupPosition, int childPosition,
boolean isLastChild, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
if (convertView == null) {
convertView = mInflater.inflate(R.layout.list_item_view, null);
}
TextView tv = (TextView) convertView
.findViewById(R.id.contact_list_item_name);
tv.setText(getChild(groupPosition, childPosition).toString());
TextView state = (TextView) convertView
.findViewById(R.id.cpntact_list_item_state);
state.setText("愛生活...愛Android...");
return convertView;
}
@Override
public View getGroupView(int groupPosition, boolean isExpanded,
View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
if (convertView == null) {
convertView = mInflater.inflate(R.layout.list_group_view, null);
}
TextView groupName = (TextView) convertView
.findViewById(R.id.group_name);
groupName.setText(groups[groupPosition]);
ImageView indicator = (ImageView) convertView
.findViewById(R.id.group_indicator);
TextView onlineNum = (TextView) convertView
.findViewById(R.id.online_count);
onlineNum.setText(getChildrenCount(groupPosition) + "/"
+ getChildrenCount(groupPosition));
if (isExpanded) {
indicator.setImageResource(R.drawable.indicator_expanded);
} else {
indicator.setImageResource(R.drawable.indicator_unexpanded);
}
return convertView;
}
@Override
public int getTreeHeaderState(int groupPosition, int childPosition) {
final int childCount = getChildrenCount(groupPosition);
if (childPosition == childCount - 1) {
return PINNED_HEADER_PUSHED_UP;
} else if (childPosition == -1
&& !iphoneTreeView.isGroupExpanded(groupPosition)) {
return PINNED_HEADER_GONE;
} else {
return PINNED_HEADER_VISIBLE;
}
}
@Override
public void configureTreeHeader(View header, int groupPosition,
int childPosition, int alpha) {
// TODO Auto-generated method stub
((TextView) header.findViewById(R.id.group_name))
.setText(groups[groupPosition]);
((TextView) header.findViewById(R.id.online_count))
.setText(getChildrenCount(groupPosition) + "/"
+ getChildrenCount(groupPosition));
}
@Override
public void onHeadViewClick(int groupPosition, int status) {
// TODO Auto-generated method stub
groupStatusMap.put(groupPosition, status);
}
@Override
public int getHeadViewClickStatus(int groupPosition) {
if (groupStatusMap.containsKey(groupPosition)) {
return groupStatusMap.get(groupPosition);
} else {
return 0;
}
}
}
}
好了,簡單的一個例子就完成了,
總結一下: 原理: 在正在顯示的最上面的組的標簽位置添加一個和組視圖完全一樣的視圖,作為組標簽。這個標簽的位置要隨著列表的滑動不斷變化,以保持總是顯示在最上方,並且該消失的時候就消失。給這個標簽添加點擊事件,實現打開和關閉分組的功能。
組標簽總是顯示在上方,這是通過不斷的調整其在布局中的位置來實現的。這個調整的過程,在初始化的時候,在 onLayout 方法中實現一次,後面都是在滾動過程中,根據對滾動狀態的監聽來實現的。
實例化要添加的標簽的時候(在外面實現,即使調用 setTreeHeaderView之前),parent 要設為該ExpandableListView.
要學習以及好好理解這個,最好的方法是將添加進來的組標簽設為半透明,便於觀察整個過程。
源碼下載