diff --git a/LICENSE b/LICENSE
index 5fe32357dd4e5b560177c7d41de7bca346594456..2445a43bf61d3546c6b81e54ec5c01977a8f1cac 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,8 @@
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
diff --git a/PinnedHeaderExpandableListView/.classpath b/PinnedHeaderExpandableListView/.classpath
new file mode 100644
index 0000000000000000000000000000000000000000..7bc01d9a9c6873b7e4fea3b29ee945267845ae86
--- /dev/null
+++ b/PinnedHeaderExpandableListView/.classpath
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/PinnedHeaderExpandableListView/.project b/PinnedHeaderExpandableListView/.project
new file mode 100644
index 0000000000000000000000000000000000000000..3bbaf24b621c22fade76b254bd4138401cbf9c47
--- /dev/null
+++ b/PinnedHeaderExpandableListView/.project
@@ -0,0 +1,33 @@
+
+
+ PinnedHeaderExpandableListView
+
+
+
+
+
+ com.android.ide.eclipse.adt.ResourceManagerBuilder
+
+
+
+
+ com.android.ide.eclipse.adt.PreCompilerBuilder
+
+
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+
+ com.android.ide.eclipse.adt.ApkBuilder
+
+
+
+
+
+ com.android.ide.eclipse.adt.AndroidNature
+ org.eclipse.jdt.core.javanature
+
+
diff --git a/PinnedHeaderExpandableListView/AndroidManifest.xml b/PinnedHeaderExpandableListView/AndroidManifest.xml
new file mode 100644
index 0000000000000000000000000000000000000000..752430bfbd0d423416ebba12f2b6a214a04b10ad
--- /dev/null
+++ b/PinnedHeaderExpandableListView/AndroidManifest.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/PinnedHeaderExpandableListView/bin/ExpandableListViewDemo.apk b/PinnedHeaderExpandableListView/bin/ExpandableListViewDemo.apk
new file mode 100644
index 0000000000000000000000000000000000000000..af66e828c96e15a65be52009861bd1114192e208
Binary files /dev/null and b/PinnedHeaderExpandableListView/bin/ExpandableListViewDemo.apk differ
diff --git a/PinnedHeaderExpandableListView/ic_launcher-web.png b/PinnedHeaderExpandableListView/ic_launcher-web.png
new file mode 100644
index 0000000000000000000000000000000000000000..c37372acb9ce85afcef436b349984adb8735347b
Binary files /dev/null and b/PinnedHeaderExpandableListView/ic_launcher-web.png differ
diff --git a/PinnedHeaderExpandableListView/libs/android-support-v4.jar b/PinnedHeaderExpandableListView/libs/android-support-v4.jar
new file mode 100644
index 0000000000000000000000000000000000000000..018c1272b251e0698d28599a4d00833c82735855
Binary files /dev/null and b/PinnedHeaderExpandableListView/libs/android-support-v4.jar differ
diff --git a/PinnedHeaderExpandableListView/proguard-project.txt b/PinnedHeaderExpandableListView/proguard-project.txt
new file mode 100644
index 0000000000000000000000000000000000000000..f2fe1559a217865a5454add526dcc446f892385b
--- /dev/null
+++ b/PinnedHeaderExpandableListView/proguard-project.txt
@@ -0,0 +1,20 @@
+# To enable ProGuard in your project, edit project.properties
+# to define the proguard.config property as described in that file.
+#
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in ${sdk.dir}/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the ProGuard
+# include property in project.properties.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
diff --git a/PinnedHeaderExpandableListView/project.properties b/PinnedHeaderExpandableListView/project.properties
new file mode 100644
index 0000000000000000000000000000000000000000..4ab125693c7c484a0252ee7eca9616e0f9b1ac67
--- /dev/null
+++ b/PinnedHeaderExpandableListView/project.properties
@@ -0,0 +1,14 @@
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file must be checked in Version Control Systems.
+#
+# To customize properties used by the Ant build system edit
+# "ant.properties", and override values to adapt the script to your
+# project structure.
+#
+# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
+#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
+
+# Project target.
+target=android-19
diff --git a/PinnedHeaderExpandableListView/res/drawable-hdpi/collapse.png b/PinnedHeaderExpandableListView/res/drawable-hdpi/collapse.png
new file mode 100644
index 0000000000000000000000000000000000000000..58c5d258f15c3454876140a7977c85703c70ed0a
Binary files /dev/null and b/PinnedHeaderExpandableListView/res/drawable-hdpi/collapse.png differ
diff --git a/PinnedHeaderExpandableListView/res/drawable-hdpi/expanded.png b/PinnedHeaderExpandableListView/res/drawable-hdpi/expanded.png
new file mode 100644
index 0000000000000000000000000000000000000000..da666a83f22edf80bacc13228f95e59406996389
Binary files /dev/null and b/PinnedHeaderExpandableListView/res/drawable-hdpi/expanded.png differ
diff --git a/PinnedHeaderExpandableListView/res/drawable-hdpi/ic_action_search.png b/PinnedHeaderExpandableListView/res/drawable-hdpi/ic_action_search.png
new file mode 100644
index 0000000000000000000000000000000000000000..67de12decbd60613f6716de113daae46dc7d6ff9
Binary files /dev/null and b/PinnedHeaderExpandableListView/res/drawable-hdpi/ic_action_search.png differ
diff --git a/PinnedHeaderExpandableListView/res/drawable-hdpi/ic_launcher.png b/PinnedHeaderExpandableListView/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000000000000000000000000000000000000..a301d5795aaa4bc1d6b829815ca4515f44f4b394
Binary files /dev/null and b/PinnedHeaderExpandableListView/res/drawable-hdpi/ic_launcher.png differ
diff --git a/PinnedHeaderExpandableListView/res/drawable-ldpi/ic_launcher.png b/PinnedHeaderExpandableListView/res/drawable-ldpi/ic_launcher.png
new file mode 100644
index 0000000000000000000000000000000000000000..2c2a58b2fad749f79450ad87434add0c4f92a7f2
Binary files /dev/null and b/PinnedHeaderExpandableListView/res/drawable-ldpi/ic_launcher.png differ
diff --git a/PinnedHeaderExpandableListView/res/drawable-mdpi/ic_action_search.png b/PinnedHeaderExpandableListView/res/drawable-mdpi/ic_action_search.png
new file mode 100644
index 0000000000000000000000000000000000000000..134d5490bd3310559cc944d44d24857eef29f2a1
Binary files /dev/null and b/PinnedHeaderExpandableListView/res/drawable-mdpi/ic_action_search.png differ
diff --git a/PinnedHeaderExpandableListView/res/drawable-mdpi/ic_launcher.png b/PinnedHeaderExpandableListView/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000000000000000000000000000000000000..f91f736fe7a364fc4227627200f1f638d4e0952e
Binary files /dev/null and b/PinnedHeaderExpandableListView/res/drawable-mdpi/ic_launcher.png differ
diff --git a/PinnedHeaderExpandableListView/res/drawable-xhdpi/ic_action_search.png b/PinnedHeaderExpandableListView/res/drawable-xhdpi/ic_action_search.png
new file mode 100644
index 0000000000000000000000000000000000000000..d699c6b37e0dcb1d636ebdd6733ab6d576d97ab5
Binary files /dev/null and b/PinnedHeaderExpandableListView/res/drawable-xhdpi/ic_action_search.png differ
diff --git a/PinnedHeaderExpandableListView/res/drawable-xhdpi/ic_launcher.png b/PinnedHeaderExpandableListView/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000000000000000000000000000000000000..96095ec84ba3819f01c064af82c87cfede3b73ec
Binary files /dev/null and b/PinnedHeaderExpandableListView/res/drawable-xhdpi/ic_launcher.png differ
diff --git a/PinnedHeaderExpandableListView/res/drawable/child_bg.xml b/PinnedHeaderExpandableListView/res/drawable/child_bg.xml
new file mode 100644
index 0000000000000000000000000000000000000000..bd88309e68135b301ebcc18077942667394c4a17
--- /dev/null
+++ b/PinnedHeaderExpandableListView/res/drawable/child_bg.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/PinnedHeaderExpandableListView/res/drawable/selector_group.xml b/PinnedHeaderExpandableListView/res/drawable/selector_group.xml
new file mode 100644
index 0000000000000000000000000000000000000000..dcdbdb16ba3858853196277a264fe3845faafa6e
--- /dev/null
+++ b/PinnedHeaderExpandableListView/res/drawable/selector_group.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/PinnedHeaderExpandableListView/res/drawable/selector_item.xml b/PinnedHeaderExpandableListView/res/drawable/selector_item.xml
new file mode 100644
index 0000000000000000000000000000000000000000..158f587beedf440ce18f8560f198faa87f7884c7
--- /dev/null
+++ b/PinnedHeaderExpandableListView/res/drawable/selector_item.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/PinnedHeaderExpandableListView/res/layout/child.xml b/PinnedHeaderExpandableListView/res/layout/child.xml
new file mode 100644
index 0000000000000000000000000000000000000000..e7df774803ed2f4cb612f7dc3b38bf65d0c18bd1
--- /dev/null
+++ b/PinnedHeaderExpandableListView/res/layout/child.xml
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/PinnedHeaderExpandableListView/res/layout/group.xml b/PinnedHeaderExpandableListView/res/layout/group.xml
new file mode 100644
index 0000000000000000000000000000000000000000..0cdb97d5f80a91196da9c992cc98fe4e3c6d0d50
--- /dev/null
+++ b/PinnedHeaderExpandableListView/res/layout/group.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/PinnedHeaderExpandableListView/res/layout/main.xml b/PinnedHeaderExpandableListView/res/layout/main.xml
new file mode 100644
index 0000000000000000000000000000000000000000..4d02dcf94b70239408df2478f0c7566083f93028
--- /dev/null
+++ b/PinnedHeaderExpandableListView/res/layout/main.xml
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/PinnedHeaderExpandableListView/res/menu/main.xml b/PinnedHeaderExpandableListView/res/menu/main.xml
new file mode 100644
index 0000000000000000000000000000000000000000..44a11e7db01bbbaa8925eb0bc694ee17636069fb
--- /dev/null
+++ b/PinnedHeaderExpandableListView/res/menu/main.xml
@@ -0,0 +1,5 @@
+
diff --git a/PinnedHeaderExpandableListView/res/values/color.xml b/PinnedHeaderExpandableListView/res/values/color.xml
new file mode 100644
index 0000000000000000000000000000000000000000..42bf12e4a9d81de9de79c1ce6c24e26347fd1759
--- /dev/null
+++ b/PinnedHeaderExpandableListView/res/values/color.xml
@@ -0,0 +1,21 @@
+
+
+
+ #ffffff
+ #00000001
+ #e0000000
+ #ffa500
+ #0066dd
+ #000000
+
+ #00000000
+ #fcb75c
+ #00000000
+ #D3D3D3
+ #50000000
+ #fe0202
+ #e5e5e5
+ #50000000
+ #BEBEBE
+
+
\ No newline at end of file
diff --git a/PinnedHeaderExpandableListView/res/values/strings.xml b/PinnedHeaderExpandableListView/res/values/strings.xml
new file mode 100644
index 0000000000000000000000000000000000000000..15be3c9d731e268a7dea4b633734d151d9e694b0
--- /dev/null
+++ b/PinnedHeaderExpandableListView/res/values/strings.xml
@@ -0,0 +1,8 @@
+
+
+ 可展开的列表
+ Hello world!
+ Settings
+ MainActivity
+
+
\ No newline at end of file
diff --git a/PinnedHeaderExpandableListView/res/values/styles.xml b/PinnedHeaderExpandableListView/res/values/styles.xml
new file mode 100644
index 0000000000000000000000000000000000000000..e20c8271729b08596e9b243a010e71c64cf99bc1
--- /dev/null
+++ b/PinnedHeaderExpandableListView/res/values/styles.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/PinnedHeaderExpandableListView/src/com/ryg/expandable/Group.java b/PinnedHeaderExpandableListView/src/com/ryg/expandable/Group.java
new file mode 100644
index 0000000000000000000000000000000000000000..88060bd1acf58ecd0acb5f3f35adec7c4782518a
--- /dev/null
+++ b/PinnedHeaderExpandableListView/src/com/ryg/expandable/Group.java
@@ -0,0 +1,14 @@
+package com.ryg.expandable;
+
+public class Group {
+
+ private String title;
+
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+}
diff --git a/PinnedHeaderExpandableListView/src/com/ryg/expandable/MainActivity.java b/PinnedHeaderExpandableListView/src/com/ryg/expandable/MainActivity.java
new file mode 100644
index 0000000000000000000000000000000000000000..773d177f32c82e11a6f7907bc3a45b0db14b9a76
--- /dev/null
+++ b/PinnedHeaderExpandableListView/src/com/ryg/expandable/MainActivity.java
@@ -0,0 +1,297 @@
+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 groupList;
+ private ArrayList> childList;
+
+ private MyexpandableListAdapter adapter;
+
+ private ViewGroup mHeaderView;
+
+ @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 = null;
+ for (int i = 0; i < 3; i++) {
+ group = new Group();
+ group.setTitle("group-" + i);
+ groupList.add(group);
+ }
+
+ childList = new ArrayList>();
+ for (int i = 0; i < groupList.size(); i++) {
+ ArrayList childTemp;
+ if (i == 0) {
+ childTemp = new ArrayList();
+ 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();
+ 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();
+ 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() {
+ if (mHeaderView == null) {
+ mHeaderView = (ViewGroup) getLayoutInflater().inflate(R.layout.group, null);
+ mHeaderView.setLayoutParams(new LayoutParams(
+ LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
+ }
+ return mHeaderView;
+ }
+
+ @Override
+ public void updatePinnedHeader(int firstVisibleGroupPos) {
+ Group firstVisibleGroup = (Group) adapter.getGroup(firstVisibleGroupPos);
+ TextView textView = (TextView) getPinnedHeader().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;
+ }
+
+}
diff --git a/PinnedHeaderExpandableListView/src/com/ryg/expandable/People.java b/PinnedHeaderExpandableListView/src/com/ryg/expandable/People.java
new file mode 100644
index 0000000000000000000000000000000000000000..25a5d990f3e56eb08116d2eed7f36726bc9ab8fb
--- /dev/null
+++ b/PinnedHeaderExpandableListView/src/com/ryg/expandable/People.java
@@ -0,0 +1,33 @@
+package com.ryg.expandable;
+
+public class People {
+
+ private String name;
+ private int age;
+ private String address;
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public int getAge() {
+ return age;
+ }
+
+ public void setAge(int age) {
+ this.age = age;
+ }
+
+ public String getAddress() {
+ return address;
+ }
+
+ public void setAddress(String address) {
+ this.address = address;
+ }
+
+}
diff --git a/PinnedHeaderExpandableListView/src/com/ryg/expandable/ui/PinnedHeaderExpandableListView.java b/PinnedHeaderExpandableListView/src/com/ryg/expandable/ui/PinnedHeaderExpandableListView.java
new file mode 100644
index 0000000000000000000000000000000000000000..70e8e9d8775ef55f5953f4457abc46991d4114a2
--- /dev/null
+++ b/PinnedHeaderExpandableListView/src/com/ryg/expandable/ui/PinnedHeaderExpandableListView.java
@@ -0,0 +1,206 @@
+/**
+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.util.AttributeSet;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+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";
+
+ public interface OnHeaderUpdateListener {
+ /**
+ * 采用单例模式返回同一个view对象即可
+ * 注意:view必须要有LayoutParams
+ */
+ public View getPinnedHeader();
+
+ public void updatePinnedHeader(int firstVisibleGroupPos);
+ }
+
+ private View mHeaderView;
+ private int mHeaderWidth;
+ private int mHeaderHeight;
+
+ private OnScrollListener mScrollListener;
+ private OnHeaderUpdateListener mHeaderUpdateListener;
+
+ private boolean mActionDownHappened = false;
+
+
+ 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;
+ }
+ super.setOnScrollListener(this);
+ }
+
+ public void setOnHeaderUpdateListener(OnHeaderUpdateListener listener) {
+ mHeaderUpdateListener = listener;
+ if (listener == null) {
+ return;
+ }
+ mHeaderView = listener.getPinnedHeader();
+ int firstVisiblePos = getFirstVisiblePosition();
+ int firstVisibleGroupPos = getPackedPositionGroup(getExpandableListPosition(firstVisiblePos));
+ listener.updatePinnedHeader(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;
+ }
+ mHeaderView.layout(0, 0, mHeaderWidth, mHeaderHeight);
+ }
+
+ @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();
+ Log.d(TAG, "dispatchTouchEvent");
+ int pos = pointToPosition(x, y);
+ if (y >= mHeaderView.getTop() && y <= mHeaderView.getBottom()) {
+ if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+ mActionDownHappened = true;
+ } else if (ev.getAction() == MotionEvent.ACTION_UP) {
+ 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);
+ }
+
+ 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 (group == firstVisibleGroupPos + 1) {
+ View view = getChildAt(1);
+ if (view.getTop() <= mHeaderHeight) {
+ int delta = mHeaderHeight - view.getTop();
+ mHeaderView.layout(0, -delta, mHeaderWidth, mHeaderHeight - delta);
+ }
+ } else {
+ mHeaderView.layout(0, 0, mHeaderWidth, mHeaderHeight);
+ }
+
+ if (mHeaderUpdateListener != null) {
+ mHeaderUpdateListener.updatePinnedHeader(firstVisibleGroupPos);
+ }
+ }
+
+ @Override
+ public void onScrollStateChanged(AbsListView view, int scrollState) {
+ if (mHeaderView != null && scrollState == SCROLL_STATE_IDLE) {
+ int firstVisiblePos = getFirstVisiblePosition();
+ if (firstVisiblePos == 0) {
+ mHeaderView.layout(0, 0, mHeaderWidth, mHeaderHeight);
+ }
+ }
+ 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);
+ }
+ }
+
+}
diff --git a/PinnedHeaderExpandableListView/src/com/ryg/expandable/ui/StickyLayout.java b/PinnedHeaderExpandableListView/src/com/ryg/expandable/ui/StickyLayout.java
new file mode 100644
index 0000000000000000000000000000000000000000..ac75bb6f94cc6d6c28f13e0b73d8c623373d54a1
--- /dev/null
+++ b/PinnedHeaderExpandableListView/src/com/ryg/expandable/ui/StickyLayout.java
@@ -0,0 +1,237 @@
+/**
+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 java.util.NoSuchElementException;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.os.Build;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+
+public class StickyLayout extends LinearLayout {
+ private static final String TAG = "StickyLayout";
+
+ public interface OnGiveUpTouchEventListener {
+ public boolean giveUpTouchEvent(MotionEvent event);
+ }
+
+ private View mHeader;
+ private View mContent;
+ private OnGiveUpTouchEventListener mGiveUpTouchEventListener;
+
+ // header的高度 单位:px
+ private int mOriginalHeaderHeight;
+ private int mHeaderHeight;
+
+ private int mStatus = STATUS_EXPANDED;
+ public static final int STATUS_EXPANDED = 1;
+ public static final int STATUS_COLLAPSED = 2;
+
+ private int mTouchSlop;
+
+ // 分别记录上次滑动的坐标
+ private int mLastX = 0;
+ private int mLastY = 0;
+
+ // 分别记录上次滑动的坐标(onInterceptTouchEvent)
+ private int mLastXIntercept = 0;
+ private int mLastYIntercept = 0;
+
+ // 用来控制滑动角度,仅当角度a满足如下条件才进行滑动:tan a = deltaX / deltaY > 2
+ private static final int TAN = 2;
+
+ public StickyLayout(Context context) {
+ super(context);
+ }
+
+ public StickyLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ public StickyLayout(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ @Override
+ public void onWindowFocusChanged(boolean hasWindowFocus) {
+ super.onWindowFocusChanged(hasWindowFocus);
+ if (hasWindowFocus && (mHeader == null || mContent == null)) {
+ initData();
+ }
+ }
+
+ private void initData() {
+ int headerId= getResources().getIdentifier("header", "id", getContext().getPackageName());
+ int contentId = getResources().getIdentifier("content", "id", getContext().getPackageName());
+ if (headerId != 0 && contentId != 0) {
+ mHeader = findViewById(headerId);
+ mContent = findViewById(contentId);
+ mOriginalHeaderHeight = mHeader.getMeasuredHeight();
+ mHeaderHeight = mOriginalHeaderHeight;
+ mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
+ Log.d(TAG, "mTouchSlop = " + mTouchSlop);
+ } else {
+ throw new NoSuchElementException("Did your view with \"header\" or \"content\" exist?");
+ }
+ }
+
+ public void setOnGiveUpTouchEventListener(OnGiveUpTouchEventListener l) {
+ mGiveUpTouchEventListener = l;
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent event) {
+ int intercepted = 0;
+ int x = (int) event.getX();
+ int y = (int) event.getY();
+
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN: {
+ mLastXIntercept = x;
+ mLastYIntercept = y;
+ mLastX = x;
+ mLastY = y;
+ intercepted = 0;
+ break;
+ }
+ case MotionEvent.ACTION_MOVE: {
+ int deltaX = x - mLastXIntercept;
+ int deltaY = y - mLastYIntercept;
+ if (mStatus == STATUS_EXPANDED && deltaY <= -mTouchSlop) {
+ intercepted = 1;
+ } else if (mGiveUpTouchEventListener != null) {
+ if (mGiveUpTouchEventListener.giveUpTouchEvent(event) && deltaY >= mTouchSlop) {
+ intercepted = 1;
+ }
+ }
+ break;
+ }
+ case MotionEvent.ACTION_UP: {
+ intercepted = 0;
+ mLastXIntercept = mLastYIntercept = 0;
+ break;
+ }
+ default:
+ break;
+ }
+
+ Log.d(TAG, "intercepted=" + intercepted);
+ return intercepted != 0;
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ int x = (int) event.getX();
+ int y = (int) event.getY();
+ Log.d(TAG, "x=" + x + " y=" + y + " mlastY=" + mLastY);
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN: {
+ break;
+ }
+ case MotionEvent.ACTION_MOVE: {
+ int deltaX = x - mLastX;
+ int deltaY = y - mLastY;
+ Log.d(TAG, "mHeaderHeight=" + mHeaderHeight + " deltaY=" + deltaY + " mlastY=" + mLastY);
+ mHeaderHeight += deltaY;
+ setHeaderHeight(mHeaderHeight);
+ break;
+ }
+ case MotionEvent.ACTION_UP: {
+ // 这里做了下判断,当松开手的时候,会自动向两边滑动,具体向哪边滑,要看当前所处的位置
+ int destHeight = 0;
+ if (mHeaderHeight <= mOriginalHeaderHeight * 0.5) {
+ destHeight = 0;
+ mStatus = STATUS_COLLAPSED;
+ } else {
+ destHeight = mOriginalHeaderHeight;
+ mStatus = STATUS_EXPANDED;
+ }
+ // 慢慢滑向终点
+ this.smoothSetHeaderHeight(mHeaderHeight, destHeight, 500);
+ break;
+ }
+ default:
+ break;
+ }
+ mLastX = x;
+ mLastY = y;
+ return true;
+ }
+
+ public void smoothSetHeaderHeight(final int from, final int to, long duration) {
+ final int frameCount = (int) (duration / 1000f * 30) + 1;
+ final float partation = (to - from) / (float) frameCount;
+ new Thread("Thread#smoothSetHeaderHeight") {
+
+ @Override
+ public void run() {
+ for (int i = 0; i < frameCount; i++) {
+ final int height;
+ if (i == frameCount - 1) {
+ height = to;
+ } else {
+ height = (int) (from + partation * i);
+ }
+ post(new Runnable() {
+ public void run() {
+ setHeaderHeight(height);
+ }
+ });
+ try {
+ sleep(10);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ };
+
+ }.start();
+ }
+
+ private void setHeaderHeight(int height) {
+ Log.d(TAG, "setHeaderHeight height=" + height);
+ if (height < 0) {
+ height = 0;
+ } else if (height > mOriginalHeaderHeight) {
+ height = mOriginalHeaderHeight;
+ }
+ if (mHeaderHeight != height || true) {
+ mHeaderHeight = height;
+ mHeader.getLayoutParams().height = mHeaderHeight;
+ mHeader.requestLayout();
+ }
+ }
+
+}
diff --git a/PinnedHeaderExpandableListView/src/com/ryg/utils/Utils.java b/PinnedHeaderExpandableListView/src/com/ryg/utils/Utils.java
new file mode 100644
index 0000000000000000000000000000000000000000..69936530a69d2f4a45c957bf79e4a29ae6d2fed1
--- /dev/null
+++ b/PinnedHeaderExpandableListView/src/com/ryg/utils/Utils.java
@@ -0,0 +1,13 @@
+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()));
+ }
+
+}