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"/> + + - - +