欢迎大家访问我的博客http://blog.csdn.net/mikejaps,专注于android ios app 开发
首先它是一个ExpandableListView,但是它的头部可以固定,其次,在它的上面还有一个头部可以来回伸缩
项目地址: https://github.com/singwhatiwanna/PinnedHeaderExpandableListView
APP示例:百度手机卫士垃圾清理界面 效果图如下:
主要代码:Group和People2个类就不贴出来了,都是2个java bean
/**
The MIT License (MIT)
Copyright (c) 2014 singwhatiwanna
https://github.com/singwhatiwanna
http://blog.csdn.net/singwhatiwanna
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
package com.ryg.expandable.ui;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.ExpandableListView;
import android.widget.AbsListView.OnScrollListener;
public class PinnedHeaderExpandableListView extends ExpandableListView implements OnScrollListener {
private static final String TAG = "PinnedHeaderExpandableListView";
private static final boolean DEBUG = true;
public interface OnHeaderUpdateListener {
/**
* 返回一个view对象即可
* 注意:view必须要有LayoutParams
*/
public View getPinnedHeader();
public void updatePinnedHeader(View headerView, int firstVisibleGroupPos);
}
private View mHeaderView;
private int mHeaderWidth;
private int mHeaderHeight;
private View mTouchTarget;
private OnScrollListener mScrollListener;
private OnHeaderUpdateListener mHeaderUpdateListener;
private boolean mActionDownHappened = false;
protected boolean mIsHeaderGroupClickable = true;
public PinnedHeaderExpandableListView(Context context) {
super(context);
initView();
}
public PinnedHeaderExpandableListView(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
}
public PinnedHeaderExpandableListView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initView();
}
private void initView() {
setFadingEdgeLength(0);
setOnScrollListener(this);
}
@Override
public void setOnScrollListener(OnScrollListener l) {
if (l != this) {
mScrollListener = l;
} else {
mScrollListener = null;
}
super.setOnScrollListener(this);
}
/**
* 给group添加点击事件监听
* @param onGroupClickListener 监听
* @param isHeaderGroupClickable 表示header是否可点击<br/>
* note : 当不想group可点击的时候,需要在OnGroupClickListener#onGroupClick中返回true,
* 并将isHeaderGroupClickable设为false即可
*/
public void setOnGroupClickListener(OnGroupClickListener onGroupClickListener, boolean isHeaderGroupClickable) {
mIsHeaderGroupClickable = isHeaderGroupClickable;
super.setOnGroupClickListener(onGroupClickListener);
}
public void setOnHeaderUpdateListener(OnHeaderUpdateListener listener) {
mHeaderUpdateListener = listener;
if (listener == null) {
mHeaderView = null;
mHeaderWidth = mHeaderHeight = 0;
return;
}
mHeaderView = listener.getPinnedHeader();
int firstVisiblePos = getFirstVisiblePosition();
int firstVisibleGroupPos = getPackedPositionGroup(getExpandableListPosition(firstVisiblePos));
listener.updatePinnedHeader(mHeaderView, firstVisibleGroupPos);
requestLayout();
postInvalidate();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (mHeaderView == null) {
return;
}
measureChild(mHeaderView, widthMeasureSpec, heightMeasureSpec);
mHeaderWidth = mHeaderView.getMeasuredWidth();
mHeaderHeight = mHeaderView.getMeasuredHeight();
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
if (mHeaderView == null) {
return;
}
int delta = mHeaderView.getTop();
mHeaderView.layout(0, delta, mHeaderWidth, mHeaderHeight + delta);
}
@Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
if (mHeaderView != null) {
drawChild(canvas, mHeaderView, getDrawingTime());
}
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
int x = (int) ev.getX();
int y = (int) ev.getY();
int pos = pointToPosition(x, y);
if (mHeaderView != null && y >= mHeaderView.getTop() && y <= mHeaderView.getBottom()) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
mTouchTarget = getTouchTarget(mHeaderView, x, y);
mActionDownHappened = true;
} else if (ev.getAction() == MotionEvent.ACTION_UP) {
View touchTarget = getTouchTarget(mHeaderView, x, y);
if (touchTarget == mTouchTarget && mTouchTarget.isClickable()) {
mTouchTarget.performClick();
invalidate(new Rect(0, 0, mHeaderWidth, mHeaderHeight));
} else if (mIsHeaderGroupClickable){
int groupPosition = getPackedPositionGroup(getExpandableListPosition(pos));
if (groupPosition != INVALID_POSITION && mActionDownHappened) {
if (isGroupExpanded(groupPosition)) {
collapseGroup(groupPosition);
} else {
expandGroup(groupPosition);
}
}
}
mActionDownHappened = false;
}
return true;
}
return super.dispatchTouchEvent(ev);
}
private View getTouchTarget(View view, int x, int y) {
if (!(view instanceof ViewGroup)) {
return view;
}
ViewGroup parent = (ViewGroup)view;
int childrenCount = parent.getChildCount();
final boolean customOrder = isChildrenDrawingOrderEnabled();
View target = null;
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i;
final View child = parent.getChildAt(childIndex);
if (isTouchPointInView(child, x, y)) {
target = child;
break;
}
}
if (target == null) {
target = parent;
}
return target;
}
private boolean isTouchPointInView(View view, int x, int y) {
if (view.isClickable() && y >= view.getTop() && y <= view.getBottom()
&& x >= view.getLeft() && x <= view.getRight()) {
return true;
}
return false;
}
public void requestRefreshHeader() {
refreshHeader();
invalidate(new Rect(0, 0, mHeaderWidth, mHeaderHeight));
}
protected void refreshHeader() {
if (mHeaderView == null) {
return;
}
int firstVisiblePos = getFirstVisiblePosition();
int pos = firstVisiblePos + 1;
int firstVisibleGroupPos = getPackedPositionGroup(getExpandableListPosition(firstVisiblePos));
int group = getPackedPositionGroup(getExpandableListPosition(pos));
if (DEBUG) {
Log.d(TAG, "refreshHeader firstVisibleGroupPos=" + firstVisibleGroupPos);
}
if (group == firstVisibleGroupPos + 1) {
View view = getChildAt(1);
if (view == null) {
Log.w(TAG, "Warning : refreshHeader getChildAt(1)=null");
return;
}
if (view.getTop() <= mHeaderHeight) {
int delta = mHeaderHeight - view.getTop();
mHeaderView.layout(0, -delta, mHeaderWidth, mHeaderHeight - delta);
} else {
//TODO : note it, when cause bug, remove it
mHeaderView.layout(0, 0, mHeaderWidth, mHeaderHeight);
}
} else {
mHeaderView.layout(0, 0, mHeaderWidth, mHeaderHeight);
}
if (mHeaderUpdateListener != null) {
mHeaderUpdateListener.updatePinnedHeader(mHeaderView, firstVisibleGroupPos);
}
}
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
if (mScrollListener != null) {
mScrollListener.onScrollStateChanged(view, scrollState);
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
if (totalItemCount > 0) {
refreshHeader();
}
if (mScrollListener != null) {
mScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount);
}
}
}
package com.ryg.expandable;
import java.util.ArrayList;
import java.util.List;
import com.ryg.expandable.R;
import com.ryg.expandable.ui.PinnedHeaderExpandableListView;
import com.ryg.expandable.ui.StickyLayout;
import com.ryg.expandable.ui.PinnedHeaderExpandableListView.OnHeaderUpdateListener;
import com.ryg.expandable.ui.StickyLayout.OnGiveUpTouchEventListener;
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.AbsListView.LayoutParams;
import android.widget.BaseExpandableListAdapter;
import android.widget.Button;
import android.widget.ExpandableListView;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
public class MainActivity extends Activity implements
ExpandableListView.OnChildClickListener,
ExpandableListView.OnGroupClickListener,
OnHeaderUpdateListener, OnGiveUpTouchEventListener {
private PinnedHeaderExpandableListView expandableListView;
private StickyLayout stickyLayout;
private ArrayList<Group> groupList;
private ArrayList<List<People>> childList;
private MyexpandableListAdapter adapter;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
expandableListView = (PinnedHeaderExpandableListView) findViewById(R.id.expandablelist);
stickyLayout = (StickyLayout)findViewById(R.id.sticky_layout);
initData();
adapter = new MyexpandableListAdapter(this);
expandableListView.setAdapter(adapter);
// 展开所有group
for (int i = 0, count = expandableListView.getCount(); i < count; i++) {
expandableListView.expandGroup(i);
}
expandableListView.setOnHeaderUpdateListener(this);
expandableListView.setOnChildClickListener(this);
expandableListView.setOnGroupClickListener(this);
stickyLayout.setOnGiveUpTouchEventListener(this);
}
/***
* InitData
*/
void initData() {
groupList = new ArrayList<Group>();
Group group = null;
for (int i = 0; i < 3; i++) {
group = new Group();
group.setTitle("group-" + i);
groupList.add(group);
}
childList = new ArrayList<List<People>>();
for (int i = 0; i < groupList.size(); i++) {
ArrayList<People> childTemp;
if (i == 0) {
childTemp = new ArrayList<People>();
for (int j = 0; j < 13; j++) {
People people = new People();
people.setName("yy-" + j);
people.setAge(30);
people.setAddress("sh-" + j);
childTemp.add(people);
}
} else if (i == 1) {
childTemp = new ArrayList<People>();
for (int j = 0; j < 8; j++) {
People people = new People();
people.setName("ff-" + j);
people.setAge(40);
people.setAddress("sh-" + j);
childTemp.add(people);
}
} else {
childTemp = new ArrayList<People>();
for (int j = 0; j < 23; j++) {
People people = new People();
people.setName("hh-" + j);
people.setAge(20);
people.setAddress("sh-" + j);
childTemp.add(people);
}
}
childList.add(childTemp);
}
}
/***
* 数据源
*
* @author Administrator
*
*/
class MyexpandableListAdapter extends BaseExpandableListAdapter {
private Context context;
private LayoutInflater inflater;
public MyexpandableListAdapter(Context context) {
this.context = context;
inflater = LayoutInflater.from(context);
}
// 返回父列表个数
@Override
public int getGroupCount() {
return groupList.size();
}
// 返回子列表个数
@Override
public int getChildrenCount(int groupPosition) {
return childList.get(groupPosition).size();
}
@Override
public Object getGroup(int groupPosition) {
return groupList.get(groupPosition);
}
@Override
public Object getChild(int groupPosition, int childPosition) {
return childList.get(groupPosition).get(childPosition);
}
@Override
public long getGroupId(int groupPosition) {
return groupPosition;
}
@Override
public long getChildId(int groupPosition, int childPosition) {
return childPosition;
}
@Override
public boolean hasStableIds() {
return true;
}
@Override
public View getGroupView(int groupPosition, boolean isExpanded,
View convertView, ViewGroup parent) {
GroupHolder groupHolder = null;
if (convertView == null) {
groupHolder = new GroupHolder();
convertView = inflater.inflate(R.layout.group, null);
groupHolder.textView = (TextView) convertView
.findViewById(R.id.group);
groupHolder.imageView = (ImageView) convertView
.findViewById(R.id.image);
convertView.setTag(groupHolder);
} else {
groupHolder = (GroupHolder) convertView.getTag();
}
groupHolder.textView.setText(((Group) getGroup(groupPosition))
.getTitle());
if (isExpanded)// ture is Expanded or false is not isExpanded
groupHolder.imageView.setImageResource(R.drawable.expanded);
else
groupHolder.imageView.setImageResource(R.drawable.collapse);
return convertView;
}
@Override
public View getChildView(int groupPosition, int childPosition,
boolean isLastChild, View convertView, ViewGroup parent) {
ChildHolder childHolder = null;
if (convertView == null) {
childHolder = new ChildHolder();
convertView = inflater.inflate(R.layout.child, null);
childHolder.textName = (TextView) convertView
.findViewById(R.id.name);
childHolder.textAge = (TextView) convertView
.findViewById(R.id.age);
childHolder.textAddress = (TextView) convertView
.findViewById(R.id.address);
childHolder.imageView = (ImageView) convertView
.findViewById(R.id.image);
Button button = (Button) convertView
.findViewById(R.id.button1);
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, "clicked pos=", Toast.LENGTH_SHORT).show();
}
});
convertView.setTag(childHolder);
} else {
childHolder = (ChildHolder) convertView.getTag();
}
childHolder.textName.setText(((People) getChild(groupPosition,
childPosition)).getName());
childHolder.textAge.setText(String.valueOf(((People) getChild(
groupPosition, childPosition)).getAge()));
childHolder.textAddress.setText(((People) getChild(groupPosition,
childPosition)).getAddress());
return convertView;
}
@Override
public boolean isChildSelectable(int groupPosition, int childPosition) {
return true;
}
}
@Override
public boolean onGroupClick(final ExpandableListView parent, final View v,
int groupPosition, final long id) {
return false;
}
@Override
public boolean onChildClick(ExpandableListView parent, View v,
int groupPosition, int childPosition, long id) {
Toast.makeText(MainActivity.this,
childList.get(groupPosition).get(childPosition).getName(), 1)
.show();
return false;
}
class GroupHolder {
TextView textView;
ImageView imageView;
}
class ChildHolder {
TextView textName;
TextView textAge;
TextView textAddress;
ImageView imageView;
}
@Override
public View getPinnedHeader() {
View headerView = (ViewGroup) getLayoutInflater().inflate(R.layout.group, null);
headerView.setLayoutParams(new LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
return headerView;
}
@Override
public void updatePinnedHeader(View headerView, int firstVisibleGroupPos) {
Group firstVisibleGroup = (Group) adapter.getGroup(firstVisibleGroupPos);
TextView textView = (TextView) headerView.findViewById(R.id.group);
textView.setText(firstVisibleGroup.getTitle());
}
@Override
public boolean giveUpTouchEvent(MotionEvent event) {
if (expandableListView.getFirstVisiblePosition() == 0) {
View view = expandableListView.getChildAt(0);
if (view != null && view.getTop() >= 0) {
return true;
}
}
return false;
}
}
package com.ryg.utils;
import android.content.Context;
import android.util.TypedValue;
public class Utils {
public static int dp2px(Context context, int dp) {
return Math.round(TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP, dp, context.getResources().getDisplayMetrics()));
}
}