Skip to content

Commit

Permalink
Add automatically hide scroller when not scrolling. (default: automat…
Browse files Browse the repository at this point in the history
…ically hide)

Introduced these public methods:

- AbsRecyclerViewFastScroller.isFastScrollAlwaysVisible()
- AbsRecyclerViewFastScroller.setFastScrollAlwaysVisible(boolean alwaysVisible)
- AbsRecyclerViewFastScroller.setIsGrabbingHandle(boolean isGrabbingHandle)
  • Loading branch information
h6ah4i committed Feb 20, 2015
1 parent 023f492 commit af496d1
Show file tree
Hide file tree
Showing 5 changed files with 250 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@
import android.os.Build.VERSION_CODES;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.view.ViewCompat;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.OnScrollListener;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.Animation;
import android.widget.FrameLayout;
import android.widget.SectionIndexer;

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

private static final int[] STYLEABLE = R.styleable.AbsRecyclerViewFastScroller;

private static final int AUTO_HIDE_SCROLLER_TIMEOUT_MILLS = 1000;

private static final int CURRENT_ANIMATION_NONE = 0;
private static final int CURRENT_ANIMATION_SHOW = 1;
private static final int CURRENT_ANIMATION_HIDE = 2;


/** The long bar along which a handle travels */
protected final View mBar;
/** The handle that signifies the user's progress in the list */
Expand All @@ -47,6 +57,15 @@ public abstract class AbsRecyclerViewFastScroller extends FrameLayout implements
* {@link OnScrollListener} an abstract class instead of an interface. Hmmm */
protected OnScrollListener mOnScrollListener;

/** true: user is grabbing the handle, false: otherwise */
private boolean mIsGrabbingHandle;

/** true: always show the scroller, false: hide the scroller automatically */
private boolean mFastScrollAlwaysVisible = false;
/** Type of the view animation (CURRENT_ANIMATION_xxx) */
private int mCurrentAnimationType = CURRENT_ANIMATION_NONE;
private Runnable mAutoHideProcessRunnable;

public AbsRecyclerViewFastScroller(Context context) {
this(context, null, 0);
}
Expand Down Expand Up @@ -83,6 +102,29 @@ public AbsRecyclerViewFastScroller(Context context, AttributeSet attrs, int defS
setOnTouchListener(new FastScrollerTouchListener(this));
}

@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();

mAutoHideProcessRunnable = new Runnable() {
@Override
public void run() {
hideWithAnimation();
}
};
if (!mFastScrollAlwaysVisible) {
hide();
}
}

@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();

cancelAutoHideScrollerProcess();
mAutoHideProcessRunnable = null;
}

private void applyCustomAttributesToView(View view, Drawable drawable, int color) {
if (drawable != null) {
setViewBackground(view, drawable);
Expand Down Expand Up @@ -183,12 +225,175 @@ public OnScrollListener getOnScrollListener() {
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
float scrollProgress = getScrollProgressCalculator().calculateScrollProgress(recyclerView);
moveHandleToPosition(scrollProgress);

if (!mFastScrollAlwaysVisible) {
showWithAnimation();
if (!mIsGrabbingHandle) {
scheduleAutoHideScrollerProcess();
}
}
}
};
}
return mOnScrollListener;
}

/**
* Returns true if the fast scroller is set to always show on this view.
*
* @return true if the fast scroller will always show
*/
public boolean isFastScrollAlwaysVisible() {
return mFastScrollAlwaysVisible;
}

/**
* Set whether or not the fast scroller should always be shown.
*
* @param alwaysVisible true if the fast scroller should always be displayed, false otherwise
*/
public void setFastScrollAlwaysVisible(boolean alwaysVisible) {
if (mFastScrollAlwaysVisible == alwaysVisible) {
return;
}
mFastScrollAlwaysVisible = alwaysVisible;
if (mFastScrollAlwaysVisible) {
show();
} else {
cancelAutoHideScrollerProcess();
}
}

private void scheduleAutoHideScrollerProcess() {
cancelAutoHideScrollerProcess();

if (!mFastScrollAlwaysVisible) {
postDelayed(mAutoHideProcessRunnable, AUTO_HIDE_SCROLLER_TIMEOUT_MILLS);
}
}

private void cancelAutoHideScrollerProcess() {
removeCallbacks(mAutoHideProcessRunnable);
}

private void show() {
cancelAutoHideScrollerProcess();

if (getAnimation() != null) {
getAnimation().cancel();
}

ViewCompat.setTranslationX(this, 0);
ViewCompat.setTranslationY(this, 0);

setVisibility(View.VISIBLE);
}

