Skip to content

Commit 48c287e

Browse files
committed
Add automatically hide scroller when not scrolling. (default: automatically hide)
Introduced these public methods: - AbsRecyclerViewFastScroller.isFastScrollAlwaysVisible() - AbsRecyclerViewFastScroller.setFastScrollAlwaysVisible(boolean alwaysVisible) - AbsRecyclerViewFastScroller.setIsGrabbingHandle(boolean isGrabbingHandle)
1 parent 3157769 commit 48c287e

5 files changed

Lines changed: 252 additions & 0 deletions

File tree

recyclerviewfastscroller/src/main/java/xyz/danoz/recyclerviewfastscroller/AbsRecyclerViewFastScroller.java

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@
88
import android.os.Build.VERSION_CODES;
99
import android.support.annotation.NonNull;
1010
import android.support.annotation.Nullable;
11+
import android.support.v4.view.ViewCompat;
1112
import android.support.v7.widget.RecyclerView;
1213
import android.support.v7.widget.RecyclerView.OnScrollListener;
1314
import android.util.AttributeSet;
1415
import android.view.LayoutInflater;
1516
import android.view.MotionEvent;
1617
import android.view.View;
18+
import android.view.animation.Animation;
1719
import android.widget.FrameLayout;
1820
import android.widget.SectionIndexer;
1921

