Skip to content

Commit 10c0d0c

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 69441f7 commit 10c0d0c

5 files changed

Lines changed: 251 additions & 0 deletions

File tree

Application/src/main/java/com/example/recyclerviewfastscroller/ui/scroller/AbsRecyclerViewFastScroller.java

Lines changed: 216 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

@@ -31,6 +33,14 @@
3133
public abstract class AbsRecyclerViewFastScroller extends FrameLayout implements RecyclerViewScroller {
3234

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

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

105+
@Override
106+
protected void onAttachedToWindow() {
107+
super.onAttachedToWindow();
108+
109+
mAutoHideProcessRunnable = new Runnable() {
110+
@Override
111+
public void run() {
112+
hideWithAnimation();
113+
}
114+
};
115+
if (!mFastScrollAlwaysVisible) {
116+
hide();
117+
}
118+
}
119+
120+
@Override
121+
protected void onDetachedFromWindow() {
122+
super.onDetachedFromWindow();
123+
124+
cancelAutoHideScrollerProcess();
125+
mAutoHideProcessRunnable = null;
126+
}
127+
86128
private void applyCustomAttributesToView(View view, Drawable drawable, int color) {
87129
if (drawable != null) {
88130
setViewBackground(view, drawable);
@@ -183,6 +225,13 @@ public OnScrollListener getOnScrollListener() {
183225
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
184226
float scrollProgress = getScrollProgressCalculator().calculateScrollProgress(recyclerView);
185227
moveHandleToPosition(scrollProgress);
228+
229+
if (!mFastScrollAlwaysVisible) {
230+
showWithAnimation();
231+
if (!mIsGrabbingHandle) {
232+
scheduleAutoHideScrollerProcess();
233+
}
234+
}
186235
}
187236
};
188237
}
@@ -207,6 +256,162 @@ protected void onLayout(boolean changed, int left, int top, int right, int botto
207256
*/
208257
protected abstract void onCreateScrollProgressCalculator();
209258

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+
* Set 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+
210415
/**
211416
* Takes a touch event and determines how much scroll progress this translates into
212417
* @param event touch event received by the layout
@@ -235,4 +440,15 @@ public float getScrollProgress(MotionEvent event) {
235440
*/
236441
public abstract void moveHandleToPosition(float scrollProgress);
237442

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();
238454
}

Application/src/main/java/com/example/recyclerviewfastscroller/ui/scroller/FastScrollerTouchListener.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import com.example.recyclerviewfastscroller.ui.scroller.vertical.VerticalRecyclerViewFastScroller;
55

66
import android.support.annotation.Nullable;
7+
import android.support.v4.view.MotionEventCompat;
78
import android.view.MotionEvent;
89
import android.view.View;
910
import android.view.View.OnTouchListener;
@@ -24,6 +25,17 @@ public FastScrollerTouchListener(AbsRecyclerViewFastScroller fastScroller) {
2425

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

Application/src/main/java/com/example/recyclerviewfastscroller/ui/scroller/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 com.example.android.recyclerview.R;
911
import com.example.recyclerviewfastscroller.ui.scroller.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)