er listener) { 660
+        mOnStickyHeaderChangedListener = listener;
661
+    }
662
+
663
+    public View getListChildAt(int index) {
664
+        return mList.getChildAt(index);
665
+    }
666
+
667
+    public int getListChildCount() {
668
+        return mList.getChildCount();
669
+    }
670
+
671
+    /**
672
+     * Use the method with extreme caution!! Changing any values on the
673
+     * underlying ListView might break everything.
674
+     *
675
+     * @return the ListView backing this view.
676
+     */
677
+    public ListView getWrappedList() {
678
+        return mList;
679
+    }
680
+
681
+    private boolean requireSdkVersion(int versionCode) {
682
+        if (Build.VERSION.SDK_INT < versionCode) {
683
+            Log.e("StickyListHeaders", "Api lvl must be at least "+versionCode+" to call this method");
684
+            return false;
685
+        }
686
+        return true;
687
+    }
688
+
689
+	/* ---------- ListView delegate methods ---------- */
690
+
691
+    public void setAdapter(StickyListHeadersAdapter adapter) {
692
+        if (adapter == null) {
693
+            if (mAdapter instanceof SectionIndexerAdapterWrapper) {
694
+                ((SectionIndexerAdapterWrapper) mAdapter).mSectionIndexerDelegate = null;
695
+            }
696
+            if (mAdapter != null) {
697
+                mAdapter.mDelegate = null;
698
+            }
699
+            mList.setAdapter(null);
700
+            clearHeader();
701
+            return;
702
+        }
703
+        if (mAdapter != null) {
704
+            mAdapter.unregisterDataSetObserver(mDataSetObserver);
705
+        }
706
+
707
+        if (adapter instanceof SectionIndexer) {
708
+            mAdapter = new SectionIndexerAdapterWrapper(getContext(), adapter);
709
+        } else {
710
+            mAdapter = new AdapterWrapper(getContext(), adapter);
711
+        }
712
+        mDataSetObserver = new AdapterWrapperDataSetObserver();
713
+        mAdapter.registerDataSetObserver(mDataSetObserver);
714
+
715
+        if (mOnHeaderClickListener != null) {
716
+            mAdapter.setOnHeaderClickListener(new AdapterWrapperHeaderClickHandler());
717
+        } else {
718
+            mAdapter.setOnHeaderClickListener(null);
719
+        }
720
+
721
+        mAdapter.setDivider(mDivider, mDividerHeight);
722
+
723
+        mList.setAdapter(mAdapter);
724
+        clearHeader();
725
+    }
726
+
727
+    public StickyListHeadersAdapter getAdapter() {
728
+        return mAdapter == null ? null : mAdapter.mDelegate;
729
+    }
730
+
731
+    public void setDivider(Drawable divider) {
732
+        mDivider = divider;
733
+        if (mAdapter != null) {
734
+            mAdapter.setDivider(mDivider, mDividerHeight);
735
+        }
736
+    }
737
+
738
+    public void setDividerHeight(int dividerHeight) {
739
+        mDividerHeight = dividerHeight;
740
+        if (mAdapter != null) {
741
+            mAdapter.setDivider(mDivider, mDividerHeight);
742
+        }
743
+    }
744
+
745
+    public Drawable getDivider() {
746
+        return mDivider;
747
+    }
748
+
749
+    public int getDividerHeight() {
750
+        return mDividerHeight;
751
+    }
752
+
753
+    public void setOnScrollListener(OnScrollListener onScrollListener) {
754
+        mOnScrollListenerDelegate = onScrollListener;
755
+    }
756
+
757
+    @Override
758
+    public void setOnTouchListener(final OnTouchListener l) {
759
+        if (l != null) {
760
+            mList.setOnTouchListener(new OnTouchListener() {
761
+                @Override
762
+                public boolean onTouch(View v, MotionEvent event) {
763
+                    return l.onTouch(StickyListHeadersListView.this, event);
764
+                }
765
+            });
766
+        } else {
767
+            mList.setOnTouchListener(null);
768
+        }
769
+    }
770
+
771
+    public void setOnItemClickListener(OnItemClickListener listener) {
772
+        mList.setOnItemClickListener(listener);
773
+    }
774
+
775
+    public void setOnItemLongClickListener(OnItemLongClickListener listener) {
776
+        mList.setOnItemLongClickListener(listener);
777
+    }
778
+
779
+    public void addHeaderView(View v, Object data, boolean isSelectable) {
780
+        mList.addHeaderView(v, data, isSelectable);
781
+    }
782
+
783
+    public void addHeaderView(View v) {
784
+        mList.addHeaderView(v);
785
+    }
786
+
787
+    public void removeHeaderView(View v) {
788
+        mList.removeHeaderView(v);
789
+    }
790
+
791
+    public int getHeaderViewsCount() {
792
+        return mList.getHeaderViewsCount();
793
+    }
794
+    
795
+    public void addFooterView(View v, Object data, boolean isSelectable) {
796
+        mList.addFooterView(v, data, isSelectable);
797
+    }
798
+
799
+    public void addFooterView(View v) {
800
+        mList.addFooterView(v);
801
+    }
802
+
803
+    public void removeFooterView(View v) {
804
+        mList.removeFooterView(v);
805
+    }
806
+
807
+    public int getFooterViewsCount() {
808
+        return mList.getFooterViewsCount();
809
+    }
810
+
811
+    public void setEmptyView(View v) {
812
+        mList.setEmptyView(v);
813
+    }
814
+
815
+    public View getEmptyView() {
816
+        return mList.getEmptyView();
817
+    }
818
+
819
+    @Override
820
+    public boolean isVerticalScrollBarEnabled() {
821
+        return mList.isVerticalScrollBarEnabled();
822
+    }
823
+
824
+    @Override
825
+    public boolean isHorizontalScrollBarEnabled() {
826
+        return mList.isHorizontalScrollBarEnabled();
827
+    }
828
+
829
+    @Override
830
+    public void setVerticalScrollBarEnabled(boolean verticalScrollBarEnabled) {
831
+        mList.setVerticalScrollBarEnabled(verticalScrollBarEnabled);
832
+    }
833
+
834
+    @Override
835
+    public void setHorizontalScrollBarEnabled(boolean horizontalScrollBarEnabled) {
836
+        mList.setHorizontalScrollBarEnabled(horizontalScrollBarEnabled);
837
+    }
838
+
839
+    @Override
840
+    @TargetApi(Build.VERSION_CODES.GINGERBREAD)
841
+    public int getOverScrollMode() {
842
+        if (requireSdkVersion(Build.VERSION_CODES.GINGERBREAD)) {
843
+            return mList.getOverScrollMode();
844
+        }
845
+        return 0;
846
+    }
847
+
848
+    @Override
849
+    @TargetApi(Build.VERSION_CODES.GINGERBREAD)
850
+    public void setOverScrollMode(int mode) {
851
+        if (requireSdkVersion(Build.VERSION_CODES.GINGERBREAD)) {
852
+            if (mList != null) {
853
+                mList.setOverScrollMode(mode);
854
+            }
855
+        }
856
+    }
857
+
858
+    @TargetApi(Build.VERSION_CODES.FROYO)
859
+    public void smoothScrollBy(int distance, int duration) {
860
+        if (requireSdkVersion(Build.VERSION_CODES.FROYO)) {
861
+            mList.smoothScrollBy(distance, duration);
862
+        }
863
+    }
864
+
865
+    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
866
+    public void smoothScrollByOffset(int offset) {
867
+        if (requireSdkVersion(Build.VERSION_CODES.HONEYCOMB)) {
868
+            mList.smoothScrollByOffset(offset);
869
+        }
870
+    }
871
+
872
+    @SuppressLint("NewApi")
873
+    @TargetApi(Build.VERSION_CODES.FROYO)
874
+    public void smoothScrollToPosition(int position) {
875
+        if (requireSdkVersion(Build.VERSION_CODES.FROYO)) {
876
+            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
877
+                mList.smoothScrollToPosition(position);
878
+            } else {
879
+                int offset = mAdapter == null ? 0 : getHeaderOverlap(position);
880
+                offset -= mClippingToPadding ? 0 : mPaddingTop;
881
+                mList.smoothScrollToPositionFromTop(position, offset);
882
+            }
883
+        }
884
+    }
885
+
886
+    @TargetApi(Build.VERSION_CODES.FROYO)
887
+    public void smoothScrollToPosition(int position, int boundPosition) {
888
+        if (requireSdkVersion(Build.VERSION_CODES.FROYO)) {
889
+            mList.smoothScrollToPosition(position, boundPosition);
890
+        }
891
+    }
892
+
893
+    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
894
+    public void smoothScrollToPositionFromTop(int position, int offset) {
895
+        if (requireSdkVersion(Build.VERSION_CODES.HONEYCOMB)) {
896
+            offset += mAdapter == null ? 0 : getHeaderOverlap(position);
897
+            offset -= mClippingToPadding ? 0 : mPaddingTop;
898
+            mList.smoothScrollToPositionFromTop(position, offset);
899
+        }
900
+    }
901
+
902
+    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
903
+    public void smoothScrollToPositionFromTop(int position, int offset,
904
+                                              int duration) {
905
+        if (requireSdkVersion(Build.VERSION_CODES.HONEYCOMB)) {
906
+            offset += mAdapter == null ? 0 : getHeaderOverlap(position);
907
+            offset -= mClippingToPadding ? 0 : mPaddingTop;
908
+            mList.smoothScrollToPositionFromTop(position, offset, duration);
909
+        }
910
+    }
911
+
912
+    public void setSelection(int position) {
913
+        setSelectionFromTop(position, 0);
914
+    }
915
+
916
+    public void setSelectionAfterHeaderView() {
917
+        mList.setSelectionAfterHeaderView();
918
+    }
919
+
920
+    public void setSelectionFromTop(int position, int y) {
921
+        y += mAdapter == null ? 0 : getHeaderOverlap(position);
922
+        y -= mClippingToPadding ? 0 : mPaddingTop;
923
+        mList.setSelectionFromTop(position, y);
924
+    }
925
+
926
+    public void setSelector(Drawable sel) {
927
+        mList.setSelector(sel);
928
+    }
929
+
930
+    public void setSelector(int resID) {
931
+        mList.setSelector(resID);
932
+    }
933
+
934
+    public int getFirstVisiblePosition() {
935
+        return mList.getFirstVisiblePosition();
936
+    }
937
+
938
+    public int getLastVisiblePosition() {
939
+        return mList.getLastVisiblePosition();
940
+    }
941
+
942
+    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
943
+    public void setChoiceMode(int choiceMode) {
944
+        mList.setChoiceMode(choiceMode);
945
+    }
946
+
947
+    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
948
+    public void setItemChecked(int position, boolean value) {
949
+        mList.setItemChecked(position, value);
950
+    }
951
+
952
+    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
953
+    public int getCheckedItemCount() {
954
+        if (requireSdkVersion(Build.VERSION_CODES.HONEYCOMB)) {
955
+            return mList.getCheckedItemCount();
956
+        }
957
+        return 0;
958
+    }
959
+
960
+    @TargetApi(Build.VERSION_CODES.FROYO)
961
+    public long[] getCheckedItemIds() {
962
+        if (requireSdkVersion(Build.VERSION_CODES.FROYO)) {
963
+            return mList.getCheckedItemIds();
964
+        }
965
+        return null;
966
+    }
967
+
968
+    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
969
+    public int getCheckedItemPosition() {
970
+        return mList.getCheckedItemPosition();
971
+    }
972
+
973
+    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
974
+    public SparseBooleanArray getCheckedItemPositions() {
975
+        return mList.getCheckedItemPositions();
976
+    }
977
+
978
+    public int getCount() {
979
+        return mList.getCount();
980
+    }
981
+
982
+    public Object getItemAtPosition(int position) {
983
+        return mList.getItemAtPosition(position);
984
+    }
985
+
986
+    public long getItemIdAtPosition(int position) {
987
+        return mList.getItemIdAtPosition(position);
988
+    }
989
+
990
+    @Override
991
+    public void setOnCreateContextMenuListener(OnCreateContextMenuListener l) {
992
+        mList.setOnCreateContextMenuListener(l);
993
+    }
994
+
995
+    @Override
996
+    public boolean showContextMenu() {
997
+        return mList.showContextMenu();
998
+    }
999
+
1000
+    public void invalidateViews() {
1001
+        mList.invalidateViews();
1002
+    }
1003
+
1004
+    @Override
1005
+    public void setClipToPadding(boolean clipToPadding) {
1006
+        if (mList != null) {
1007
+            mList.setClipToPadding(clipToPadding);
1008
+        }
1009
+        mClippingToPadding = clipToPadding;
1010
+    }
1011
+
1012
+    @Override
1013
+    public void setPadding(int left, int top, int right, int bottom) {
1014
+        mPaddingLeft = left;
1015
+        mPaddingTop = top;
1016
+        mPaddingRight = right;
1017
+        mPaddingBottom = bottom;
1018
+
1019
+        if (mList != null) {
1020
+            mList.setPadding(left, top, right, bottom);
1021
+        }
1022
+        super.setPadding(0, 0, 0, 0);
1023
+        requestLayout();
1024
+    }
1025
+
1026
+    /*
1027
+     * Overrides an @hide method in View
1028
+     */
1029
+    protected void recomputePadding() {
1030
+        setPadding(mPaddingLeft, mPaddingTop, mPaddingRight, mPaddingBottom);
1031
+    }
1032
+
1033
+    @Override
1034
+    public int getPaddingLeft() {
1035
+        return mPaddingLeft;
1036
+    }
1037
+
1038
+    @Override
1039
+    public int getPaddingTop() {
1040
+        return mPaddingTop;
1041
+    }
1042
+
1043
+    @Override
1044
+    public int getPaddingRight() {
1045
+        return mPaddingRight;
1046
+    }
1047
+
1048
+    @Override
1049
+    public int getPaddingBottom() {
1050
+        return mPaddingBottom;
1051
+    }
1052
+
1053
+    public void setFastScrollEnabled(boolean fastScrollEnabled) {
1054
+        mList.setFastScrollEnabled(fastScrollEnabled);
1055
+    }
1056
+
1057
+    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
1058
+    public void setFastScrollAlwaysVisible(boolean alwaysVisible) {
1059
+        if (requireSdkVersion(Build.VERSION_CODES.HONEYCOMB)) {
1060
+            mList.setFastScrollAlwaysVisible(alwaysVisible);
1061
+        }
1062
+    }
1063
+
1064
+    /**
1065
+     * @return true if the fast scroller will always show. False on pre-Honeycomb devices.
1066
+     * @see AbsListView#isFastScrollAlwaysVisible()
1067
+     */
1068
+    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
1069
+    public boolean isFastScrollAlwaysVisible() {
1070
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
1071
+            return false;
1072
+        }
1073
+        return mList.isFastScrollAlwaysVisible();
1074
+    }
1075
+
1076
+    public void setScrollBarStyle(int style) {
1077
+        mList.setScrollBarStyle(style);
1078
+    }
1079
+
1080
+    public int getScrollBarStyle() {
1081
+        return mList.getScrollBarStyle();
1082
+    }
1083
+
1084
+    public int getPositionForView(View view) {
1085
+        return mList.getPositionForView(view);
1086
+    }
1087
+
1088
+    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
1089
+    public void setMultiChoiceModeListener(MultiChoiceModeListener listener) {
1090
+        if (requireSdkVersion(Build.VERSION_CODES.HONEYCOMB)) {
1091
+            mList.setMultiChoiceModeListener(listener);
1092
+        }
1093
+    }
1094
+
1095
+    @Override
1096
+    public Parcelable onSaveInstanceState() {
1097
+        Parcelable superState = super.onSaveInstanceState();
1098
+        if (superState != BaseSavedState.EMPTY_STATE) {
1099
+          throw new IllegalStateException("Handling non empty state of parent class is not implemented");
1100
+        }
1101
+        return mList.onSaveInstanceState();
1102
+    }
1103
+
1104
+    @Override
1105
+    public void onRestoreInstanceState(Parcelable state) {
1106
+        super.onRestoreInstanceState(BaseSavedState.EMPTY_STATE);
1107
+        mList.onRestoreInstanceState(state);
1108
+    }
1109
+
1110
+    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
1111
+    @Override
1112
+    public boolean canScrollVertically(int direction) {
1113
+        return mList.canScrollVertically(direction);
1114
+    }
1115
+
1116
+    public void setTranscriptMode (int mode) {
1117
+        mList.setTranscriptMode(mode);
1118
+    }
1119
+
1120
+    public void setBlockLayoutChildren(boolean blockLayoutChildren) {
1121
+        mList.setBlockLayoutChildren(blockLayoutChildren);
1122
+    }
1123
+    
1124
+    public void setStackFromBottom(boolean stackFromBottom) {
1125
+    	mList.setStackFromBottom(stackFromBottom);
1126
+    }
1127
+
1128
+    public boolean isStackFromBottom() {
1129
+    	return mList.isStackFromBottom();
1130
+    }
1131
+}

