diff --git a/recyclerviewfastscroller/src/main/java/xyz/danoz/recyclerviewfastscroller/AbsRecyclerViewFastScroller.java b/recyclerviewfastscroller/src/main/java/xyz/danoz/recyclerviewfastscroller/AbsRecyclerViewFastScroller.java index 40d0e18..a3547c8 100644 --- a/recyclerviewfastscroller/src/main/java/xyz/danoz/recyclerviewfastscroller/AbsRecyclerViewFastScroller.java +++ b/recyclerviewfastscroller/src/main/java/xyz/danoz/recyclerviewfastscroller/AbsRecyclerViewFastScroller.java @@ -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; @@ -30,6 +32,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 */ @@ -46,6 +56,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); } @@ -82,6 +101,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); @@ -182,6 +224,14 @@ public OnScrollListener getOnScrollListener() { public void onScrolled(RecyclerView recyclerView, int dx, int dy) { float scrollProgress = getScrollProgressCalculator().calculateScrollProgress(recyclerView); moveHandleToPosition(scrollProgress); + + // show scroll bar + if (!mFastScrollAlwaysVisible) { + showWithAnimation(); + if (!mIsGrabbingHandle) { + scheduleAutoHideScrollerProcess(); + } + } } }; } @@ -206,6 +256,162 @@ protected void onLayout(boolean changed, int left, int top, int right, int botto */ protected abstract void onCreateScrollProgressCalculator(); + /** + * 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; + } + + /** + * Sets 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 @@ -234,4 +440,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(); } \ No newline at end of file diff --git a/recyclerviewfastscroller/src/main/java/xyz/danoz/recyclerviewfastscroller/FastScrollerTouchListener.java b/recyclerviewfastscroller/src/main/java/xyz/danoz/recyclerviewfastscroller/FastScrollerTouchListener.java index a4bc5db..33679fb 100644 --- a/recyclerviewfastscroller/src/main/java/xyz/danoz/recyclerviewfastscroller/FastScrollerTouchListener.java +++ b/recyclerviewfastscroller/src/main/java/xyz/danoz/recyclerviewfastscroller/FastScrollerTouchListener.java @@ -3,6 +3,7 @@ import xyz.danoz.recyclerviewfastscroller.sectionindicator.SectionIndicator; import android.support.annotation.Nullable; +import android.support.v4.view.MotionEventCompat; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; @@ -23,6 +24,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); diff --git a/recyclerviewfastscroller/src/main/java/xyz/danoz/recyclerviewfastscroller/vertical/VerticalRecyclerViewFastScroller.java b/recyclerviewfastscroller/src/main/java/xyz/danoz/recyclerviewfastscroller/vertical/VerticalRecyclerViewFastScroller.java index 3ce61c6..a1f1356 100644 --- a/recyclerviewfastscroller/src/main/java/xyz/danoz/recyclerviewfastscroller/vertical/VerticalRecyclerViewFastScroller.java +++ b/recyclerviewfastscroller/src/main/java/xyz/danoz/recyclerviewfastscroller/vertical/VerticalRecyclerViewFastScroller.java @@ -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 xyz.danoz.recyclerviewfastscroller.R; import xyz.danoz.recyclerviewfastscroller.AbsRecyclerViewFastScroller; @@ -50,6 +52,17 @@ 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 protected void onCreateScrollProgressCalculator() { VerticalScrollBoundsProvider boundsProvider = new VerticalScrollBoundsProvider(mBar.getY(), mBar.getY() + mBar.getHeight() - mHandle.getHeight()); diff --git a/recyclerviewfastscroller/src/main/res/anim/fast_scroller_slide_in_right.xml b/recyclerviewfastscroller/src/main/res/anim/fast_scroller_slide_in_right.xml new file mode 100644 index 0000000..b0d6d38 --- /dev/null +++ b/recyclerviewfastscroller/src/main/res/anim/fast_scroller_slide_in_right.xml @@ -0,0 +1,5 @@ + + \ No newline at end of file diff --git a/recyclerviewfastscroller/src/main/res/anim/fast_scroller_slide_out_right.xml b/recyclerviewfastscroller/src/main/res/anim/fast_scroller_slide_out_right.xml new file mode 100644 index 0000000..b7ea3cd --- /dev/null +++ b/recyclerviewfastscroller/src/main/res/anim/fast_scroller_slide_out_right.xml @@ -0,0 +1,5 @@ + + \ No newline at end of file