├── .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 | --------------------------------------------------------------------------------