+ 156 - 0
views/src/main/java/com/android/views/stickylistheaders/WrapperView.java

@@ -0,0 +1,156 @@
1
+package com.android.views.stickylistheaders;
2
+
3
+import android.content.Context;
4
+import android.graphics.Canvas;
5
+import android.graphics.drawable.Drawable;
6
+import android.os.Build;
7
+import android.view.View;
8
+import android.view.ViewGroup;
9
+import android.view.ViewParent;
10
+
11
+/**
12
+ * 
13
+ * the view that wrapps a divider header and a normal list item. The listview sees this as 1 item
14
+ * 
15
+ * @author Emil Sjölander
16
+ */
17
+public class WrapperView extends ViewGroup {
18
+
19
+	View mItem;
20
+	Drawable mDivider;
21
+	int mDividerHeight;
22
+	View mHeader;
23
+	int mItemTop;
24
+
25
+	WrapperView(Context c) {
26
+		super(c);
27
+	}
28
+
29
+	public boolean hasHeader() {
30
+		return mHeader != null;
31
+	}
32
+	
33
+	public View getItem() {
34
+		return mItem;
35
+	}
36
+	
37
+	public View getHeader() {
38
+		return mHeader;
39
+	}
40
+
41
+	void update(View item, View header, Drawable divider, int dividerHeight) {
42
+		
43
+		//every wrapperview must have a list item
44
+		if (item == null) {
45
+			throw new NullPointerException("List view item must not be null.");
46
+		}
47
+
48
+		//only remove the current item if it is not the same as the new item. this can happen if wrapping a recycled view
49
+		if (this.mItem != item) {
50
+			removeView(this.mItem);
51
+			this.mItem = item;
52
+			final ViewParent parent = item.getParent();
53
+			if(parent != null && parent != this) {
54
+				if(parent instanceof ViewGroup) {
55
+					((ViewGroup) parent).removeView(item);
56
+				}
57
+			}
58
+			addView(item);
59
+		}
60
+
61
+		//same logik as above but for the header
62
+		if (this.mHeader != header) {
63
+			if (this.mHeader != null) {
64
+				removeView(this.mHeader);
65
+			}
66
+			this.mHeader = header;
67
+			if (header != null) {
68
+				addView(header);
69
+			}
70
+		}
71
+
72
+		if (this.mDivider != divider) {
73
+			this.mDivider = divider;
74
+			this.mDividerHeight = dividerHeight;
75
+			invalidate();
76
+		}
77
+	}
78
+
79
+	@Override
80
+	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
81
+		int measuredWidth = MeasureSpec.getSize(widthMeasureSpec);
82
+		int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(measuredWidth,
83
+				MeasureSpec.EXACTLY);
84
+		int measuredHeight = 0;
85
+		
86
+		//measure header or divider. when there is a header visible it acts as the divider
87
+		if (mHeader != null) {
88
+			LayoutParams params = mHeader.getLayoutParams();
89
+			if (params != null && params.height > 0) {
90
+				mHeader.measure(childWidthMeasureSpec,
91
+						MeasureSpec.makeMeasureSpec(params.height, MeasureSpec.EXACTLY));
92
+			} else {
93
+				mHeader.measure(childWidthMeasureSpec,
94
+						MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
95
+			}
96
+			measuredHeight += mHeader.getMeasuredHeight();
97
+		} else if (mDivider != null&&mItem.getVisibility()!=View.GONE) {
98
+			measuredHeight += mDividerHeight;
99
+		}
100
+
101
+		//measure item
102
+		LayoutParams params = mItem.getLayoutParams();
103
+        //enable hiding listview item,ex. toggle off items in group
104
+		if(mItem.getVisibility()==View.GONE){
105
+            mItem.measure(childWidthMeasureSpec,
106
+                    MeasureSpec.makeMeasureSpec(0, MeasureSpec.EXACTLY));
107
+        }else if (params != null && params.height >= 0) {
108
+			mItem.measure(childWidthMeasureSpec,
109
+					MeasureSpec.makeMeasureSpec(params.height, MeasureSpec.EXACTLY));
110
+            measuredHeight += mItem.getMeasuredHeight();
111
+		} else {
112
+			mItem.measure(childWidthMeasureSpec,
113
+					MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
114
+            measuredHeight += mItem.getMeasuredHeight();
115
+		}
116
+
117
+
118
+		setMeasuredDimension(measuredWidth, measuredHeight);
119
+	}
120
+
121
+	@Override
122
+	protected void onLayout(boolean changed, int l, int t, int r, int b) {
123
+
124
+		l = 0;
125
+		t = 0;
126
+		r = getWidth();
127
+		b = getHeight();
128
+
129
+		if (mHeader != null) {
130
+			int headerHeight = mHeader.getMeasuredHeight();
131
+			mHeader.layout(l, t, r, headerHeight);
132
+			mItemTop = headerHeight;
133
+			mItem.layout(l, headerHeight, r, b);
134
+		} else if (mDivider != null) {
135
+			mDivider.setBounds(l, t, r, mDividerHeight);
136
+			mItemTop = mDividerHeight;
137
+			mItem.layout(l, mDividerHeight, r, b);
138
+		} else {
139
+			mItemTop = t;
140
+			mItem.layout(l, t, r, b);
141
+		}
142
+	}
143
+
144
+	@Override
145
+	protected void dispatchDraw(Canvas canvas) {
146
+		super.dispatchDraw(canvas);
147
+		if (mHeader == null && mDivider != null&&mItem.getVisibility()!=View.GONE) {
148
+			// Drawable.setBounds() does not seem to work pre-honeycomb. So have
149
+			// to do this instead
150
+			if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
151
+				canvas.clipRect(0, 0, getWidth(), mDividerHeight);
152
+			}
153
+			mDivider.draw(canvas);
154
+		}
155
+	}
156
+}

