diff --git a/LICENSE b/LICENSE
index 37ec93a1..6615411d 100644
--- a/LICENSE
+++ b/LICENSE
@@ -176,7 +176,7 @@ recommend that a file or class name and description of purpose be included on
the same "printed page" as the copyright notice for easier identification within
third-party archives.
- Copyright [yyyy] [name of copyright owner]
+ Copyright 2014 Emil Sjölander
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
diff --git a/README.md b/README.md
index 2cfff299..f9c0a4c1 100644
--- a/README.md
+++ b/README.md
@@ -17,6 +17,16 @@ The goal of this project is to deliver a high performance replacement to `ListVi
Installing
---------------
+###Maven
+Add the following maven dependency exchanging `x.x.x` for the latest release.
+```XML
+
+ se.emilsjolander
+ stickylistheaders
+ x.x.x
+
+```
+
###Gradle
Add the following gradle dependency exchanging `x.x.x` for the latest release.
```groovy
@@ -40,6 +50,8 @@ In the following dialog navigate to StickyListHeaders which you cloned to your c
Getting Started
---------------
+###Base usage
+
Ok lets start with your activities or fragments xml file. It might look something like this.
```xml
+```
+Then you need to setup your listview on `onCreate()` or `onCreateView()`:
+```java
+ExpandableStickyListHeadersListView expandableStickyList = (ExpandableStickyListHeadersListView) findViewById(R.id.list);
+StickyListHeadersAdapter adapter = new MyAdapter(this);
+expandableStickyList.setAdapter(adapter);
+expandableStickyList.setOnHeaderClickListener(new StickyListHeadersListView.OnHeaderClickListener() {
+ @Override
+ public void onHeaderClick(StickyListHeadersListView l, View header, int itemPosition, long headerId, boolean currentlySticky) {
+ if(expandableStickyList.isHeaderCollapsed(headerId)){
+ expandableStickyList.expand(headerId);
+ }else {
+ expandableStickyList.collapse(headerId);
+ }
+ }
+ });
+```
+As you see,MyAdapter is just a StickyListHeadersAdapter which is mentioned in the previous section.
+You needn't do any more extra operations.
+
+There are three important functions:
+`isHeaderCollapsed(long headerId)`,`expand(long headerId)` and `collapse(long headerId)`.
+
+The function `isHeaderCollapsed` is used to check whether the subitems belonging to the header have collapsed.
+You can call `expand` or `collapse` method to hide or show subitems.
+You can also define a AnimationExecutor which implements `ExpandableStickyListHeadersListView.IAnimationExecutor`,
+and put it into the ExpandableStickyListHeadersListView by `setAnimExecutor` method,if you want more fancy animation when hiding or showing subitems.
+
+
Upgrading from 1.x versions
---------------------------
First of all the package name has changed from `com.emilsjolander.components.stickylistheaders` -> `se.emilsjolander.stickylistheaders` so update all your imports and xml files using StickyListHeaders!
@@ -184,6 +233,15 @@ public interface OnStickyHeaderOffsetChangedListener {
}
```
+A `OnStickyHeaderChangedListener` listens for changes to the header. This enables UI elements elsewhere to react to the current header (e.g. if each header is a date, then the rest of the UI can update when you scroll to a new date).
+```java
+public void setOnStickyHeaderChangedListener(OnStickyHeaderChangedListener listener);
+
+public interface OnStickyHeaderChangedListener {
+ void onStickyHeaderChanged(StickyListHeadersListView l, View header, int itemPosition, long headerId);
+}
+```
+
Here are two methods added to the API for inspecting the children of the underlying `ListView`. I could not override the normal `getChildAt()` and `getChildCount()` methods as that would mess up the underlying measurement system of the `FrameLayout` wrapping the `ListView`.
```java
public View getListChildAt(int index);
@@ -196,6 +254,17 @@ public void setDrawingListUnderStickyHeader(boolean drawingListUnderStickyHeader
public boolean isDrawingListUnderStickyHeader();
```
+If you are using a transparent action bar the following getter+setter will be very helpful. Use them to set the position of the sticky header from the top of the view.
+```java
+public void setStickyHeaderTopOffset(int stickyHeaderTopOffset);
+public int getStickyHeaderTopOffset();
+```
+
+Get the amount of overlap the sticky header has when position in on the top of the list.
+```java
+public int getHeaderOverlap(int position);
+```
+
Contributing
------------
Contributions are very welcome. Now that this library has grown in popularity i have a hard time keeping upp with all the issues while tending to a multitude of other projects as well as school. So if you find a bug in the library or want a feature and think you can fix it yourself, fork + pull request and i will greatly appreciate it!
diff --git a/build.gradle b/build.gradle
index a0989898..deca1d97 100644
--- a/build.gradle
+++ b/build.gradle
@@ -4,7 +4,7 @@ buildscript {
}
dependencies {
- classpath 'com.android.tools.build:gradle:0.7.+'
+ classpath 'com.android.tools.build:gradle:0.12.+'
}
}
diff --git a/gradle.properties b/gradle.properties
index e7b225dd..53aeae06 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,4 +1,4 @@
-VERSION_NAME=2.1.4
+VERSION_NAME=2.5.2
GROUP=se.emilsjolander
POM_DESCRIPTION=A small android library that makes it easy to make lists with section headers that stick to the top until a new section header comes along.
@@ -11,3 +11,4 @@ POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt
POM_LICENCE_DIST=repo
POM_DEVELOPER_ID=emilsjolander
POM_DEVELOPER_NAME=Emil Sjolander
+POM_DEVELOPER_EMAIL=sjolander.emil@gmail.com
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 6d44e25f..f8096caa 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=http\://services.gradle.org/distributions/gradle-1.9-all.zip
+distributionUrl=http\://services.gradle.org/distributions/gradle-1.12-all.zip
diff --git a/gradlew b/gradlew
old mode 100644
new mode 100755
diff --git a/library/build.gradle b/library/build.gradle
index a15de94c..c7bf0981 100644
--- a/library/build.gradle
+++ b/library/build.gradle
@@ -2,7 +2,7 @@ apply plugin: 'android-library'
android {
compileSdkVersion 19
- buildToolsVersion '19.0.0'
+ buildToolsVersion '19.1.0'
sourceSets {
main {
@@ -13,4 +13,4 @@ android {
}
}
-apply from: 'https://raw.github.com/chrisbanes/gradle-mvn-push/eaa6b5404b7594e6c23b884fdc5795f545db55dd/gradle-mvn-push.gradle'
+apply from: 'https://raw.github.com/shamanland/gradle-mvn-push/cc18d56549cdea03f744b6fff27911569394073e/gradle-mvn-push.gradle'
diff --git a/library/pom.xml b/library/pom.xml
new file mode 100644
index 00000000..7bd7ab43
--- /dev/null
+++ b/library/pom.xml
@@ -0,0 +1,60 @@
+
+
+ 4.0.0
+ se.emilsjolander
+ StickylListHeaders
+ 1.0-SNAPSHOT
+ apk
+ StickyListHeaders
+
+
+ UTF-8
+ 4.1.1.4
+
+ 3.6.0
+
+
+
+
+ com.google.android
+ android
+ ${platform.version}
+ provided
+
+
+
+
+ ${project.artifactId}
+ ${project.basedir}/src/se/emilsjolander/stickylistheaders
+
+
+ ${project.basedir}/res
+
+
+
+
+
+ com.jayway.maven.plugins.android.generation2
+ android-maven-plugin
+ ${android.plugin.version}
+ true
+
+
+
+
+
+ com.jayway.maven.plugins.android.generation2
+ android-maven-plugin
+
+
+
+ 16
+
+
+
+
+
+
+
+
diff --git a/library/res/values/attrs.xml b/library/res/values/attrs.xml
index f4560890..210ebe72 100644
--- a/library/res/values/attrs.xml
+++ b/library/res/values/attrs.xml
@@ -25,6 +25,7 @@
+
diff --git a/library/src/se/emilsjolander/stickylistheaders/AdapterWrapper.java b/library/src/se/emilsjolander/stickylistheaders/AdapterWrapper.java
index e67de0db..9721f9df 100644
--- a/library/src/se/emilsjolander/stickylistheaders/AdapterWrapper.java
+++ b/library/src/se/emilsjolander/stickylistheaders/AdapterWrapper.java
@@ -26,7 +26,7 @@
class AdapterWrapper extends BaseAdapter implements StickyListHeadersAdapter {
interface OnHeaderClickListener {
- public void onHeaderClick(View header, int itemPosition, long headerId);
+ void onHeaderClick(View header, int itemPosition, long headerId);
}
final StickyListHeadersAdapter mDelegate;
diff --git a/library/src/se/emilsjolander/stickylistheaders/ApiLevelTooLowException.java b/library/src/se/emilsjolander/stickylistheaders/ApiLevelTooLowException.java
deleted file mode 100644
index 5b0a8382..00000000
--- a/library/src/se/emilsjolander/stickylistheaders/ApiLevelTooLowException.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package se.emilsjolander.stickylistheaders;
-
-public class ApiLevelTooLowException extends RuntimeException {
-
- private static final long serialVersionUID = -5480068364264456757L;
-
- public ApiLevelTooLowException(int versionCode) {
- super("Requires API level " + versionCode);
- }
-
-}
diff --git a/library/src/se/emilsjolander/stickylistheaders/DistinctMultiHashMap.java b/library/src/se/emilsjolander/stickylistheaders/DistinctMultiHashMap.java
new file mode 100644
index 00000000..bf4523e7
--- /dev/null
+++ b/library/src/se/emilsjolander/stickylistheaders/DistinctMultiHashMap.java
@@ -0,0 +1,147 @@
+package se.emilsjolander.stickylistheaders;
+
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * a hash map can maintain an one-to-many relationship which the value only belongs to one “one” part
+ * and the map also support getKey by value quickly
+ *
+ * @author lsjwzh
+ */
+class DistinctMultiHashMap {
+ private IDMapper mIDMapper;
+
+ interface IDMapper{
+ public Object keyToKeyId(TKey key);
+ public TKey keyIdToKey(Object keyId);
+ public Object valueToValueId(TItemValue value);
+ public TItemValue valueIdToValue(Object valueId);
+ }
+
+ LinkedHashMap> mKeyToValuesMap = new LinkedHashMap>();
+ LinkedHashMap mValueToKeyIndexer = new LinkedHashMap();
+
+ DistinctMultiHashMap(){
+ this(new IDMapper() {
+ @Override
+ public Object keyToKeyId(TKey key) {
+ return key;
+ }
+
+ @Override
+ public TKey keyIdToKey(Object keyId) {
+ return (TKey) keyId;
+ }
+
+ @Override
+ public Object valueToValueId(TItemValue value) {
+ return value;
+ }
+
+ @Override
+ public TItemValue valueIdToValue(Object valueId) {
+ return (TItemValue) valueId;
+ }
+ });
+ }
+ DistinctMultiHashMap(IDMapper idMapper){
+ mIDMapper = idMapper;
+ }
+
+ public List get(TKey key){
+ //todo immutable
+ return mKeyToValuesMap.get(mIDMapper.keyToKeyId(key));
+ }
+ public TKey getKey(TItemValue value){
+ return mValueToKeyIndexer.get(mIDMapper.valueToValueId(value));
+ }
+
+ public void add(TKey key,TItemValue value){
+ Object keyId = mIDMapper.keyToKeyId(key);
+ if(mKeyToValuesMap.get(keyId)==null){
+ mKeyToValuesMap.put(keyId,new ArrayList());
+ }
+ //remove old relationship
+ TKey keyForValue = getKey(value);
+ if(keyForValue !=null){
+ mKeyToValuesMap.get(mIDMapper.keyToKeyId(keyForValue)).remove(value);
+ }
+ mValueToKeyIndexer.put(mIDMapper.valueToValueId(value), key);
+ if(!containsValue(mKeyToValuesMap.get(mIDMapper.keyToKeyId(key)),value)) {
+ mKeyToValuesMap.get(mIDMapper.keyToKeyId(key)).add(value);
+ }
+ }
+
+ public void removeKey(TKey key){
+ if(mKeyToValuesMap.get(mIDMapper.keyToKeyId(key))!=null){
+ for (TItemValue value : mKeyToValuesMap.get(mIDMapper.keyToKeyId(key))){
+ mValueToKeyIndexer.remove(mIDMapper.valueToValueId(value));
+ }
+ mKeyToValuesMap.remove(mIDMapper.keyToKeyId(key));
+ }
+ }
+ public void removeValue(TItemValue value){
+ if(getKey(value)!=null){
+ List itemValues = mKeyToValuesMap.get(mIDMapper.keyToKeyId(getKey(value)));
+ if(itemValues!=null){
+ itemValues.remove(value);
+ }
+ }
+ mValueToKeyIndexer.remove(mIDMapper.valueToValueId(value));
+ }
+
+ public void clear(){
+ mValueToKeyIndexer.clear();
+ mKeyToValuesMap.clear();
+ }
+
+ public void clearValues(){
+ for (Map.Entry> entry:entrySet()){
+ if(entry.getValue()!=null){
+ entry.getValue().clear();
+ }
+ }
+ mValueToKeyIndexer.clear();
+ }
+
+ public Set>> entrySet(){
+ return mKeyToValuesMap.entrySet();
+ }
+
+ public Set> reverseEntrySet(){
+ return mValueToKeyIndexer.entrySet();
+ }
+
+ public int size(){
+ return mKeyToValuesMap.size();
+ }
+ public int valuesSize(){
+ return mValueToKeyIndexer.size();
+ }
+
+ protected boolean containsValue(List list,TItemValue value){
+ for (TItemValue itemValue :list){
+ if(mIDMapper.valueToValueId(itemValue).equals(mIDMapper.valueToValueId(value))){
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * @param position
+ * @return
+ */
+ public TItemValue getValueByPosition(int position){
+ Object[] vauleIdArray = mValueToKeyIndexer.keySet().toArray();
+ if(position>vauleIdArray.length){
+ throw new IndexOutOfBoundsException();
+ }
+ Object valueId = vauleIdArray[position];
+ return mIDMapper.valueIdToValue(valueId);
+ }
+}
diff --git a/library/src/se/emilsjolander/stickylistheaders/DualHashMap.java b/library/src/se/emilsjolander/stickylistheaders/DualHashMap.java
new file mode 100644
index 00000000..efaaa69e
--- /dev/null
+++ b/library/src/se/emilsjolander/stickylistheaders/DualHashMap.java
@@ -0,0 +1,39 @@
+package se.emilsjolander.stickylistheaders;
+
+import java.util.HashMap;
+
+/**
+ * simple two way hashmap
+ * @author lsjwzh
+ */
+class DualHashMap {
+ HashMap mKeyToValue = new HashMap();
+ HashMap mValueToKey = new HashMap();
+
+ public void put(TKey t1, TValue t2){
+ remove(t1);
+ removeByValue(t2);
+ mKeyToValue.put(t1, t2);
+ mValueToKey.put(t2, t1);
+ }
+
+ public TKey getKey(TValue value){
+ return mValueToKey.get(value);
+ }
+ public TValue get(TKey key){
+ return mKeyToValue.get(key);
+ }
+
+ public void remove(TKey key){
+ if(get(key)!=null){
+ mValueToKey.remove(get(key));
+ }
+ mKeyToValue.remove(key);
+ }
+ public void removeByValue(TValue value){
+ if(getKey(value)!=null){
+ mKeyToValue.remove(getKey(value));
+ }
+ mValueToKey.remove(value);
+ }
+}
diff --git a/library/src/se/emilsjolander/stickylistheaders/ExpandableStickyListHeadersAdapter.java b/library/src/se/emilsjolander/stickylistheaders/ExpandableStickyListHeadersAdapter.java
new file mode 100644
index 00000000..5c932070
--- /dev/null
+++ b/library/src/se/emilsjolander/stickylistheaders/ExpandableStickyListHeadersAdapter.java
@@ -0,0 +1,131 @@
+package se.emilsjolander.stickylistheaders;
+
+import android.database.DataSetObserver;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+
+import java.util.ArrayList;
+import java.util.List;
+
+
+/**
+ * @author lsjwzh
+ */
+ class ExpandableStickyListHeadersAdapter extends BaseAdapter implements StickyListHeadersAdapter {
+
+ private final StickyListHeadersAdapter mInnerAdapter;
+ DualHashMap mViewToItemIdMap = new DualHashMap();
+ DistinctMultiHashMap mHeaderIdToViewMap = new DistinctMultiHashMap();
+ List mCollapseHeaderIds = new ArrayList();
+
+ ExpandableStickyListHeadersAdapter(StickyListHeadersAdapter innerAdapter){
+ this.mInnerAdapter = innerAdapter;
+ }
+
+ @Override
+ public View getHeaderView(int position, View convertView, ViewGroup parent) {
+ return mInnerAdapter.getHeaderView(position,convertView,parent);
+ }
+
+ @Override
+ public long getHeaderId(int position) {
+ return mInnerAdapter.getHeaderId(position);
+ }
+
+ @Override
+ public boolean areAllItemsEnabled() {
+ return mInnerAdapter.areAllItemsEnabled();
+ }
+
+ @Override
+ public boolean isEnabled(int i) {
+ return mInnerAdapter.isEnabled(i);
+ }
+
+ @Override
+ public void registerDataSetObserver(DataSetObserver dataSetObserver) {
+ mInnerAdapter.registerDataSetObserver(dataSetObserver);
+ }
+
+ @Override
+ public void unregisterDataSetObserver(DataSetObserver dataSetObserver) {
+ mInnerAdapter.unregisterDataSetObserver(dataSetObserver);
+ }
+
+ @Override
+ public int getCount() {
+ return mInnerAdapter.getCount();
+ }
+
+ @Override
+ public Object getItem(int i) {
+ return mInnerAdapter.getItem(i);
+ }
+
+ @Override
+ public long getItemId(int i) {
+ return mInnerAdapter.getItemId(i);
+ }
+
+ @Override
+ public boolean hasStableIds() {
+ return mInnerAdapter.hasStableIds();
+ }
+
+ @Override
+ public View getView(int i, View view, ViewGroup viewGroup) {
+ View convertView = mInnerAdapter.getView(i,view,viewGroup);
+ mViewToItemIdMap.put(convertView, getItemId(i));
+ mHeaderIdToViewMap.add((int) getHeaderId(i), convertView);
+ if(mCollapseHeaderIds.contains(getHeaderId(i))){
+ convertView.setVisibility(View.GONE);
+ }else {
+ convertView.setVisibility(View.VISIBLE);
+ }
+ return convertView;
+ }
+
+ @Override
+ public int getItemViewType(int i) {
+ return mInnerAdapter.getItemViewType(i);
+ }
+
+ @Override
+ public int getViewTypeCount() {
+ return mInnerAdapter.getViewTypeCount();
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return mInnerAdapter.isEmpty();
+ }
+
+ public List getItemViewsByHeaderId(long headerId){
+ return mHeaderIdToViewMap.get((int) headerId);
+ }
+
+ public boolean isHeaderCollapsed(long headerId){
+ return mCollapseHeaderIds.contains(headerId);
+ }
+
+ public void expand(long headerId) {
+ if(isHeaderCollapsed(headerId)){
+ mCollapseHeaderIds.remove((Object) headerId);
+ }
+ }
+
+ public void collapse(long headerId) {
+ if(!isHeaderCollapsed(headerId)){
+ mCollapseHeaderIds.add(headerId);
+ }
+ }
+
+ public View findViewByItemId(long itemId){
+ return mViewToItemIdMap.getKey(itemId);
+ }
+
+ public long findItemIdByView(View view){
+ return mViewToItemIdMap.get(view);
+ }
+}
diff --git a/library/src/se/emilsjolander/stickylistheaders/ExpandableStickyListHeadersListView.java b/library/src/se/emilsjolander/stickylistheaders/ExpandableStickyListHeadersListView.java
new file mode 100644
index 00000000..e314c2b3
--- /dev/null
+++ b/library/src/se/emilsjolander/stickylistheaders/ExpandableStickyListHeadersListView.java
@@ -0,0 +1,126 @@
+package se.emilsjolander.stickylistheaders;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+
+import java.util.List;
+
+/**
+ * add expand/collapse functions like ExpandableListView
+ * @author lsjwzh
+ */
+public class ExpandableStickyListHeadersListView extends StickyListHeadersListView {
+ public interface IAnimationExecutor{
+ public void executeAnim(View target,int animType);
+ }
+
+ public final static int ANIMATION_COLLAPSE = 1;
+ public final static int ANIMATION_EXPAND = 0;
+
+ ExpandableStickyListHeadersAdapter mExpandableStickyListHeadersAdapter;
+
+
+
+ IAnimationExecutor mDefaultAnimExecutor = new IAnimationExecutor() {
+ @Override
+ public void executeAnim(View target, int animType) {
+ if(animType==ANIMATION_EXPAND){
+ target.setVisibility(VISIBLE);
+ }else if(animType==ANIMATION_COLLAPSE){
+ target.setVisibility(GONE);
+ }
+ }
+ };
+
+
+ public ExpandableStickyListHeadersListView(Context context) {
+ super(context);
+ }
+
+ public ExpandableStickyListHeadersListView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public ExpandableStickyListHeadersListView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ @Override
+ public ExpandableStickyListHeadersAdapter getAdapter() {
+ return mExpandableStickyListHeadersAdapter;
+ }
+
+ @Override
+ public void setAdapter(StickyListHeadersAdapter adapter) {
+ mExpandableStickyListHeadersAdapter = new ExpandableStickyListHeadersAdapter(adapter);
+ super.setAdapter(mExpandableStickyListHeadersAdapter);
+ }
+
+ public View findViewByItemId(long itemId){
+ return mExpandableStickyListHeadersAdapter.findViewByItemId(itemId);
+ }
+
+ public long findItemIdByView(View view){
+ return mExpandableStickyListHeadersAdapter.findItemIdByView(view);
+ }
+
+ public void expand(long headerId) {
+ if(!mExpandableStickyListHeadersAdapter.isHeaderCollapsed(headerId)){
+ return;
+ }
+ mExpandableStickyListHeadersAdapter.expand(headerId);
+ //find and expand views in group
+ List itemViews = mExpandableStickyListHeadersAdapter.getItemViewsByHeaderId(headerId);
+ if(itemViews==null){
+ return;
+ }
+ for (View view : itemViews) {
+ animateView(view, ANIMATION_EXPAND);
+ }
+ }
+
+ public void collapse(long headerId) {
+ if(mExpandableStickyListHeadersAdapter.isHeaderCollapsed(headerId)){
+ return;
+ }
+ mExpandableStickyListHeadersAdapter.collapse(headerId);
+ //find and hide views with the same header
+ List itemViews = mExpandableStickyListHeadersAdapter.getItemViewsByHeaderId(headerId);
+ if(itemViews==null){
+ return;
+ }
+ for (View view : itemViews) {
+ animateView(view, ANIMATION_COLLAPSE);
+ }
+ }
+
+ public boolean isHeaderCollapsed(long headerId){
+ return mExpandableStickyListHeadersAdapter.isHeaderCollapsed(headerId);
+ }
+
+ public void setAnimExecutor(IAnimationExecutor animExecutor) {
+ this.mDefaultAnimExecutor = animExecutor;
+ }
+
+ /**
+ * Performs either COLLAPSE or EXPAND animation on the target view
+ *
+ * @param target the view to animate
+ * @param type the animation type, either ExpandCollapseAnimation.COLLAPSE
+ * or ExpandCollapseAnimation.EXPAND
+ */
+ private void animateView(final View target, final int type) {
+ if(ANIMATION_EXPAND==type&&target.getVisibility()==VISIBLE){
+ return;
+ }
+ if(ANIMATION_COLLAPSE==type&&target.getVisibility()!=VISIBLE){
+ return;
+ }
+ if(mDefaultAnimExecutor !=null){
+ mDefaultAnimExecutor.executeAnim(target,type);
+ }
+
+ }
+
+}
diff --git a/library/src/se/emilsjolander/stickylistheaders/StickyListHeadersListView.java b/library/src/se/emilsjolander/stickylistheaders/StickyListHeadersListView.java
index bffab541..7d3f9b57 100644
--- a/library/src/se/emilsjolander/stickylistheaders/StickyListHeadersListView.java
+++ b/library/src/se/emilsjolander/stickylistheaders/StickyListHeadersListView.java
@@ -10,7 +10,9 @@
import android.os.Build;
import android.os.Parcelable;
import android.util.AttributeSet;
+import android.util.Log;
import android.util.SparseBooleanArray;
+import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
@@ -25,17 +27,17 @@
import se.emilsjolander.stickylistheaders.WrapperViewList.LifeCycleListener;
/**
- * Even though this is a FrameLayout subclass we it is called a ListView. This
- * is because of 2 reasons. 1. It acts like as ListView 2. It used to be a
- * ListView subclass and i did not was to change to name causing compatibility
- * errors.
+ * Even though this is a FrameLayout subclass we still consider it a ListView.
+ * This is because of 2 reasons:
+ * 1. It acts like as ListView.
+ * 2. It used to be a ListView subclass and refactoring the name would cause compatibility errors.
*
* @author Emil Sjölander
*/
public class StickyListHeadersListView extends FrameLayout {
public interface OnHeaderClickListener {
- public void onHeaderClick(StickyListHeadersListView l, View header,
+ void onHeaderClick(StickyListHeadersListView l, View header,
int itemPosition, long headerId, boolean currentlySticky);
}
@@ -52,7 +54,23 @@ public interface OnStickyHeaderOffsetChangedListener {
* get* methods for determining the view's size.
* @param offset The amount the sticky header is offset by towards to top of the screen.
*/
- public void onStickyHeaderOffsetChanged(StickyListHeadersListView l, View header, int offset);
+ void onStickyHeaderOffsetChanged(StickyListHeadersListView l, View header, int offset);
+ }
+
+ /**
+ * Notifies the listener when the sticky header has been updated
+ */
+ public interface OnStickyHeaderChangedListener {
+ /**
+ * @param l The view parent
+ * @param header The new sticky header view.
+ * @param itemPosition The position of the item within the adapter's data set of
+ * the item whose header is now sticky.
+ * @param headerId The id of the new sticky header.
+ */
+ void onStickyHeaderChanged(StickyListHeadersListView l, View header,
+ int itemPosition, long headerId);
+
}
/* --- Children --- */
@@ -73,6 +91,7 @@ public interface OnStickyHeaderOffsetChangedListener {
private boolean mAreHeadersSticky = true;
private boolean mClippingToPadding = true;
private boolean mIsDrawingListUnderStickyHeader = true;
+ private int mStickyHeaderTopOffset = 0;
private int mPaddingLeft = 0;
private int mPaddingTop = 0;
private int mPaddingRight = 0;
@@ -81,6 +100,7 @@ public interface OnStickyHeaderOffsetChangedListener {
/* --- Other --- */
private OnHeaderClickListener mOnHeaderClickListener;
private OnStickyHeaderOffsetChangedListener mOnStickyHeaderOffsetChangedListener;
+ private OnStickyHeaderChangedListener mOnStickyHeaderChangedListener;
private AdapterWrapperDataSetObserver mDataSetObserver;
private Drawable mDivider;
private int mDividerHeight;
@@ -117,8 +137,7 @@ public StickyListHeadersListView(Context context, AttributeSet attrs, int defSty
mPaddingRight = a.getDimensionPixelSize(R.styleable.StickyListHeadersListView_android_paddingRight, padding);
mPaddingBottom = a.getDimensionPixelSize(R.styleable.StickyListHeadersListView_android_paddingBottom, padding);
- setPadding(mPaddingLeft, mPaddingTop, mPaddingRight,
- mPaddingBottom);
+ setPadding(mPaddingLeft, mPaddingTop, mPaddingRight, mPaddingBottom);
// Set clip to padding on the list and reset value to default on
// wrapper
@@ -132,7 +151,9 @@ public StickyListHeadersListView(Context context, AttributeSet attrs, int defSty
mList.setHorizontalScrollBarEnabled((scrollBars & 0x00000100) != 0);
// overscroll
- mList.setOverScrollMode(a.getInt(R.styleable.StickyListHeadersListView_android_overScrollMode, 0));
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
+ mList.setOverScrollMode(a.getInt(R.styleable.StickyListHeadersListView_android_overScrollMode, 0));
+ }
// -- ListView attributes --
mList.setFadingEdgeLength(a.getDimensionPixelSize(R.styleable.StickyListHeadersListView_android_fadingEdgeLength,
@@ -179,6 +200,9 @@ public StickyListHeadersListView(Context context, AttributeSet attrs, int defSty
mDividerHeight = a.getDimensionPixelSize(R.styleable.StickyListHeadersListView_android_dividerHeight,
mDividerHeight);
+ mList.setTranscriptMode(a.getInt(R.styleable.StickyListHeadersListView_android_transcriptMode,
+ ListView.TRANSCRIPT_MODE_DISABLED));
+
// -- StickyListHeaders attributes --
mAreHeadersSticky = a.getBoolean(R.styleable.StickyListHeadersListView_hasStickyHeaders, true);
mIsDrawingListUnderStickyHeader = a.getBoolean(
@@ -206,10 +230,12 @@ private void ensureHeaderHasCorrectLayoutParams(View header) {
ViewGroup.LayoutParams lp = header.getLayoutParams();
if (lp == null) {
lp = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
- } else if (lp.height == LayoutParams.MATCH_PARENT) {
+ header.setLayoutParams(lp);
+ } else if (lp.height == LayoutParams.MATCH_PARENT || lp.width == LayoutParams.WRAP_CONTENT) {
lp.height = LayoutParams.WRAP_CONTENT;
+ lp.width = LayoutParams.MATCH_PARENT;
+ header.setLayoutParams(lp);
}
- header.setLayoutParams(lp);
}
private void measureHeader(View header) {
@@ -225,16 +251,11 @@ private void measureHeader(View header) {
}
@Override
- protected void onLayout(boolean changed, int left, int top, int right,
- int bottom) {
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
mList.layout(0, 0, mList.getMeasuredWidth(), getHeight());
if (mHeader != null) {
- MarginLayoutParams lp = (MarginLayoutParams) mHeader
- .getLayoutParams();
- int headerTop = lp.topMargin
- + (mClippingToPadding ? mPaddingTop : 0);
- // The left parameter must for some reason be set to 0.
- // I think it should be set to mPaddingLeft but apparently not
+ MarginLayoutParams lp = (MarginLayoutParams) mHeader.getLayoutParams();
+ int headerTop = lp.topMargin + stickyHeaderTop();
mHeader.layout(mPaddingLeft, headerTop, mHeader.getMeasuredWidth()
+ mPaddingLeft, headerTop + mHeader.getMeasuredHeight());
}
@@ -246,7 +267,9 @@ protected void dispatchDraw(Canvas canvas) {
// The header should be drawn right after the lists children are drawn.
// This is done so that the header is above the list items
// but below the list decorators (scroll bars etc).
- drawChild(canvas, mList, 0);
+ if (mList.getVisibility() == VISIBLE || mList.getAnimation() != null) {
+ drawChild(canvas, mList, 0);
+ }
}
// Reset values tied the header. also remove header form layout
@@ -272,46 +295,51 @@ private void updateOrClearHeader(int firstVisiblePosition) {
}
final int headerViewCount = mList.getHeaderViewsCount();
- final int realFirstVisibleItem = firstVisiblePosition - headerViewCount;
+ int headerPosition = firstVisiblePosition - headerViewCount;
+ if (mList.getChildCount() > 0) {
+ View firstItem = mList.getChildAt(0);
+ if (firstItem.getBottom() < stickyHeaderTop()) {
+ headerPosition++;
+ }
+ }
// It is not a mistake to call getFirstVisiblePosition() here.
// Most of the time getFixedFirstVisibleItem() should be called
// but that does not work great together with getChildAt()
final boolean doesListHaveChildren = mList.getChildCount() != 0;
- final boolean isFirstViewBelowTop = doesListHaveChildren && mList
- .getFirstVisiblePosition() == 0
- && mList.getChildAt(0).getTop() > (mClippingToPadding ? mPaddingTop : 0);
- final boolean isFirstVisibleItemOutsideAdapterRange = realFirstVisibleItem > adapterCount - 1
- || realFirstVisibleItem < 0;
- if (!doesListHaveChildren || isFirstVisibleItemOutsideAdapterRange
- || isFirstViewBelowTop) {
+ final boolean isFirstViewBelowTop = doesListHaveChildren
+ && mList.getFirstVisiblePosition() == 0
+ && mList.getChildAt(0).getTop() >= stickyHeaderTop();
+ final boolean isHeaderPositionOutsideAdapterRange = headerPosition > adapterCount - 1
+ || headerPosition < 0;
+ if (!doesListHaveChildren || isHeaderPositionOutsideAdapterRange || isFirstViewBelowTop) {
clearHeader();
return;
}
- updateHeader(realFirstVisibleItem);
+ updateHeader(headerPosition);
}
- private void updateHeader(int firstVisiblePosition) {
+ private void updateHeader(int headerPosition) {
// check if there is a new header should be sticky
- if (mHeaderPosition == null || mHeaderPosition != firstVisiblePosition) {
- mHeaderPosition = firstVisiblePosition;
- final long headerId = mAdapter.getHeaderId(firstVisiblePosition);
+ if (mHeaderPosition == null || mHeaderPosition != headerPosition) {
+ mHeaderPosition = headerPosition;
+ final long headerId = mAdapter.getHeaderId(headerPosition);
if (mHeaderId == null || mHeaderId != headerId) {
mHeaderId = headerId;
- final View header = mAdapter.getHeaderView(mHeaderPosition,
- mHeader, this);
+ final View header = mAdapter.getHeaderView(mHeaderPosition, mHeader, this);
if (mHeader != header) {
if (header == null) {
throw new NullPointerException("header may not be null");
}
swapHeader(header);
}
-
ensureHeaderHasCorrectLayoutParams(mHeader);
measureHeader(mHeader);
-
+ if(mOnStickyHeaderChangedListener != null) {
+ mOnStickyHeaderChangedListener.onStickyHeaderChanged(this, mHeader, headerPosition, mHeaderId);
+ }
// Reset mHeaderOffset to null ensuring
// that it will be set on the header and
// not skipped for performance reasons.
@@ -324,15 +352,12 @@ private void updateHeader(int firstVisiblePosition) {
// Calculate new header offset
// Skip looking at the first view. it never matters because it always
// results in a headerOffset = 0
- int headerBottom = mHeader.getMeasuredHeight()
- + (mClippingToPadding ? mPaddingTop : 0);
+ int headerBottom = mHeader.getMeasuredHeight() + stickyHeaderTop();
for (int i = 0; i < mList.getChildCount(); i++) {
final View child = mList.getChildAt(i);
- final boolean doesChildHaveHeader = child instanceof WrapperView
- && ((WrapperView) child).hasHeader();
+ final boolean doesChildHaveHeader = child instanceof WrapperView && ((WrapperView) child).hasHeader();
final boolean isChildFooter = mList.containsFooterView(child);
- if (child.getTop() >= (mClippingToPadding ? mPaddingTop : 0)
- && (doesChildHaveHeader || isChildFooter)) {
+ if (child.getTop() >= stickyHeaderTop() && (doesChildHaveHeader || isChildFooter)) {
headerOffset = Math.min(child.getTop() - headerBottom, 0);
break;
}
@@ -354,18 +379,17 @@ private void swapHeader(View newHeader) {
}
mHeader = newHeader;
addView(mHeader);
- mHeader.setOnClickListener(new OnClickListener() {
-
- @Override
- public void onClick(View v) {
- if (mOnHeaderClickListener != null) {
+ if (mOnHeaderClickListener != null) {
+ mHeader.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
mOnHeaderClickListener.onHeaderClick(
StickyListHeadersListView.this, mHeader,
mHeaderPosition, mHeaderId, true);
}
- }
-
- });
+ });
+ }
+ mHeader.setClickable(true);
}
// hides the headers in the list under the sticky header.
@@ -373,10 +397,9 @@ public void onClick(View v) {
private void updateHeaderVisibilities() {
int top;
if (mHeader != null) {
- top = mHeader.getMeasuredHeight()
- + (mHeaderOffset != null ? mHeaderOffset : 0);
+ top = mHeader.getMeasuredHeight() + (mHeaderOffset != null ? mHeaderOffset : 0) + mStickyHeaderTopOffset;
} else {
- top = mClippingToPadding ? mPaddingTop : 0;
+ top = stickyHeaderTop();
}
int childCount = mList.getChildCount();
for (int i = 0; i < childCount; i++) {
@@ -416,14 +439,12 @@ private void setHeaderOffet(int offset) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
mHeader.setTranslationY(mHeaderOffset);
} else {
- MarginLayoutParams params = (MarginLayoutParams) mHeader
- .getLayoutParams();
+ MarginLayoutParams params = (MarginLayoutParams) mHeader.getLayoutParams();
params.topMargin = mHeaderOffset;
mHeader.setLayoutParams(params);
}
if (mOnStickyHeaderOffsetChangedListener != null) {
- mOnStickyHeaderOffsetChangedListener
- .onStickyHeaderOffsetChanged(this, mHeader, -mHeaderOffset);
+ mOnStickyHeaderOffsetChangedListener.onStickyHeaderOffsetChanged(this, mHeader, -mHeaderOffset);
}
}
}
@@ -500,13 +521,11 @@ public void onHeaderClick(View header, int itemPosition, long headerId) {
}
private boolean isStartOfSection(int position) {
- return position == 0
- || mAdapter.getHeaderId(position) != mAdapter
- .getHeaderId(position - 1);
+ return position == 0 || mAdapter.getHeaderId(position) != mAdapter.getHeaderId(position - 1);
}
- private int getHeaderOverlap(int position) {
- boolean isStartOfSection = isStartOfSection(position);
+ public int getHeaderOverlap(int position) {
+ boolean isStartOfSection = isStartOfSection(Math.max(0, position - getHeaderViewsCount()));
if (!isStartOfSection) {
View header = mAdapter.getHeaderView(position, null, mList);
if (header == null) {
@@ -519,7 +538,11 @@ private int getHeaderOverlap(int position) {
return 0;
}
- /* ---------- StickyListHeaders specific API ---------- */
+ private int stickyHeaderTop() {
+ return mStickyHeaderTopOffset + (mClippingToPadding ? mPaddingTop : 0);
+ }
+
+ /* ---------- StickyListHeaders specific API ---------- */
public void setAreHeadersSticky(boolean areHeadersSticky) {
mAreHeadersSticky = areHeadersSticky;
@@ -544,6 +567,20 @@ public boolean getAreHeadersSticky() {
return areHeadersSticky();
}
+ /**
+ *
+ * @param stickyHeaderTopOffset
+ * The offset of the sticky header fom the top of the view
+ */
+ public void setStickyHeaderTopOffset(int stickyHeaderTopOffset) {
+ mStickyHeaderTopOffset = stickyHeaderTopOffset;
+ updateOrClearHeader(mList.getFixedFirstVisibleItem());
+ }
+
+ public int getStickyHeaderTopOffset() {
+ return mStickyHeaderTopOffset;
+ }
+
public void setDrawingListUnderStickyHeader(
boolean drawingListUnderStickyHeader) {
mIsDrawingListUnderStickyHeader = drawingListUnderStickyHeader;
@@ -560,6 +597,17 @@ public void setOnHeaderClickListener(OnHeaderClickListener listener) {
if (mAdapter != null) {
if (mOnHeaderClickListener != null) {
mAdapter.setOnHeaderClickListener(new AdapterWrapperHeaderClickHandler());
+
+ if (mHeader != null) {
+ mHeader.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mOnHeaderClickListener.onHeaderClick(
+ StickyListHeadersListView.this, mHeader,
+ mHeaderPosition, mHeaderId, true);
+ }
+ });
+ }
} else {
mAdapter.setOnHeaderClickListener(null);
}
@@ -570,6 +618,10 @@ public void setOnStickyHeaderOffsetChangedListener(OnStickyHeaderOffsetChangedLi
mOnStickyHeaderOffsetChangedListener = listener;
}
+ public void setOnStickyHeaderChangedListener(OnStickyHeaderChangedListener listener) {
+ mOnStickyHeaderChangedListener = listener;
+ }
+
public View getListChildAt(int index) {
return mList.getChildAt(index);
}
@@ -588,6 +640,14 @@ public ListView getWrappedList() {
return mList;
}
+ private boolean requireSdkVersion(int versionCode) {
+ if (Build.VERSION.SDK_INT < versionCode) {
+ Log.e("StickyListHeaders", "Api lvl must be at least "+versionCode+" to call this method");
+ return false;
+ }
+ return true;
+ }
+
/* ---------- ListView delegate methods ---------- */
public void setAdapter(StickyListHeadersAdapter adapter) {
@@ -650,6 +710,20 @@ public void setOnScrollListener(OnScrollListener onScrollListener) {
mOnScrollListenerDelegate = onScrollListener;
}
+ @Override
+ public void setOnTouchListener(final OnTouchListener l) {
+ if (l != null) {
+ mList.setOnTouchListener(new OnTouchListener() {
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ return l.onTouch(StickyListHeadersListView.this, event);
+ }
+ });
+ } else {
+ mList.setOnTouchListener(null);
+ }
+ }
+
public void setOnItemClickListener(OnItemClickListener listener) {
mList.setOnItemClickListener(listener);
}
@@ -673,6 +747,10 @@ public void removeHeaderView(View v) {
public int getHeaderViewsCount() {
return mList.getHeaderViewsCount();
}
+
+ public void addFooterView(View v, Object data, boolean isSelectable) {
+ mList.addFooterView(v, data, isSelectable);
+ }
public void addFooterView(View v) {
mList.addFooterView(v);
@@ -717,64 +795,74 @@ public void setHorizontalScrollBarEnabled(boolean horizontalScrollBarEnabled) {
@Override
@TargetApi(Build.VERSION_CODES.GINGERBREAD)
public int getOverScrollMode() {
- requireSdkVersion(Build.VERSION_CODES.GINGERBREAD);
- return mList.getOverScrollMode();
+ if (requireSdkVersion(Build.VERSION_CODES.GINGERBREAD)) {
+ return mList.getOverScrollMode();
+ }
+ return 0;
}
@Override
@TargetApi(Build.VERSION_CODES.GINGERBREAD)
public void setOverScrollMode(int mode) {
- requireSdkVersion(Build.VERSION_CODES.GINGERBREAD);
- if (mList != null) {
- mList.setOverScrollMode(mode);
+ if (requireSdkVersion(Build.VERSION_CODES.GINGERBREAD)) {
+ if (mList != null) {
+ mList.setOverScrollMode(mode);
+ }
}
}
@TargetApi(Build.VERSION_CODES.FROYO)
public void smoothScrollBy(int distance, int duration) {
- requireSdkVersion(Build.VERSION_CODES.FROYO);
- mList.smoothScrollBy(distance, duration);
+ if (requireSdkVersion(Build.VERSION_CODES.FROYO)) {
+ mList.smoothScrollBy(distance, duration);
+ }
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public void smoothScrollByOffset(int offset) {
- requireSdkVersion(Build.VERSION_CODES.HONEYCOMB);
- mList.smoothScrollByOffset(offset);
+ if (requireSdkVersion(Build.VERSION_CODES.HONEYCOMB)) {
+ mList.smoothScrollByOffset(offset);
+ }
}
@SuppressLint("NewApi")
@TargetApi(Build.VERSION_CODES.FROYO)
public void smoothScrollToPosition(int position) {
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
- mList.smoothScrollToPosition(position);
- } else {
- int offset = mAdapter == null ? 0 : getHeaderOverlap(position);
- offset -= mClippingToPadding ? 0 : mPaddingTop;
- mList.smoothScrollToPositionFromTop(position, offset);
+ if (requireSdkVersion(Build.VERSION_CODES.FROYO)) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
+ mList.smoothScrollToPosition(position);
+ } else {
+ int offset = mAdapter == null ? 0 : getHeaderOverlap(position);
+ offset -= mClippingToPadding ? 0 : mPaddingTop;
+ mList.smoothScrollToPositionFromTop(position, offset);
+ }
}
}
@TargetApi(Build.VERSION_CODES.FROYO)
public void smoothScrollToPosition(int position, int boundPosition) {
- requireSdkVersion(Build.VERSION_CODES.FROYO);
- mList.smoothScrollToPosition(position, boundPosition);
+ if (requireSdkVersion(Build.VERSION_CODES.FROYO)) {
+ mList.smoothScrollToPosition(position, boundPosition);
+ }
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public void smoothScrollToPositionFromTop(int position, int offset) {
- requireSdkVersion(Build.VERSION_CODES.HONEYCOMB);
- offset += mAdapter == null ? 0 : getHeaderOverlap(position);
- offset -= mClippingToPadding ? 0 : mPaddingTop;
- mList.smoothScrollToPositionFromTop(position, offset);
+ if (requireSdkVersion(Build.VERSION_CODES.HONEYCOMB)) {
+ offset += mAdapter == null ? 0 : getHeaderOverlap(position);
+ offset -= mClippingToPadding ? 0 : mPaddingTop;
+ mList.smoothScrollToPositionFromTop(position, offset);
+ }
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public void smoothScrollToPositionFromTop(int position, int offset,
int duration) {
- requireSdkVersion(Build.VERSION_CODES.HONEYCOMB);
- offset += mAdapter == null ? 0 : getHeaderOverlap(position);
- offset -= mClippingToPadding ? 0 : mPaddingTop;
- mList.smoothScrollToPositionFromTop(position, offset, duration);
+ if (requireSdkVersion(Build.VERSION_CODES.HONEYCOMB)) {
+ offset += mAdapter == null ? 0 : getHeaderOverlap(position);
+ offset -= mClippingToPadding ? 0 : mPaddingTop;
+ mList.smoothScrollToPositionFromTop(position, offset, duration);
+ }
}
public void setSelection(int position) {
@@ -807,30 +895,38 @@ public int getLastVisiblePosition() {
return mList.getLastVisiblePosition();
}
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
public void setChoiceMode(int choiceMode) {
mList.setChoiceMode(choiceMode);
}
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
public void setItemChecked(int position, boolean value) {
mList.setItemChecked(position, value);
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public int getCheckedItemCount() {
- requireSdkVersion(Build.VERSION_CODES.HONEYCOMB);
- return mList.getCheckedItemCount();
+ if (requireSdkVersion(Build.VERSION_CODES.HONEYCOMB)) {
+ return mList.getCheckedItemCount();
+ }
+ return 0;
}
@TargetApi(Build.VERSION_CODES.FROYO)
public long[] getCheckedItemIds() {
- requireSdkVersion(Build.VERSION_CODES.FROYO);
- return mList.getCheckedItemIds();
+ if (requireSdkVersion(Build.VERSION_CODES.FROYO)) {
+ return mList.getCheckedItemIds();
+ }
+ return null;
}
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
public int getCheckedItemPosition() {
return mList.getCheckedItemPosition();
}
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
public SparseBooleanArray getCheckedItemPositions() {
return mList.getCheckedItemPositions();
}
@@ -914,14 +1010,11 @@ public void setFastScrollEnabled(boolean fastScrollEnabled) {
mList.setFastScrollEnabled(fastScrollEnabled);
}
- /**
- * @throws ApiLevelTooLowException on pre-Honeycomb device.
- * @see android.widget.AbsListView#setFastScrollAlwaysVisible(boolean)
- */
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public void setFastScrollAlwaysVisible(boolean alwaysVisible) {
- requireSdkVersion(Build.VERSION_CODES.HONEYCOMB);
- mList.setFastScrollAlwaysVisible(alwaysVisible);
+ if (requireSdkVersion(Build.VERSION_CODES.HONEYCOMB)) {
+ mList.setFastScrollAlwaysVisible(alwaysVisible);
+ }
}
/**
@@ -944,14 +1037,44 @@ public int getScrollBarStyle() {
return mList.getScrollBarStyle();
}
- private void requireSdkVersion(int versionCode) {
- if (Build.VERSION.SDK_INT < versionCode) {
- throw new ApiLevelTooLowException(versionCode);
+ public int getPositionForView(View view) {
+ return mList.getPositionForView(view);
+ }
+
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ public void setMultiChoiceModeListener(MultiChoiceModeListener listener) {
+ if (requireSdkVersion(Build.VERSION_CODES.HONEYCOMB)) {
+ mList.setMultiChoiceModeListener(listener);
}
}
- public int getPositionForView(View view) {
- return mList.getPositionForView(view);
+ @Override
+ public Parcelable onSaveInstanceState() {
+ Parcelable superState = super.onSaveInstanceState();
+ if (superState != BaseSavedState.EMPTY_STATE) {
+ throw new IllegalStateException("Handling non empty state of parent class is not implemented");
+ }
+ return mList.onSaveInstanceState();
+ }
+
+ @Override
+ public void onRestoreInstanceState(Parcelable state) {
+ super.onRestoreInstanceState(BaseSavedState.EMPTY_STATE);
+ mList.onRestoreInstanceState(state);
+ }
+
+ @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
+ @Override
+ public boolean canScrollVertically(int direction) {
+ return mList.canScrollVertically(direction);
+ }
+
+ public void setTranscriptMode (int mode) {
+ mList.setTranscriptMode(mode);
+ }
+
+ public void setBlockLayoutChildren(boolean blockLayoutChildren) {
+ mList.setBlockLayoutChildren(blockLayoutChildren);
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
diff --git a/library/src/se/emilsjolander/stickylistheaders/WrapperView.java b/library/src/se/emilsjolander/stickylistheaders/WrapperView.java
index f51416c1..6ef8f11f 100644
--- a/library/src/se/emilsjolander/stickylistheaders/WrapperView.java
+++ b/library/src/se/emilsjolander/stickylistheaders/WrapperView.java
@@ -94,20 +94,26 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
}
measuredHeight += mHeader.getMeasuredHeight();
- } else if (mDivider != null) {
+ } else if (mDivider != null&&mItem.getVisibility()!=View.GONE) {
measuredHeight += mDividerHeight;
}
//measure item
ViewGroup.LayoutParams params = mItem.getLayoutParams();
- if (params != null && params.height > 0) {
+ //enable hiding listview item,ex. toggle off items in group
+ if(mItem.getVisibility()==View.GONE){
+ mItem.measure(childWidthMeasureSpec,
+ MeasureSpec.makeMeasureSpec(0, MeasureSpec.EXACTLY));
+ }else if (params != null && params.height >= 0) {
mItem.measure(childWidthMeasureSpec,
MeasureSpec.makeMeasureSpec(params.height, MeasureSpec.EXACTLY));
+ measuredHeight += mItem.getMeasuredHeight();
} else {
mItem.measure(childWidthMeasureSpec,
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
+ measuredHeight += mItem.getMeasuredHeight();
}
- measuredHeight += mItem.getMeasuredHeight();
+
setMeasuredDimension(measuredWidth, measuredHeight);
}
@@ -138,7 +144,7 @@ protected void onLayout(boolean changed, int l, int t, int r, int b) {
@Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
- if (mHeader == null && mDivider != null) {
+ if (mHeader == null && mDivider != null&&mItem.getVisibility()!=View.GONE) {
// Drawable.setBounds() does not seem to work pre-honeycomb. So have
// to do this instead
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
diff --git a/library/src/se/emilsjolander/stickylistheaders/WrapperViewList.java b/library/src/se/emilsjolander/stickylistheaders/WrapperViewList.java
index 3d68e98d..fdf2b76a 100644
--- a/library/src/se/emilsjolander/stickylistheaders/WrapperViewList.java
+++ b/library/src/se/emilsjolander/stickylistheaders/WrapperViewList.java
@@ -24,6 +24,7 @@ interface LifeCycleListener {
private Rect mSelectorRect = new Rect();// for if reflection fails
private Field mSelectorPositionField;
private boolean mClippingToPadding = true;
+ private boolean mBlockLayoutChildren = false;
public WrapperViewList(Context context) {
super(context);
@@ -72,7 +73,7 @@ private void positionSelectorRect() {
private int getSelectorPosition() {
if (mSelectorPositionField == null) { // not all supported andorid
- // version have this variable
+ // version have this variable
for (int i = 0; i < getChildCount(); i++) {
if (getChildAt(i).getBottom() == mSelectorRect.bottom) {
return i + getFixedFirstVisibleItem();
@@ -113,6 +114,16 @@ void setLifeCycleListener(LifeCycleListener lifeCycleListener) {
@Override
public void addFooterView(View v) {
super.addFooterView(v);
+ addInternalFooterView(v);
+ }
+
+ @Override
+ public void addFooterView(View v, Object data, boolean isSelectable) {
+ super.addFooterView(v, data, isSelectable);
+ addInternalFooterView(v);
+ }
+
+ private void addInternalFooterView(View v) {
if (mFooterViews == null) {
mFooterViews = new ArrayList();
}
@@ -172,4 +183,14 @@ public void setClipToPadding(boolean clipToPadding) {
super.setClipToPadding(clipToPadding);
}
+ public void setBlockLayoutChildren(boolean block) {
+ mBlockLayoutChildren = block;
+ }
+
+ @Override
+ protected void layoutChildren() {
+ if (!mBlockLayoutChildren) {
+ super.layoutChildren();
+ }
+ }
}
diff --git a/sample/AndroidManifest.xml b/sample/AndroidManifest.xml
index 4bf61a44..54b2aa1b 100644
--- a/sample/AndroidManifest.xml
+++ b/sample/AndroidManifest.xml
@@ -1,5 +1,5 @@
@@ -11,7 +11,7 @@
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
- android:theme="@style/Theme.AppCompat.Light.DarkActionBar"
+ android:theme="@style/AppTheme"
android:supportsRtl="true">
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/sample/build.gradle b/sample/build.gradle
index b620008e..e5215fe8 100644
--- a/sample/build.gradle
+++ b/sample/build.gradle
@@ -6,12 +6,14 @@ repositories {
dependencies {
compile project(':library')
compile 'com.android.support:appcompat-v7:19.0.+'
- compile 'com.android.support:support-v4:19.0.0'
+ compile 'com.android.support:support-v4:19.1.+'
+ compile 'com.nineoldandroids:library:2.4.0+'
+
}
android {
compileSdkVersion 19
- buildToolsVersion '19.0.0'
+ buildToolsVersion '19.1.0'
sourceSets {
main {
diff --git a/sample/res/layout/expandable_sample.xml b/sample/res/layout/expandable_sample.xml
new file mode 100644
index 00000000..414c58df
--- /dev/null
+++ b/sample/res/layout/expandable_sample.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/sample/res/layout/header.xml b/sample/res/layout/header.xml
index 5530b530..319a24bb 100644
--- a/sample/res/layout/header.xml
+++ b/sample/res/layout/header.xml
@@ -4,7 +4,7 @@
android:layout_height="wrap_content"
android:background="@drawable/header_selector" >
-
-
+
+
+ android:overScrollMode="never"/>
+
+
-
-
+
- OPTIONS
ACTIONS
Restore list
+ Open expandable list
Update list
Clear list
Sticky header
diff --git a/sample/res/values/style.xml b/sample/res/values/styles.xml
similarity index 78%
rename from sample/res/values/style.xml
rename to sample/res/values/styles.xml
index b34634b5..36397410 100644
--- a/sample/res/values/style.xml
+++ b/sample/res/values/styles.xml
@@ -1,9 +1,15 @@
+
+
+
+
\ No newline at end of file
diff --git a/sample/src/se/emilsjolander/stickylistheaders/sample/ExpandableListTestActivity.java b/sample/src/se/emilsjolander/stickylistheaders/sample/ExpandableListTestActivity.java
new file mode 100644
index 00000000..dd12d14e
--- /dev/null
+++ b/sample/src/se/emilsjolander/stickylistheaders/sample/ExpandableListTestActivity.java
@@ -0,0 +1,103 @@
+package se.emilsjolander.stickylistheaders.sample;
+import com.nineoldandroids.animation.ValueAnimator;
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.nineoldandroids.animation.Animator;
+
+import java.util.WeakHashMap;
+
+import se.emilsjolander.stickylistheaders.ExpandableStickyListHeadersListView;
+import se.emilsjolander.stickylistheaders.StickyListHeadersListView;
+
+/**
+ * @author lsjwzh
+ */
+public class ExpandableListTestActivity extends Activity {
+
+ private ExpandableStickyListHeadersListView mListView;
+ TestBaseAdapter mTestBaseAdapter;
+ WeakHashMap mOriginalViewHeightPool = new WeakHashMap();
+
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.expandable_sample);
+ mListView = (ExpandableStickyListHeadersListView) findViewById(R.id.list);
+ //custom expand/collapse animation
+ mListView.setAnimExecutor(new AnimationExecutor());
+ mTestBaseAdapter = new TestBaseAdapter(this);
+ mListView.setAdapter(mTestBaseAdapter);
+ mListView.setOnHeaderClickListener(new StickyListHeadersListView.OnHeaderClickListener() {
+ @Override
+ public void onHeaderClick(StickyListHeadersListView l, View header, int itemPosition, long headerId, boolean currentlySticky) {
+ if(mListView.isHeaderCollapsed(headerId)){
+ mListView.expand(headerId);
+ }else {
+ mListView.collapse(headerId);
+ }
+ }
+ });
+ }
+ //animation executor
+ class AnimationExecutor implements ExpandableStickyListHeadersListView.IAnimationExecutor {
+
+ @Override
+ public void executeAnim(final View target, final int animType) {
+ if(ExpandableStickyListHeadersListView.ANIMATION_EXPAND==animType&&target.getVisibility()==View.VISIBLE){
+ return;
+ }
+ if(ExpandableStickyListHeadersListView.ANIMATION_COLLAPSE==animType&&target.getVisibility()!=View.VISIBLE){
+ return;
+ }
+ if(mOriginalViewHeightPool.get(target)==null){
+ mOriginalViewHeightPool.put(target,target.getHeight());
+ }
+ final int viewHeight = mOriginalViewHeightPool.get(target);
+ float animStartY = animType == ExpandableStickyListHeadersListView.ANIMATION_EXPAND ? 0f : viewHeight;
+ float animEndY = animType == ExpandableStickyListHeadersListView.ANIMATION_EXPAND ? viewHeight : 0f;
+ final ViewGroup.LayoutParams lp = target.getLayoutParams();
+ ValueAnimator animator = ValueAnimator.ofFloat(animStartY, animEndY);
+ animator.setDuration(200);
+ target.setVisibility(View.VISIBLE);
+ animator.addListener(new Animator.AnimatorListener() {
+ @Override
+ public void onAnimationStart(Animator animator) {
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animator) {
+ if (animType == ExpandableStickyListHeadersListView.ANIMATION_EXPAND) {
+ target.setVisibility(View.VISIBLE);
+ } else {
+ target.setVisibility(View.GONE);
+ }
+ target.getLayoutParams().height = viewHeight;
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animator) {
+
+ }
+
+ @Override
+ public void onAnimationRepeat(Animator animator) {
+
+ }
+ });
+ animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator valueAnimator) {
+ lp.height = ((Float) valueAnimator.getAnimatedValue()).intValue();
+ target.setLayoutParams(lp);
+ target.requestLayout();
+ }
+ });
+ animator.start();
+
+ }
+ }
+}
diff --git a/sample/src/se/emilsjolander/stickylistheaders/TestActivity.java b/sample/src/se/emilsjolander/stickylistheaders/sample/TestActivity.java
similarity index 75%
rename from sample/src/se/emilsjolander/stickylistheaders/TestActivity.java
rename to sample/src/se/emilsjolander/stickylistheaders/sample/TestActivity.java
index 2298daf9..0ac6041e 100644
--- a/sample/src/se/emilsjolander/stickylistheaders/TestActivity.java
+++ b/sample/src/se/emilsjolander/stickylistheaders/sample/TestActivity.java
@@ -1,22 +1,32 @@
-package se.emilsjolander.stickylistheaders;
+package se.emilsjolander.stickylistheaders.sample;
import android.annotation.TargetApi;
+import android.content.Intent;
import android.content.res.Configuration;
import android.os.Build;
import android.os.Bundle;
+import android.os.Handler;
import android.support.v4.app.ActionBarDrawerToggle;
import android.support.v4.widget.DrawerLayout;
+import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.app.ActionBarActivity;
import android.view.MenuItem;
import android.view.View;
-import android.widget.*;
+import android.widget.AdapterView;
+import android.widget.Button;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.Toast;
+
+import se.emilsjolander.stickylistheaders.StickyListHeadersListView;
/**
* @author Emil Sjölander
*/
public class TestActivity extends ActionBarActivity implements
AdapterView.OnItemClickListener, StickyListHeadersListView.OnHeaderClickListener,
- StickyListHeadersListView.OnStickyHeaderOffsetChangedListener {
+ StickyListHeadersListView.OnStickyHeaderOffsetChangedListener,
+ StickyListHeadersListView.OnStickyHeaderChangedListener {
private TestBaseAdapter mAdapter;
private DrawerLayout mDrawerLayout;
@@ -24,6 +34,7 @@ public class TestActivity extends ActionBarActivity implements
private boolean fadeHeader = true;
private StickyListHeadersListView stickyList;
+ private SwipeRefreshLayout refreshLayout;
private Button restoreButton;
private Button updateButton;
@@ -33,17 +44,32 @@ public class TestActivity extends ActionBarActivity implements
private CheckBox fadeCheckBox;
private CheckBox drawBehindCheckBox;
private CheckBox fastScrollCheckBox;
+ private Button openExpandableListButton;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
+ refreshLayout = (SwipeRefreshLayout) findViewById(R.id.refresh_layout);
+ refreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
+ @Override
+ public void onRefresh() {
+ new Handler().postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ refreshLayout.setRefreshing(false);
+ }
+ }, 1000);
+ }
+ });
+
mAdapter = new TestBaseAdapter(this);
stickyList = (StickyListHeadersListView) findViewById(R.id.list);
stickyList.setOnItemClickListener(this);
stickyList.setOnHeaderClickListener(this);
+ stickyList.setOnStickyHeaderChangedListener(this);
stickyList.setOnStickyHeaderOffsetChangedListener(this);
stickyList.addHeaderView(getLayoutInflater().inflate(R.layout.list_header, null));
stickyList.addFooterView(getLayoutInflater().inflate(R.layout.list_footer, null));
@@ -69,6 +95,8 @@ public void onCreate(Bundle savedInstanceState) {
restoreButton = (Button) findViewById(R.id.restore_button);
restoreButton.setOnClickListener(buttonListener);
+ openExpandableListButton = (Button) findViewById(R.id.open_expandable_list_button);
+ openExpandableListButton.setOnClickListener(buttonListener);
updateButton = (Button) findViewById(R.id.update_button);
updateButton.setOnClickListener(buttonListener);
clearButton = (Button) findViewById(R.id.clear_button);
@@ -82,6 +110,8 @@ public void onCreate(Bundle savedInstanceState) {
drawBehindCheckBox.setOnCheckedChangeListener(checkBoxListener);
fastScrollCheckBox = (CheckBox) findViewById(R.id.fast_scroll_checkBox);
fastScrollCheckBox.setOnCheckedChangeListener(checkBoxListener);
+
+ stickyList.setStickyHeaderTopOffset(-20);
}
@Override
@@ -102,7 +132,6 @@ public boolean onOptionsItemSelected(MenuItem item) {
if (mDrawerToggle.onOptionsItemSelected(item)) {
return true;
}
-
return super.onOptionsItemSelected(item);
}
@@ -141,22 +170,22 @@ public void onClick(View view) {
case R.id.clear_button:
mAdapter.clear();
break;
+ case R.id.open_expandable_list_button:
+ Intent intent = new Intent(TestActivity.this,ExpandableListTestActivity.class);
+ startActivity(intent);
+ break;
}
}
};
@Override
- public void onItemClick(AdapterView> parent, View view, int position,
- long id) {
- Toast.makeText(this, "Item " + position + " clicked!",
- Toast.LENGTH_SHORT).show();
+ public void onItemClick(AdapterView> parent, View view, int position, long id) {
+ Toast.makeText(this, "Item " + position + " clicked!", Toast.LENGTH_SHORT).show();
}
@Override
- public void onHeaderClick(StickyListHeadersListView l, View header,
- int itemPosition, long headerId, boolean currentlySticky) {
- Toast.makeText(this, "Header " + headerId + " currentlySticky ? " + currentlySticky,
- Toast.LENGTH_SHORT).show();
+ public void onHeaderClick(StickyListHeadersListView l, View header, int itemPosition, long headerId, boolean currentlySticky) {
+ Toast.makeText(this, "Header " + headerId + " currentlySticky ? " + currentlySticky, Toast.LENGTH_SHORT).show();
}
@Override
@@ -166,4 +195,11 @@ public void onStickyHeaderOffsetChanged(StickyListHeadersListView l, View header
header.setAlpha(1 - (offset / (float) header.getMeasuredHeight()));
}
}
+
+ @Override
+ @TargetApi(Build.VERSION_CODES.HONEYCOMB)
+ public void onStickyHeaderChanged(StickyListHeadersListView l, View header, int itemPosition, long headerId) {
+ header.setAlpha(1);
+ }
+
}
\ No newline at end of file
diff --git a/sample/src/se/emilsjolander/stickylistheaders/TestBaseAdapter.java b/sample/src/se/emilsjolander/stickylistheaders/sample/TestBaseAdapter.java
similarity index 95%
rename from sample/src/se/emilsjolander/stickylistheaders/TestBaseAdapter.java
rename to sample/src/se/emilsjolander/stickylistheaders/sample/TestBaseAdapter.java
index 777b7bd5..7505d03b 100644
--- a/sample/src/se/emilsjolander/stickylistheaders/TestBaseAdapter.java
+++ b/sample/src/se/emilsjolander/stickylistheaders/sample/TestBaseAdapter.java
@@ -1,6 +1,4 @@
-package se.emilsjolander.stickylistheaders;
-
-import java.util.ArrayList;
+package se.emilsjolander.stickylistheaders.sample;
import android.content.Context;
import android.view.LayoutInflater;
@@ -10,9 +8,10 @@
import android.widget.SectionIndexer;
import android.widget.TextView;
-/**
- * @author Emil Sjölander
- */
+import java.util.ArrayList;
+
+import se.emilsjolander.stickylistheaders.StickyListHeadersAdapter;
+
public class TestBaseAdapter extends BaseAdapter implements
StickyListHeadersAdapter, SectionIndexer {
@@ -121,6 +120,10 @@ public long getHeaderId(int position) {
@Override
public int getPositionForSection(int section) {
+ if (mSectionIndices.length == 0) {
+ return 0;
+ }
+
if (section >= mSectionIndices.length) {
section = mSectionIndices.length - 1;
} else if (section < 0) {
diff --git a/sample/src/se/emilsjolander/stickylistheaders/views/UnderlineTextView.java b/sample/src/se/emilsjolander/stickylistheaders/sample/views/UnderlineTextView.java
similarity index 96%
rename from sample/src/se/emilsjolander/stickylistheaders/views/UnderlineTextView.java
rename to sample/src/se/emilsjolander/stickylistheaders/sample/views/UnderlineTextView.java
index c202c00b..3dd083f2 100644
--- a/sample/src/se/emilsjolander/stickylistheaders/views/UnderlineTextView.java
+++ b/sample/src/se/emilsjolander/stickylistheaders/sample/views/UnderlineTextView.java
@@ -1,4 +1,4 @@
-package se.emilsjolander.stickylistheaders.views;
+package se.emilsjolander.stickylistheaders.sample.views;
import android.content.Context;
import android.content.res.Resources;