├── .gitignore ├── README.md ├── demo_capture.png ├── library ├── AndroidManifest.xml ├── project.properties ├── res │ ├── anim │ │ ├── in_animation.xml │ │ └── out_animation.xml │ └── values │ │ └── attrs.xml └── src │ └── com │ └── dafruits │ └── android │ └── library │ └── widgets │ └── ExtendedListView.java └── sample ├── AndroidManifest.xml ├── project.properties ├── res ├── anim │ ├── in.xml │ └── out.xml ├── drawable-hdpi │ ├── icon.png │ └── scrollbarpanel_background.9.png ├── drawable-ldpi │ └── icon.png ├── drawable-mdpi │ └── icon.png ├── drawable-xhdpi │ └── icon.png ├── drawable │ └── background_scrollbarpanel.xml ├── layout │ ├── list_item.xml │ ├── main.xml │ └── scrollbarpanel.xml └── values │ └── strings.xml └── src └── com └── dafruits └── android └── samples └── DemoScrollBarPanelActivity.java /.gitignore: -------------------------------------------------------------------------------- 1 | #Android generated 2 | bin 3 | gen 4 | 5 | #Eclipse 6 | .project 7 | .classpath 8 | .settings 9 | 10 | #IntelliJ IDEA 11 | .idea 12 | *.iml 13 | *.ipr 14 | *.iws 15 | out 16 | 17 | #Maven 18 | target 19 | release.properties 20 | pom.xml.* 21 | 22 | #Ant 23 | build.xml 24 | local.properties 25 | proguard.cfg 26 | 27 | #OSX 28 | .DS_Store 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Path 2.0 like ScrollBarPanel for Android 2 | 3 | ![Screenshot](https://github.com/rno/Android-ScrollBarPanel/raw/master/demo_capture.png) 4 | 5 | Android-ScrollBarPanel allows to attach a View to a scroll indicator like it's done in Path 2.0. 6 | 7 | ## Features 8 | 9 | * Supports custom View as ScrollBarPanel. 10 | 11 | Repository at . 12 | 13 | ## Usage 14 | 15 | ### Layout 16 | 17 | ``` xml 18 | 22 | 30 | ``` 31 | 32 | ### Activity 33 | 34 | ``` java 35 | // Set your scrollBarPanel 36 | ExtendedListView listView = (ExtendedListView) findViewById(android.R.id.list); 37 | 38 | // Attach a position changed listener on the listview and play with your scrollBarPanel 39 | // when you need to update its content 40 | mListView.setOnPositionChangedListener(new OnPositionChangedListener() { 41 | 42 | @Override 43 | public void onPositionChanged(ExtendedListView listView, int firstVisiblePosition, View scrollBarPanel) { 44 | ((TextView) scrollBarPanel).setText("Position " + firstVisiblePosition); 45 | } 46 | }); 47 | 48 | ``` 49 | 50 | ## Pull Requests 51 | 52 | I will gladly accept pull requests for fixes and feature enhancements but please do them in the dev branch. The master branch is for the latest stable code, dev is where I try things out before releasing them as stable. Any pull requests that are against master from now on will be closed asking for you to do another pull against dev. 53 | 54 | ## Changelog 55 | 56 | ### 0.1.0 57 | 58 | * first commit :-) 59 | 60 | ### 0.1.1 61 | 62 | * added scrollBarPanel attribute to ExtendedListView 63 | 64 | ### 0.1.2 65 | 66 | * Optimisations 67 | 68 | ### 0.1.3 69 | 70 | * More precision regarding scrollBarPanel fade in and fade out (Thanks [Cyril Mottier](https://github.com/cyrilmottier) for the tip!) 71 | 72 | ### 0.1.4 73 | 74 | * Use the position the scrollbar thumb is on instead of the firstVisibleItem position. 75 | 76 | ### 0.1.5 77 | 78 | * Fix NPE (Thanks to [Chris Banes](https://github.com/chrisbanes)) 79 | * Added attributes to ExtendedListView to customize in / out animation of scrollBarPanel 80 | 81 | ## Acknowledgments 82 | 83 | * [Chris Banes](https://github.com/chrisbanes) 84 | 85 | ## License 86 | 87 | Licensed under the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.html) 88 | -------------------------------------------------------------------------------- /demo_capture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rno/Android-ScrollBarPanel/9c6352632353de97cb12ec00fe1739e254e3c01b/demo_capture.png -------------------------------------------------------------------------------- /library/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /library/project.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system use, 7 | # "ant.properties", and override values to adapt the script to your 8 | # project structure. 9 | 10 | # Project target. 11 | target=android-15 12 | android.library=true 13 | -------------------------------------------------------------------------------- /library/res/anim/in_animation.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 10 | 14 | 15 | -------------------------------------------------------------------------------- /library/res/anim/out_animation.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 10 | 14 | 15 | -------------------------------------------------------------------------------- /library/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /library/src/com/dafruits/android/library/widgets/ExtendedListView.java: -------------------------------------------------------------------------------- 1 | package com.dafruits.android.library.widgets; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.graphics.Canvas; 6 | import android.os.Handler; 7 | import android.util.AttributeSet; 8 | import android.view.LayoutInflater; 9 | import android.view.View; 10 | import android.view.ViewConfiguration; 11 | import android.view.animation.Animation; 12 | import android.view.animation.Animation.AnimationListener; 13 | import android.view.animation.AnimationUtils; 14 | import android.widget.AbsListView; 15 | import android.widget.AbsListView.OnScrollListener; 16 | import android.widget.ListView; 17 | 18 | import com.dafruits.android.library.R; 19 | 20 | public class ExtendedListView extends ListView implements OnScrollListener { 21 | 22 | public static interface OnPositionChangedListener { 23 | 24 | public void onPositionChanged(ExtendedListView listView, int position, View scrollBarPanel); 25 | 26 | } 27 | 28 | private OnScrollListener mOnScrollListener = null; 29 | 30 | private View mScrollBarPanel = null; 31 | private int mScrollBarPanelPosition = 0; 32 | 33 | private OnPositionChangedListener mPositionChangedListener; 34 | private int mLastPosition = -1; 35 | 36 | private Animation mInAnimation = null; 37 | private Animation mOutAnimation = null; 38 | 39 | private final Handler mHandler = new Handler(); 40 | 41 | private final Runnable mScrollBarPanelFadeRunnable = new Runnable() { 42 | 43 | @Override 44 | public void run() { 45 | if (mOutAnimation != null) { 46 | mScrollBarPanel.startAnimation(mOutAnimation); 47 | } 48 | } 49 | }; 50 | 51 | /* 52 | * keep track of Measure Spec 53 | */ 54 | private int mWidthMeasureSpec; 55 | private int mHeightMeasureSpec; 56 | 57 | public ExtendedListView(Context context) { 58 | this(context, null); 59 | } 60 | 61 | public ExtendedListView(Context context, AttributeSet attrs) { 62 | this(context, attrs, android.R.attr.listViewStyle); 63 | } 64 | 65 | public ExtendedListView(Context context, AttributeSet attrs, int defStyle) { 66 | super(context, attrs, defStyle); 67 | 68 | super.setOnScrollListener(this); 69 | 70 | final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ExtendedListView); 71 | final int scrollBarPanelLayoutId = a.getResourceId(R.styleable.ExtendedListView_scrollBarPanel, -1); 72 | final int scrollBarPanelInAnimation = a.getResourceId(R.styleable.ExtendedListView_scrollBarPanelInAnimation, R.anim.in_animation); 73 | final int scrollBarPanelOutAnimation = a.getResourceId(R.styleable.ExtendedListView_scrollBarPanelOutAnimation, R.anim.out_animation); 74 | a.recycle(); 75 | 76 | if (scrollBarPanelLayoutId != -1) { 77 | setScrollBarPanel(scrollBarPanelLayoutId); 78 | } 79 | 80 | final int scrollBarPanelFadeDuration = ViewConfiguration.getScrollBarFadeDuration(); 81 | 82 | if (scrollBarPanelInAnimation > 0) { 83 | mInAnimation = AnimationUtils.loadAnimation(getContext(), scrollBarPanelInAnimation); 84 | } 85 | 86 | if (scrollBarPanelOutAnimation > 0) { 87 | mOutAnimation = AnimationUtils.loadAnimation(getContext(), scrollBarPanelOutAnimation); 88 | mOutAnimation.setDuration(scrollBarPanelFadeDuration); 89 | 90 | mOutAnimation.setAnimationListener(new AnimationListener() { 91 | 92 | @Override 93 | public void onAnimationStart(Animation animation) { 94 | } 95 | 96 | @Override 97 | public void onAnimationRepeat(Animation animation) { 98 | 99 | } 100 | 101 | @Override 102 | public void onAnimationEnd(Animation animation) { 103 | if (mScrollBarPanel != null) { 104 | mScrollBarPanel.setVisibility(View.GONE); 105 | } 106 | } 107 | }); 108 | } 109 | } 110 | 111 | @Override 112 | public void onScrollStateChanged(AbsListView view, int scrollState) { 113 | if (mOnScrollListener != null) { 114 | mOnScrollListener.onScrollStateChanged(view, scrollState); 115 | } 116 | } 117 | 118 | @Override 119 | public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { 120 | if (null != mPositionChangedListener && null != mScrollBarPanel) { 121 | 122 | // Don't do anything if there is no itemviews 123 | if (totalItemCount > 0) { 124 | /* 125 | * from android source code (ScrollBarDrawable.java) 126 | */ 127 | final int thickness = getVerticalScrollbarWidth(); 128 | int height = Math.round((float) getMeasuredHeight() * computeVerticalScrollExtent() / computeVerticalScrollRange()); 129 | int thumbOffset = Math.round((float) (getMeasuredHeight() - height) * computeVerticalScrollOffset() / (computeVerticalScrollRange() - computeVerticalScrollExtent())); 130 | final int minLength = thickness * 2; 131 | if (height < minLength) { 132 | height = minLength; 133 | } 134 | thumbOffset += height / 2; 135 | 136 | /* 137 | * find out which itemviews the center of thumb is on 138 | */ 139 | final int count = getChildCount(); 140 | for (int i = 0; i < count; ++i) { 141 | final View childView = getChildAt(i); 142 | if (childView != null) { 143 | if (thumbOffset > childView.getTop() && thumbOffset < childView.getBottom()) { 144 | /* 145 | * we have our candidate 146 | */ 147 | if (mLastPosition != firstVisibleItem + i) { 148 | mLastPosition = firstVisibleItem + i; 149 | 150 | /* 151 | * inform the position of the panel has changed 152 | */ 153 | mPositionChangedListener.onPositionChanged(this, mLastPosition, mScrollBarPanel); 154 | 155 | /* 156 | * measure panel right now since it has just changed 157 | * 158 | * INFO: quick hack to handle TextView has ScrollBarPanel (to wrap text in 159 | * case TextView's content has changed) 160 | */ 161 | measureChild(mScrollBarPanel, mWidthMeasureSpec, mHeightMeasureSpec); 162 | } 163 | break; 164 | } 165 | } 166 | } 167 | 168 | /* 169 | * update panel position 170 | */ 171 | mScrollBarPanelPosition = thumbOffset - mScrollBarPanel.getMeasuredHeight() / 2; 172 | final int x = getMeasuredWidth() - mScrollBarPanel.getMeasuredWidth() - getVerticalScrollbarWidth(); 173 | mScrollBarPanel.layout(x, mScrollBarPanelPosition, x + mScrollBarPanel.getMeasuredWidth(), 174 | mScrollBarPanelPosition + mScrollBarPanel.getMeasuredHeight()); 175 | } 176 | } 177 | 178 | if (mOnScrollListener != null) { 179 | mOnScrollListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount); 180 | } 181 | } 182 | 183 | public void setOnPositionChangedListener(OnPositionChangedListener onPositionChangedListener) { 184 | mPositionChangedListener = onPositionChangedListener; 185 | } 186 | 187 | @Override 188 | public void setOnScrollListener(OnScrollListener onScrollListener) { 189 | mOnScrollListener = onScrollListener; 190 | } 191 | 192 | public void setScrollBarPanel(View scrollBarPanel) { 193 | mScrollBarPanel = scrollBarPanel; 194 | mScrollBarPanel.setVisibility(View.GONE); 195 | requestLayout(); 196 | } 197 | 198 | public void setScrollBarPanel(int resId) { 199 | setScrollBarPanel(LayoutInflater.from(getContext()).inflate(resId, this, false)); 200 | } 201 | 202 | public View getScrollBarPanel() { 203 | return mScrollBarPanel; 204 | } 205 | 206 | @Override 207 | protected boolean awakenScrollBars(int startDelay, boolean invalidate) { 208 | final boolean isAnimationPlayed = super.awakenScrollBars(startDelay, invalidate); 209 | 210 | if (isAnimationPlayed == true && mScrollBarPanel != null) { 211 | if (mScrollBarPanel.getVisibility() == View.GONE) { 212 | mScrollBarPanel.setVisibility(View.VISIBLE); 213 | if (mInAnimation != null) { 214 | mScrollBarPanel.startAnimation(mInAnimation); 215 | } 216 | } 217 | 218 | mHandler.removeCallbacks(mScrollBarPanelFadeRunnable); 219 | mHandler.postAtTime(mScrollBarPanelFadeRunnable, AnimationUtils.currentAnimationTimeMillis() + startDelay); 220 | } 221 | 222 | return isAnimationPlayed; 223 | } 224 | 225 | @Override 226 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 227 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 228 | 229 | if (mScrollBarPanel != null && getAdapter() != null) { 230 | mWidthMeasureSpec = widthMeasureSpec; 231 | mHeightMeasureSpec = heightMeasureSpec; 232 | measureChild(mScrollBarPanel, widthMeasureSpec, heightMeasureSpec); 233 | } 234 | } 235 | 236 | @Override 237 | protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 238 | super.onLayout(changed, left, top, right, bottom); 239 | 240 | if (mScrollBarPanel != null) { 241 | final int x = getMeasuredWidth() - mScrollBarPanel.getMeasuredWidth() - getVerticalScrollbarWidth(); 242 | mScrollBarPanel.layout(x, mScrollBarPanelPosition, x + mScrollBarPanel.getMeasuredWidth(), 243 | mScrollBarPanelPosition + mScrollBarPanel.getMeasuredHeight()); 244 | } 245 | } 246 | 247 | @Override 248 | protected void dispatchDraw(Canvas canvas) { 249 | super.dispatchDraw(canvas); 250 | 251 | if (mScrollBarPanel != null && mScrollBarPanel.getVisibility() == View.VISIBLE) { 252 | drawChild(canvas, mScrollBarPanel, getDrawingTime()); 253 | } 254 | } 255 | 256 | @Override 257 | public void onDetachedFromWindow() { 258 | super.onDetachedFromWindow(); 259 | 260 | mHandler.removeCallbacks(mScrollBarPanelFadeRunnable); 261 | } 262 | } 263 | -------------------------------------------------------------------------------- /sample/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 14 | 15 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /sample/project.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system use, 7 | # "ant.properties", and override values to adapt the script to your 8 | # project structure. 9 | 10 | # Project target. 11 | target=android-15 12 | android.library.reference.1=../library 13 | -------------------------------------------------------------------------------- /sample/res/anim/in.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 10 | 14 | 15 | -------------------------------------------------------------------------------- /sample/res/anim/out.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 10 | 14 | 15 | -------------------------------------------------------------------------------- /sample/res/drawable-hdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rno/Android-ScrollBarPanel/9c6352632353de97cb12ec00fe1739e254e3c01b/sample/res/drawable-hdpi/icon.png -------------------------------------------------------------------------------- /sample/res/drawable-hdpi/scrollbarpanel_background.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rno/Android-ScrollBarPanel/9c6352632353de97cb12ec00fe1739e254e3c01b/sample/res/drawable-hdpi/scrollbarpanel_background.9.png -------------------------------------------------------------------------------- /sample/res/drawable-ldpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rno/Android-ScrollBarPanel/9c6352632353de97cb12ec00fe1739e254e3c01b/sample/res/drawable-ldpi/icon.png -------------------------------------------------------------------------------- /sample/res/drawable-mdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rno/Android-ScrollBarPanel/9c6352632353de97cb12ec00fe1739e254e3c01b/sample/res/drawable-mdpi/icon.png -------------------------------------------------------------------------------- /sample/res/drawable-xhdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rno/Android-ScrollBarPanel/9c6352632353de97cb12ec00fe1739e254e3c01b/sample/res/drawable-xhdpi/icon.png -------------------------------------------------------------------------------- /sample/res/drawable/background_scrollbarpanel.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | -------------------------------------------------------------------------------- /sample/res/layout/list_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | -------------------------------------------------------------------------------- /sample/res/layout/main.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /sample/res/layout/scrollbarpanel.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | -------------------------------------------------------------------------------- /sample/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Hello World! 5 | Android-ScrollBarPanel-Sample 6 | 7 | -------------------------------------------------------------------------------- /sample/src/com/dafruits/android/samples/DemoScrollBarPanelActivity.java: -------------------------------------------------------------------------------- 1 | package com.dafruits.android.samples; 2 | 3 | import android.app.Activity; 4 | import android.graphics.Color; 5 | import android.os.Bundle; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | import android.widget.BaseAdapter; 10 | import android.widget.TextView; 11 | 12 | import com.dafruits.android.library.widgets.ExtendedListView; 13 | import com.dafruits.android.library.widgets.ExtendedListView.OnPositionChangedListener; 14 | 15 | public class DemoScrollBarPanelActivity extends Activity implements OnPositionChangedListener { 16 | 17 | private ExtendedListView mListView; 18 | 19 | @Override 20 | public void onCreate(Bundle savedInstanceState) { 21 | super.onCreate(savedInstanceState); 22 | 23 | setContentView(R.layout.main); 24 | 25 | mListView = (ExtendedListView) findViewById(android.R.id.list); 26 | mListView.setAdapter(new DummyAdapter()); 27 | mListView.setCacheColorHint(Color.TRANSPARENT); 28 | mListView.setOnPositionChangedListener(this); 29 | } 30 | 31 | private class DummyAdapter extends BaseAdapter { 32 | 33 | private int mNumDummies = 100; 34 | 35 | @Override 36 | public int getCount() { 37 | return mNumDummies; 38 | } 39 | 40 | @Override 41 | public Object getItem(int position) { 42 | return position; 43 | } 44 | 45 | @Override 46 | public long getItemId(int position) { 47 | return position; 48 | } 49 | 50 | @Override 51 | public View getView(int position, View convertView, ViewGroup parent) { 52 | if (convertView == null) { 53 | convertView = LayoutInflater.from(DemoScrollBarPanelActivity.this).inflate(R.layout.list_item, parent, 54 | false); 55 | } 56 | 57 | TextView textView = (TextView) convertView; 58 | textView.setText("" + position); 59 | 60 | return convertView; 61 | } 62 | } 63 | 64 | @Override 65 | public void onPositionChanged(ExtendedListView listView, int firstVisiblePosition, View scrollBarPanel) { 66 | ((TextView) scrollBarPanel).setText("Position " + firstVisiblePosition); 67 | } 68 | } 69 | --------------------------------------------------------------------------------