+ 196 - 0
views/src/main/java/com/android/views/stickylistheaders/WrapperViewList.java

@@ -0,0 +1,196 @@
1
+package com.android.views.stickylistheaders;
2
+
3
+import android.content.Context;
4
+import android.graphics.Canvas;
5
+import android.graphics.Rect;
6
+import android.os.Build;
7
+import android.view.View;
8
+import android.widget.AbsListView;
9
+import android.widget.ListView;
10
+
11
+import java.lang.reflect.Field;
12
+import java.util.ArrayList;
13
+import java.util.List;
14
+
15
+class WrapperViewList extends ListView {
16
+
17
+	interface LifeCycleListener {
18
+		void onDispatchDrawOccurred(Canvas canvas);
19
+	}
20
+
21
+	private LifeCycleListener mLifeCycleListener;
22
+	private List<View> mFooterViews;
23
+	private int mTopClippingLength;
24
+	private Rect mSelectorRect = new Rect();// for if reflection fails
25
+	private Field mSelectorPositionField;
26
+	private boolean mClippingToPadding = true;
27
+    private boolean mBlockLayoutChildren = false;
28
+
29
+	public WrapperViewList(Context context) {
30
+		super(context);
31
+
32
+		// Use reflection to be able to change the size/position of the list
33
+		// selector so it does not come under/over the header
34
+		try {
35
+			Field selectorRectField = AbsListView.class.getDeclaredField("mSelectorRect");
36
+			selectorRectField.setAccessible(true);
37
+			mSelectorRect = (Rect) selectorRectField.get(this);
38
+
39
+			if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
40
+				mSelectorPositionField = AbsListView.class.getDeclaredField("mSelectorPosition");
41
+				mSelectorPositionField.setAccessible(true);
42
+			}
43
+		} catch (NoSuchFieldException e) {
44
+			e.printStackTrace();
45
+		} catch (IllegalArgumentException e) {
46
+			e.printStackTrace();
47
+		} catch (IllegalAccessException e) {
48
+			e.printStackTrace();
49
+		}
50
+	}
51
+
52
+	@Override
53
+	public boolean performItemClick(View view, int position, long id) {
54
+		if (view instanceof WrapperView) {
55
+			view = ((WrapperView) view).mItem;
56
+		}
57
+		return super.performItemClick(view, position, id);
58
+	}
59
+
60
+	private void positionSelectorRect() {
61
+		if (!mSelectorRect.isEmpty()) {
62
+			int selectorPosition = getSelectorPosition();
63
+			if (selectorPosition >= 0) {
64
+				int firstVisibleItem = getFixedFirstVisibleItem();
65
+				View v = getChildAt(selectorPosition - firstVisibleItem);
66
+				if (v instanceof WrapperView) {
67
+					WrapperView wrapper = ((WrapperView) v);
68
+					mSelectorRect.top = wrapper.getTop() + wrapper.mItemTop;
69
+				}
70
+			}
71
+		}
72
+	}
73
+
74
+	private int getSelectorPosition() {
75
+		if (mSelectorPositionField == null) { // not all supported andorid
76
+			// version have this variable
77
+			for (int i = 0; i < getChildCount(); i++) {
78
+				if (getChildAt(i).getBottom() == mSelectorRect.bottom) {
79
+					return i + getFixedFirstVisibleItem();
80
+				}
81
+			}
82
+		} else {
83
+			try {
84
+				return mSelectorPositionField.getInt(this);
85
+			} catch (IllegalArgumentException e) {
86
+				e.printStackTrace();
87
+			} catch (IllegalAccessException e) {
88
+				e.printStackTrace();
89
+			}
90
+		}
91
+		return -1;
92
+	}
93
+
94
+	@Override
95
+	protected void dispatchDraw(Canvas canvas) {
96
+		positionSelectorRect();
97
+		if (mTopClippingLength != 0) {
98
+			canvas.save();
99
+			Rect clipping = canvas.getClipBounds();
100
+			clipping.top = mTopClippingLength;
101
+			canvas.clipRect(clipping);
102
+			super.dispatchDraw(canvas);
103
+			canvas.restore();
104
+		} else {
105
+			super.dispatchDraw(canvas);
106
+		}
107
+		mLifeCycleListener.onDispatchDrawOccurred(canvas);
108
+	}
109
+
110
+	void setLifeCycleListener(LifeCycleListener lifeCycleListener) {
111
+		mLifeCycleListener = lifeCycleListener;
112
+	}
113
+
114
+	@Override
115
+	public void addFooterView(View v) {
116
+		super.addFooterView(v);
117
+		addInternalFooterView(v);
118
+	}
119
+
120
+	@Override
121
+	public void addFooterView(View v, Object data, boolean isSelectable) {
122
+		super.addFooterView(v, data, isSelectable);
123
+		addInternalFooterView(v);
124
+	}
125
+
126
+	private void addInternalFooterView(View v) {
127
+		if (mFooterViews == null) {
128
+			mFooterViews = new ArrayList<View>();
129
+		}
130
+		mFooterViews.add(v);
131
+	}
132
+
133
+	@Override
134
+	public boolean removeFooterView(View v) {
135
+		if (super.removeFooterView(v)) {
136
+			mFooterViews.remove(v);
137
+			return true;
138
+		}
139
+		return false;
140
+	}
141
+
142
+	boolean containsFooterView(View v) {
143
+		if (mFooterViews == null) {
144
+			return false;
145
+		}
146
+		return mFooterViews.contains(v);
147
+	}
148
+
149
+	void setTopClippingLength(int topClipping) {
150
+		mTopClippingLength = topClipping;
151
+	}
152
+
153
+	int getFixedFirstVisibleItem() {
154
+		int firstVisibleItem = getFirstVisiblePosition();
155
+		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
156
+			return firstVisibleItem;
157
+		}
158
+
159
+		// first getFirstVisiblePosition() reports items
160
+		// outside the view sometimes on old versions of android
161
+		for (int i = 0; i < getChildCount(); i++) {
162
+			if (getChildAt(i).getBottom() >= 0) {
163
+				firstVisibleItem += i;
164
+				break;
165
+			}
166
+		}
167
+
168
+		// work around to fix bug with firstVisibleItem being to high
169
+		// because list view does not take clipToPadding=false into account
170
+		// on old versions of android
171
+		if (!mClippingToPadding && getPaddingTop() > 0 && firstVisibleItem > 0) {
172
+			if (getChildAt(0).getTop() > 0) {
173
+				firstVisibleItem -= 1;
174
+			}
175
+		}
176
+
177
+		return firstVisibleItem;
178
+	}
179
+
180
+	@Override
181
+	public void setClipToPadding(boolean clipToPadding) {
182
+		mClippingToPadding = clipToPadding;
183
+		super.setClipToPadding(clipToPadding);
184
+	}
185
+
186
+    public void setBlockLayoutChildren(boolean block) {
187
+        mBlockLayoutChildren = block;
188
+    }
189
+
190
+    @Override
191
+    protected void layoutChildren() {
192
+        if (!mBlockLayoutChildren) {
193
+            super.layoutChildren();
194
+        }
195
+    }
196
+}