private void hide() {
cancelAutoHideScrollerProcess();

if (getAnimation() != null) {
getAnimation().cancel();
}
setVisibility(View.INVISIBLE);
}

private void showWithAnimation() {
if ((mCurrentAnimationType == CURRENT_ANIMATION_SHOW) || (getVisibility() == View.VISIBLE)) {
return;
}

cancelAutoHideScrollerProcess();

if (getAnimation() != null) {
getAnimation().cancel();
}

final Animation anim = loadShowAnimation();

if (anim != null) {
anim.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
setVisibility(View.VISIBLE);
}

@Override
public void onAnimationEnd(Animation animation) {
mCurrentAnimationType = CURRENT_ANIMATION_NONE;
}

@Override
public void onAnimationRepeat(Animation animation) {
}
});

mCurrentAnimationType = CURRENT_ANIMATION_SHOW;

startAnimation(anim);
} else {
show();
}
}

private void hideWithAnimation() {
if ((mCurrentAnimationType == CURRENT_ANIMATION_HIDE) || (getVisibility() != View.VISIBLE)) {
return;
}

cancelAutoHideScrollerProcess();

if (getAnimation() != null) {
getAnimation().cancel();
}

final Animation anim = loadHideAnimation();

if (anim != null) {
anim.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}

@Override
public void onAnimationEnd(Animation animation) {
mCurrentAnimationType = CURRENT_ANIMATION_NONE;
setVisibility(View.INVISIBLE);
}

@Override
public void onAnimationRepeat(Animation animation) {
}
});

mCurrentAnimationType = CURRENT_ANIMATION_HIDE;

startAnimation(anim);
} else {
hide();
}
}

/**
* Sets whether user is grabbing the scroller handle
* @param isGrabbingHandle true: grabbing, false: not grabbing
*/
public void setIsGrabbingHandle(boolean isGrabbingHandle) {
if (mIsGrabbingHandle == isGrabbingHandle) {
return;
}

mIsGrabbingHandle = isGrabbingHandle;

if (!mFastScrollAlwaysVisible) {
if (isGrabbingHandle) {
show();
} else {
scheduleAutoHideScrollerProcess();
}
}
}

/**
* Takes a touch event and determines how much scroll progress this translates into
* @param event touch event received by the layout
Expand Down Expand Up @@ -217,4 +422,15 @@ public float getScrollProgress(MotionEvent event) {
*/
public abstract void moveHandleToPosition(float scrollProgress);

/**
* Loads scroller show animation
* @return animation which is used for the showWithAnimation() method
*/
protected abstract Animation loadShowAnimation();

/**
* Loads scroller hide animation
* @return animation which is used for the hideWithAnimation() method
*/
protected abstract Animation loadHideAnimation();
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.example.recyclerviewfastscroller.ui.scroller.vertical.VerticalRecyclerViewFastScroller;

import android.support.annotation.Nullable;
import android.support.v4.view.MotionEventCompat;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
Expand All @@ -24,6 +25,17 @@ public FastScrollerTouchListener(AbsRecyclerViewFastScroller fastScroller) {

@Override
public boolean onTouch(View v, MotionEvent event) {
int action = MotionEventCompat.getActionMasked(event);

switch (action) {
case MotionEvent.ACTION_DOWN:
mFastScroller.setIsGrabbingHandle(true);
break;
case MotionEvent.ACTION_UP:
mFastScroller.setIsGrabbingHandle(false);
break;
}

SectionIndicator sectionIndicator = mFastScroller.getSectionIndicator();
showOrHideIndicator(sectionIndicator, event);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;

import com.example.android.recyclerview.R;
import com.example.recyclerviewfastscroller.ui.scroller.AbsRecyclerViewFastScroller;
Expand Down Expand Up @@ -50,6 +52,16 @@ public void moveHandleToPosition(float scrollProgress) {
mHandle.setY(mScreenPositionCalculator.getYPositionFromScrollProgress(scrollProgress));
}

@Override
protected Animation loadShowAnimation() {
return AnimationUtils.loadAnimation(getContext(), R.anim.fast_scroller_slide_in_right);
}

@Override
protected Animation loadHideAnimation() {
return AnimationUtils.loadAnimation(getContext(), R.anim.fast_scroller_slide_out_right);
}

@Override
public void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/accelerate_interpolator"
android:fromXDelta="100%" android:toXDelta="0"
android:duration="@android:integer/config_shortAnimTime"/>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<translate xmlns:android="http://schemas.android.com/apk/res/android"
android:interpolator="@android:anim/accelerate_interpolator"
android:fromXDelta="0" android:toXDelta="100%"
android:duration="@android:integer/config_shortAnimTime"/>

0 comments on commit af496d1

Please sign in to comment.