├── .gitignore
├── AndroidManifest.xml
├── pom.xml
├── res
├── drawable
│ └── shadow.xml
└── values
│ └── attrs.xml
└── src
└── main
└── java
└── com
└── madgag
└── android
└── listviews
└── pinnedheader
├── PinnedHeaderLayout.java
└── PinnedHeaderTrait.java
/.gitignore:
--------------------------------------------------------------------------------
1 | */target
2 | target
3 | tmp
4 | *~
5 | lib
6 | */test-output
7 | temp-testng-customsuite.xml
8 | **pom.xml.releaseBackup
9 | release.properties
10 | *.iws
11 | *.iml
12 | *.ipr
13 | .classpath
14 | .project
15 | .settings
16 | gen
17 | */seed.txt
18 | notes
19 | logs
20 |
--------------------------------------------------------------------------------
/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
20 |
21 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
20 |
21 |
22 | 4.0.0
23 |
24 | org.sonatype.oss
25 | oss-parent
26 | 7
27 |
28 | com.madgag
29 | android-pinned-header-listviews
30 | 0.5-SNAPSHOT
31 | aar
32 | Android Pinned-Header ListViews
33 | Small library providing pinned headers for ExpandableListViews
34 | https://github.com/rtyley/android-pinned-header-listviews
35 |
36 | UTF-8
37 | 2.2.1
38 |
39 |
40 |
41 | GPL v3
42 | http://www.gnu.org/licenses/gpl-3.0.html
43 | repo
44 |
45 |
46 |
47 | scm:git:git@github.com:rtyley/android-pinned-header-listviews.git
48 | scm:git:git@github.com:rtyley/android-pinned-header-listviews.git
49 | git@github.com:rtyley/android-pinned-header-listviews.git
50 |
51 |
52 |
53 | roberto
54 | Roberto Tyley
55 | 0
56 | https://github.com/rtyley
57 |
58 |
59 |
60 |
61 |
62 | org.apache.maven.plugins
63 | maven-compiler-plugin
64 |
65 | 1.5
66 | 1.5
67 | UTF-8
68 |
69 |
70 |
71 | com.jayway.maven.plugins.android.generation2
72 | android-maven-plugin
73 | 3.8.2
74 | true
75 |
76 |
77 | 19
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 | com.google.android
86 | android
87 | provided
88 | ${android.version}
89 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/res/drawable/shadow.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
--------------------------------------------------------------------------------
/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/src/main/java/com/madgag/android/listviews/pinnedheader/PinnedHeaderLayout.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2011 Roberto Tyley
3 | *
4 | * This file is part of 'android-pinned-header-listviews'.
5 | *
6 | * android-pinned-header-listviews is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * android-pinned-header-listviews is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package com.madgag.android.listviews.pinnedheader;
21 |
22 | import android.content.Context;
23 | import android.content.res.Resources;
24 | import android.content.res.TypedArray;
25 | import android.graphics.Canvas;
26 | import android.graphics.drawable.Drawable;
27 | import android.util.AttributeSet;
28 | import android.util.Log;
29 | import android.view.View;
30 | import android.view.ViewGroup;
31 | import android.widget.ExpandableListView;
32 |
33 | import static com.madgag.android.listviews.pinnedheader.R.drawable.shadow;
34 | import static com.madgag.android.listviews.pinnedheader.R.styleable.PinnedHeaderLayout_shadowDrawable;
35 |
36 | public class PinnedHeaderLayout extends ViewGroup implements PinnedHeaderTrait.HeaderViewGroupAttacher {
37 |
38 | private Drawable shadowDrawable;
39 | private static final String TAG = "PHL";
40 |
41 | private PinnedHeaderTrait pinnedHeaderTrait;
42 |
43 | public PinnedHeaderLayout(Context context) {
44 | super(context);
45 | }
46 |
47 | public PinnedHeaderLayout(Context context, AttributeSet attrs) {
48 | super(context, attrs);
49 |
50 | TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.PinnedHeaderLayout);
51 | shadowDrawable=typedArray.getDrawable(PinnedHeaderLayout_shadowDrawable);
52 | if (shadowDrawable==null) {
53 | shadowDrawable = getResources().getDrawable(shadow);
54 | }
55 | }
56 |
57 | @Override
58 | protected void onFinishInflate() {
59 | super.onFinishInflate();
60 | initListView();
61 | }
62 |
63 | private void initListView() {
64 | pinnedHeaderTrait = new PinnedHeaderTrait((ExpandableListView) getChildAt(0), this, this);
65 | }
66 |
67 | @Override
68 | protected void dispatchDraw(Canvas canvas) {
69 | super.dispatchDraw(canvas);
70 | View headerView = getHeaderView();
71 | if (headerView!=null && headerView.getVisibility()==VISIBLE && shadowDrawable!=null) {
72 | shadowDrawable.setBounds(0, headerView.getBottom(), getWidth(), 15 + headerView.getBottom());
73 | shadowDrawable.draw(canvas);
74 | }
75 | }
76 |
77 | @Override
78 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
79 | View v = getChildAt(0);
80 | // measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
81 | measureChild(v, widthMeasureSpec, heightMeasureSpec);
82 |
83 | setMeasuredDimension(
84 | resolveSize(v.getMeasuredWidth(), widthMeasureSpec),
85 | resolveSize(v.getMeasuredHeight(), heightMeasureSpec));
86 |
87 | View headerView = getHeaderView();
88 | if (headerView != null) {
89 | measureChild(headerView, widthMeasureSpec, heightMeasureSpec);
90 | }
91 | }
92 |
93 | private View getHeaderView() {
94 | return pinnedHeaderTrait == null ? null : pinnedHeaderTrait.getHeaderView();
95 | }
96 |
97 | @Override
98 | protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
99 | View v = getChildAt(0);
100 | v.layout(0, 0, v.getMeasuredWidth(), v.getMeasuredHeight());
101 | pinnedHeaderTrait.configureHeaderView();
102 | }
103 |
104 | public void attach(View header) {
105 | View currentHeader = getChildAt(1);
106 | if (currentHeader == null) {
107 | addHeaderAndTriggerMeasure(header);
108 | } else if (currentHeader!=header) {
109 | removeViewAt(1);
110 | addHeaderAndTriggerMeasure(header);
111 | }
112 | }
113 |
114 | private void addHeaderAndTriggerMeasure(View header) {
115 | addView(header, 1);
116 | header.requestLayout();
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/src/main/java/com/madgag/android/listviews/pinnedheader/PinnedHeaderTrait.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2011 Roberto Tyley
3 | *
4 | * This file is part of 'android-pinned-header-listviews'.
5 | *
6 | * android-pinned-header-listviews is free software: you can redistribute it and/or modify
7 | * it under the terms of the GNU General Public License as published by
8 | * the Free Software Foundation, either version 3 of the License, or
9 | * (at your option) any later version.
10 | *
11 | * android-pinned-header-listviews is distributed in the hope that it will be useful,
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 | * GNU General Public License for more details.
15 | *
16 | * You should have received a copy of the GNU General Public License
17 | * along with this program. If not, see .
18 | */
19 |
20 | package com.madgag.android.listviews.pinnedheader;
21 |
22 | import android.graphics.Color;
23 | import android.graphics.drawable.ColorDrawable;
24 | import android.graphics.drawable.Drawable;
25 | import android.view.View;
26 | import android.view.ViewGroup;
27 | import android.widget.AbsListView;
28 | import android.widget.ExpandableListView;
29 |
30 | import static android.view.View.GONE;
31 | import static android.view.View.VISIBLE;
32 | import static android.widget.ExpandableListView.getPackedPositionForGroup;
33 | import static android.widget.ExpandableListView.getPackedPositionGroup;
34 | import static java.lang.Math.round;
35 |
36 | public class PinnedHeaderTrait implements AbsListView.OnScrollListener {
37 |
38 | private static final String TAG = "PHT";
39 |
40 | public static final int HEADER_TO_HEADER_BUFFER = 20;
41 |
42 | private final ExpandableListView listView;
43 | private final ViewGroup parentViewGroup;
44 | private final HeaderViewGroupAttacher headerViewGroupAttacher;
45 |
46 | private View headerView;
47 |
48 | public PinnedHeaderTrait(ExpandableListView listView, ViewGroup parentViewGroup, HeaderViewGroupAttacher headerViewGroupAttacher) {
49 | this.listView = listView;
50 | this.parentViewGroup = parentViewGroup;
51 | this.headerViewGroupAttacher = headerViewGroupAttacher;
52 | listView.setOnScrollListener(this);
53 | }
54 |
55 | public View getHeaderView() {
56 | return headerView;
57 | }
58 |
59 | public void configureHeaderView() {
60 | if (listView.getAdapter()==null) {
61 | return;
62 | }
63 |
64 | int firstVisiblePosition = listView.getFirstVisiblePosition();
65 | long expandableListPosition = listView.getExpandableListPosition(firstVisiblePosition);
66 |
67 | final int group = getPackedPositionGroup(expandableListPosition);
68 |
69 | if (firstVisiblePosition < 0 || !listView.isGroupExpanded(group) ||
70 | groupHeaderExactlyAtTopOfListView(firstVisiblePosition, group)) {
71 | if (headerView !=null) {
72 | headerView.setVisibility(GONE);
73 | }
74 | return;
75 | }
76 |
77 | headerView = listView.getExpandableListAdapter().getGroupView(group, false, headerView, parentViewGroup);
78 | headerViewGroupAttacher.attach(headerView);
79 | headerView.setOnClickListener(new View.OnClickListener() {
80 | public void onClick(View view) {
81 | listView.collapseGroup(group);
82 | listView.setSelectedGroup(group);
83 | }
84 | });
85 |
86 | int headerTop = calculatePushOffset(group, firstVisiblePosition);
87 | Drawable background = new ColorDrawable(Color.WHITE); // header.getBackground()
88 | headerView.setBackgroundDrawable(background);
89 | int measuredHeight = headerView.getMeasuredHeight();
90 | if (headerView.getTop() != headerTop || headerView.getHeight()!= measuredHeight) {
91 | headerView.layout(0, headerTop,
92 | headerView.getMeasuredWidth(), measuredHeight + headerTop);
93 | }
94 | headerView.setVisibility(VISIBLE);
95 | }
96 |
97 | private boolean groupHeaderExactlyAtTopOfListView(int firstVisiblePosition, int group) {
98 | return (firstVisiblePosition==listView.getFlatListPosition(getPackedPositionForGroup(group)) && listView.getChildAt(0).getTop()>=0);
99 | }
100 |
101 | private int calculatePushOffset(int group, int firstVisiblePosition) {
102 | int nextHeaderGroup = group + 1;
103 | int headerHeight = headerView.getHeight();
104 | int headerHeightWithBuffer = headerHeight + HEADER_TO_HEADER_BUFFER;
105 |
106 | int headerTop=0;
107 | int nextHeaderFlatListPosition = listView.getFlatListPosition(getPackedPositionForGroup(nextHeaderGroup));
108 | if (nextHeaderFlatListPosition<=listView.getLastVisiblePosition()) {
109 | // next header is visible, we may need to handle partial push-up
110 | View v = listView.getChildAt(nextHeaderFlatListPosition-firstVisiblePosition);
111 | if (v!=null && v.getTop() < headerHeightWithBuffer) {
112 | headerTop = round((v.getTop() - headerHeightWithBuffer)*1.1f); // make header 'zoom' away from next header
113 | }
114 | }
115 | return headerTop;
116 | }
117 |
118 | public void onScrollStateChanged(AbsListView view, int scrollState) {}
119 |
120 | public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
121 | configureHeaderView();
122 | }
123 |
124 | public interface HeaderViewGroupAttacher {
125 | void attach(View header);
126 | }
127 | }
128 |
--------------------------------------------------------------------------------