package com.likebamboo.osa.android.ui.view.fastscroll; import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; import android.animation.ObjectAnimator; import android.annotation.TargetApi; import android.content.Context; import android.os.Build; import android.support.annotation.NonNull; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.util.AttributeSet; import android.util.Log; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.widget.LinearLayout; import android.widget.TextView; import com.likebamboo.osa.android.R; import static android.support.v7.widget.RecyclerView.OnScrollListener; /** * */ @TargetApi(Build.VERSION_CODES.HONEYCOMB) public class FastScroller extends LinearLayout { private static final int BUBBLE_ANIMATION_DURATION = 500; private static final int TRACK_SNAP_RANGE = 5; private TextView bubble; private View handle; private RecyclerView recyclerView; private final ScrollListener scrollListener = new ScrollListener(); private int height; private boolean isMoving = false; private ObjectAnimator currentAnimator = null; public FastScroller(final Context context, final AttributeSet attrs, final int defStyleAttr) { super(context, attrs, defStyleAttr); initialise(context); } public FastScroller(final Context context) { super(context); initialise(context); } public FastScroller(final Context context, final AttributeSet attrs) { super(context, attrs); initialise(context); } private void initialise(Context context) { setOrientation(HORIZONTAL); setClipChildren(false); LayoutInflater inflater = LayoutInflater.from(context); inflater.inflate(R.layout.recycler_view_fast_scroller, this, true); bubble = (TextView) findViewById(R.id.fastscroller_bubble); handle = findViewById(R.id.fastscroller_handle); bubble.setVisibility(INVISIBLE); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); height = h; } @Override public boolean onTouchEvent(@NonNull MotionEvent event) { final int action = event.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: isMoving = false; if (event.getX() < handle.getX()) return false; if (currentAnimator != null) currentAnimator.cancel(); if (bubble.getVisibility() == INVISIBLE) showBubble(); handle.setSelected(true); case MotionEvent.ACTION_MOVE: isMoving = true; final float y = event.getY(); setBubbleAndHandlePosition(y); setRecyclerViewPosition(y); return true; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: isMoving = false; handle.setSelected(false); hideBubble(); return true; } return super.onTouchEvent(event); } public void setRecyclerView(RecyclerView recyclerView) { this.recyclerView = recyclerView; recyclerView.addOnScrollListener(scrollListener); } private void setRecyclerViewPosition(float y) { if (recyclerView != null) { int itemCount = recyclerView.getAdapter().getItemCount(); float proportion; if (handle.getY() == 0) proportion = 0f; else if (handle.getY() + handle.getHeight() >= height - TRACK_SNAP_RANGE) proportion = 1f; else proportion = y / (float) height; int targetPos = getValueInRange(0, itemCount - 1, (int) (proportion * (float) itemCount)); Log.d("AppLog", "targetPos:" + targetPos); ((LinearLayoutManager) recyclerView.getLayoutManager()).scrollToPositionWithOffset(targetPos, 0); // recyclerView.oPositionWithOffset(targetPos); String bubbleText = ((BubbleTextGetter) recyclerView.getAdapter()).getTextToShowInBubble(targetPos); bubble.setText(bubbleText); } } private int getValueInRange(int min, int max, int value) { int minimum = Math.max(min, value); return Math.min(minimum, max); } private void setBubbleAndHandlePosition(float y) { int bubbleHeight = bubble.getHeight(); int handleHeight = handle.getHeight(); handle.setY(getValueInRange(0, height - handleHeight, (int) (y - handleHeight / 2))); bubble.setY(getValueInRange(0, height - bubbleHeight - handleHeight / 2, (int) (y - bubbleHeight))); } private void showBubble() { AnimatorSet animatorSet = new AnimatorSet(); bubble.setVisibility(VISIBLE); if (currentAnimator != null) currentAnimator.cancel(); currentAnimator = ObjectAnimator.ofFloat(bubble, "alpha", 0f, 1f).setDuration(BUBBLE_ANIMATION_DURATION); currentAnimator.start(); } private void hideBubble() { if (currentAnimator != null) currentAnimator.cancel(); currentAnimator = ObjectAnimator.ofFloat(bubble, "alpha", 1f, 0f).setDuration(BUBBLE_ANIMATION_DURATION); currentAnimator.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { super.onAnimationEnd(animation); bubble.setVisibility(INVISIBLE); currentAnimator = null; } @Override public void onAnimationCancel(Animator animation) { super.onAnimationCancel(animation); bubble.setVisibility(INVISIBLE); currentAnimator = null; } }); currentAnimator.start(); } private class ScrollListener extends OnScrollListener { @Override public void onScrolled(RecyclerView rv, int dx, int dy) { if (isMoving) { return; } View firstVisibleView = recyclerView.getChildAt(0); int firstVisiblePosition = recyclerView.getChildPosition(firstVisibleView); int visibleRange = recyclerView.getChildCount(); int lastVisiblePosition = firstVisiblePosition + visibleRange; int itemCount = recyclerView.getAdapter().getItemCount(); int position; if (firstVisiblePosition == 0) position = 0; else if (lastVisiblePosition == itemCount) position = itemCount; else position = (int) (((float) firstVisiblePosition / (((float) itemCount - (float) visibleRange))) * (float) itemCount); float proportion = (float) position / (float) itemCount; setBubbleAndHandlePosition(height * proportion); } } }