+ 34 - 0
views/src/main/res/values/attrs.xml

@@ -87,4 +87,38 @@
87 87
     </declare-styleable>
88 88
 
89 89
     <attr name="SwipeBackLayoutStyle" format="reference"/>
90
+
91
+    <declare-styleable name="StickyListHeadersListView">
92
+        <attr name="stickyListHeadersListViewStyle" format="reference"/>
93
+
94
+        <!-- View attributes -->
95
+        <attr name="android:clipToPadding" />
96
+        <attr name="android:scrollbars" />
97
+        <attr name="android:overScrollMode" />
98
+        <attr name="android:padding" />
99
+        <attr name="android:paddingLeft" />
100
+        <attr name="android:paddingTop" />
101
+        <attr name="android:paddingRight" />
102
+        <attr name="android:paddingBottom" />
103
+
104
+        <!-- ListView attributes -->
105
+        <attr name="android:fadingEdgeLength" />
106
+        <attr name="android:requiresFadingEdge" />
107
+        <attr name="android:cacheColorHint" />
108
+        <attr name="android:choiceMode" />
109
+        <attr name="android:drawSelectorOnTop" />
110
+        <attr name="android:fastScrollEnabled" />
111
+        <attr name="android:fastScrollAlwaysVisible" />
112
+        <attr name="android:listSelector" />
113
+        <attr name="android:scrollingCache" />
114
+        <attr name="android:scrollbarStyle" />
115
+        <attr name="android:divider" />
116
+        <attr name="android:dividerHeight" />
117
+        <attr name="android:transcriptMode" />
118
+        <attr name="android:stackFromBottom" />
119
+
120
+        <!-- StickyListHeaders attributes -->
121
+        <attr name="hasStickyHeaders" format="boolean" />
122
+        <attr name="isDrawingListUnderStickyHeader" format="boolean" />
123
+    </declare-styleable>
90 124
 </resources>

Kodo/kodo - Gogs: Go Git Service

1 Commits (15861b09710b608473b1a2856b16baa67c69125b)

Autor SHA1 Mensagem Data
  Brightcells d10bd672ea AdministratorInfo/BrandModelDistributorPriceInfo 7 anos atrás