@@ -30,6 +32,14 @@
3032
public abstract class AbsRecyclerViewFastScroller extends FrameLayout implements RecyclerViewScroller {
3133

3234
private static final int[] STYLEABLE = R.styleable.AbsRecyclerViewFastScroller;
35+
36+
private static final int AUTO_HIDE_SCROLLER_TIMEOUT_MILLS = 1000;
37+
38+
private static final int CURRENT_ANIMATION_NONE = 0;
39+
private static final int CURRENT_ANIMATION_SHOW = 1;
40+
private static final int CURRENT_ANIMATION_HIDE = 2;
41+
42+
3343
/** The long bar along which a handle travels */
3444
protected final View mBar;
3545
/** The handle that signifies the user's progress in the list */
@@ -46,6 +56,15 @@ public abstract class AbsRecyclerViewFastScroller extends FrameLayout implements
4656
* {@link OnScrollListener} an abstract class instead of an interface. Hmmm */
4757
protected OnScrollListener mOnScrollListener;
4858

59+
/** true: user is grabbing the handle, false: otherwise */
60+
private boolean mIsGrabbingHandle;
61+
62+
/** true: always show the scroller, false: hide the scroller automatically */
63+
private boolean mFastScrollAlwaysVisible = false;
64+
/** Type of the view animation (CURRENT_ANIMATION_xxx) */
65+
private int mCurrentAnimationType = CURRENT_ANIMATION_NONE;
66+
private Runnable mAutoHideProcessRunnable;
67+
4968
public AbsRecyclerViewFastScroller(Context context) {
5069
this(context, null, 0);
5170
}
@@ -82,6 +101,29 @@ public AbsRecyclerViewFastScroller(Context context, AttributeSet attrs, int defS
82101
setOnTouchListener(new FastScrollerTouchListener(this));
83102
}
84103

104+
@Override
105+
protected void onAttachedToWindow() {
106+
super.onAttachedToWindow();
107+
108+
mAutoHideProcessRunnable = new Runnable() {
109+
@Override
110+
public void run() {
111+
hideWithAnimation();
112+
}
113+
};
114+
if (!mFastScrollAlwaysVisible) {
115+
hide();
116+
}
117+
}
118+
119+
@Override
120+
protected void onDetachedFromWindow() {
121+
super.onDetachedFromWindow();
122+
123+
cancelAutoHideScrollerProcess();
124+
mAutoHideProcessRunnable = null;
125+
}
126+
85127
private void applyCustomAttributesToView(View view, Drawable drawable, int color) {
86128
if (drawable != null) {
87129
setViewBackground(view, drawable);
@@ -182,6 +224,14 @@ public OnScrollListener getOnScrollListener() {
182224
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
183225
float scrollProgress = getScrollProgressCalculator().calculateScrollProgress(recyclerView);
184226
moveHandleToPosition(scrollProgress);
227+
228+
// show scroll bar
229+
if (!mFastScrollAlwaysVisible) {
230+
showWithAnimation();
231+
if (!mIsGrabbingHandle) {
232+
scheduleAutoHideScrollerProcess();
233+
}
234+
}
185235
}
186236
};
187237
}
@@ -206,6 +256,162 @@ protected void onLayout(boolean changed, int left, int top, int right, int botto
206256
*/
207257
protected abstract void onCreateScrollProgressCalculator();
208258

259+
/**
260+
* Returns true if the fast scroller is set to always show on this view.
261+
*
262+
* @return true if the fast scroller will always show
263+
*/
264+
public boolean isFastScrollAlwaysVisible() {
265+
return mFastScrollAlwaysVisible;
266+
}
267+
268+
/**
269+
* Sets whether or not the fast scroller should always be shown.
270+
*
271+
* @param alwaysVisible true if the fast scroller should always be displayed, false otherwise
272+
*/
273+
public void setFastScrollAlwaysVisible(boolean alwaysVisible) {
274+
if (mFastScrollAlwaysVisible == alwaysVisible) {
275+
return;
276+
}
277+
mFastScrollAlwaysVisible = alwaysVisible;
278+
if (mFastScrollAlwaysVisible) {
279+
show();
280+
} else {
281+
cancelAutoHideScrollerProcess();
282+
}
283+
}
284+
285+
private void scheduleAutoHideScrollerProcess() {
286+
cancelAutoHideScrollerProcess();
287+
288+
if (!mFastScrollAlwaysVisible) {
289+
postDelayed(mAutoHideProcessRunnable, AUTO_HIDE_SCROLLER_TIMEOUT_MILLS);
290+
}
291+
}
292+
293+
private void cancelAutoHideScrollerProcess() {
294+
removeCallbacks(mAutoHideProcessRunnable);
295+
}
296+
297+
private void show() {
298+
cancelAutoHideScrollerProcess();
299+
300+
if (getAnimation() != null) {
301+
getAnimation().cancel();
302+
}
303+
304+
ViewCompat.setTranslationX(this, 0);
305+
ViewCompat.setTranslationY(this, 0);
306+
307+
setVisibility(View.VISIBLE);
308+
}
309+
310+
private void hide() {
311+
cancelAutoHideScrollerProcess();
312+
313+
if (getAnimation() != null) {
314+
getAnimation().cancel();
315+
}
316+
setVisibility(View.INVISIBLE);
317+
}
318+
319+
private void showWithAnimation() {
320+
if ((mCurrentAnimationType == CURRENT_ANIMATION_SHOW) || (getVisibility() == View.VISIBLE)) {
321+
return;
322+
}
323+
324+
cancelAutoHideScrollerProcess();
325+
326+
if (getAnimation() != null) {
327+
getAnimation().cancel();
328+
}
329+
330+
final Animation anim = loadShowAnimation();
331+
332+
if (anim != null) {
333+
anim.setAnimationListener(new Animation.AnimationListener() {
334+
@Override
335+
public void onAnimationStart(Animation animation) {
336+
setVisibility(View.VISIBLE);
337+
}
338+
339+
@Override
340+
public void onAnimationEnd(Animation animation) {
341+
mCurrentAnimationType = CURRENT_ANIMATION_NONE;
342+
}
343+
344+
@Override
345+
public void onAnimationRepeat(Animation animation) {
346+
}
347+
});
348+
349+
mCurrentAnimationType = CURRENT_ANIMATION_SHOW;
350+
351+
startAnimation(anim);
352+
} else {
353+
show();
354+
}
355+
}
356+
357+
private void hideWithAnimation() {
358+
if ((mCurrentAnimationType == CURRENT_ANIMATION_HIDE) || (getVisibility() != View.VISIBLE)) {
359+
return;
360+
}
361+
362+
cancelAutoHideScrollerProcess();
363+
364+
if (getAnimation() != null) {
365+
getAnimation().cancel();
366+
}
367+
368+
final Animation anim = loadHideAnimation();
369+
370+
if (anim != null) {
371+
anim.setAnimationListener(new Animation.AnimationListener() {
372+
@Override
373+
public void onAnimationStart(Animation animation) {
374+
}
375+
376+
@Override
377+
public void onAnimationEnd(Animation animation) {
378+
mCurrentAnimationType = CURRENT_ANIMATION_NONE;
379+
setVisibility(View.INVISIBLE);
380+
}
381+
382+
@Override
383+
public void onAnimationRepeat(Animation animation) {
384+
}
385+
});
386+
387+
mCurrentAnimationType = CURRENT_ANIMATION_HIDE;
388+
389+
startAnimation(anim);
390+
} else {
391+
hide();
392+
}
393+
}
394+
395+
/**
396+
* Sets whether user is grabbing the scroller handle
397+
* @param isGrabbingHandle true: grabbing, false: not grabbing
398+
*/
399+
public void setIsGrabbingHandle(boolean isGrabbingHandle) {
400+
if (mIsGrabbingHandle == isGrabbingHandle) {
401+
return;
402+
}
403+
404+
mIsGrabbingHandle = isGrabbingHandle;
405+
406+
if (!mFastScrollAlwaysVisible) {
407+
if (isGrabbingHandle) {
408+
show();
409+
} else {
410+
scheduleAutoHideScrollerProcess();
411+
}
412+
}
413+
}
414+
209415
/**
210416
* Takes a touch event and determines how much scroll progress this translates into
211417
* @param event touch event received by the layout
@@ -234,4 +440,15 @@ public float getScrollProgress(MotionEvent event) {
234440
*/
235441
public abstract void moveHandleToPosition(float scrollProgress);
236442

443+
/**
444+
* Loads scroller show animation
445+
* @return animation which is used for the showWithAnimation() method
446+
*/
447+
protected abstract Animation loadShowAnimation();
448+
449+
/**
450+
* Loads scroller hide animation
451+
* @return animation which is used for the hideWithAnimation() method
452+
*/
453+
protected abstract Animation loadHideAnimation();
237454
}

recyclerviewfastscroller/src/main/java/xyz/danoz/recyclerviewfastscroller/FastScrollerTouchListener.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import xyz.danoz.recyclerviewfastscroller.sectionindicator.SectionIndicator;
44

55
import android.support.annotation.Nullable;
6+
import android.support.v4.view.MotionEventCompat;
67
import android.view.MotionEvent;
78
import android.view.View;
89
import android.view.View.OnTouchListener;
@@ -23,6 +24,17 @@ public FastScrollerTouchListener(AbsRecyclerViewFastScroller fastScroller) {
2324

2425
@Override
2526
public boolean onTouch(View v, MotionEvent event) {
27+
int action = MotionEventCompat.getActionMasked(event);
28+
29+
switch (action) {
30+
case MotionEvent.ACTION_DOWN:
31+
mFastScroller.setIsGrabbingHandle(true);
32+
break;
33+
case MotionEvent.ACTION_UP:
34+
mFastScroller.setIsGrabbingHandle(false);
35+
break;
36+
}
37+
2638
SectionIndicator sectionIndicator = mFastScroller.getSectionIndicator();
2739
showOrHideIndicator(sectionIndicator, event);
2840

recyclerviewfastscroller/src/main/java/xyz/danoz/recyclerviewfastscroller/vertical/VerticalRecyclerViewFastScroller.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
import android.support.v7.widget.LinearLayoutManager;
55
import android.support.v7.widget.RecyclerView;
66
import android.util.AttributeSet;
7+
import android.view.animation.Animation;
8+
import android.view.animation.AnimationUtils;
79

810
import xyz.danoz.recyclerviewfastscroller.R;
911
import xyz.danoz.recyclerviewfastscroller.AbsRecyclerViewFastScroller;
@@ -50,6 +52,17 @@ public void moveHandleToPosition(float scrollProgress) {
5052
mHandle.setY(mScreenPositionCalculator.getYPositionFromScrollProgress(scrollProgress));
5153
}
5254

55+
@Override
56+
protected Animation loadShowAnimation() {
57+
return AnimationUtils.loadAnimation(getContext(), R.anim.fast_scroller_slide_in_right);
58+
}
59+
60+
@Override
61+
protected Animation loadHideAnimation() {
62+
return AnimationUtils.loadAnimation(getContext(), R.anim.fast_scroller_slide_out_right);
63+
}
64+
65+
@Override
5366
protected void onCreateScrollProgressCalculator() {
5467
VerticalScrollBoundsProvider boundsProvider =
5568
new VerticalScrollBoundsProvider(mBar.getY(), mBar.getY() + mBar.getHeight() - mHandle.getHeight());
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<translate xmlns:android="http://schemas.android.com/apk/res/android"
3+
android:interpolator="@android:anim/accelerate_interpolator"
4+
android:fromXDelta="100%" android:toXDelta="0"
5+
android:duration="@android:integer/config_shortAnimTime"/>
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<translate xmlns:android="http://schemas.android.com/apk/res/android"
3+
android:interpolator="@android:anim/accelerate_interpolator"
4+
android:fromXDelta="0" android:toXDelta="100%"
5+
android:duration="@android:integer/config_shortAnimTime"/>

0 commit comments

Comments
 (0)