Added FastScroll capability to KeyListFragment. Removed unnecessary dependency.

This commit is contained in:
Tobias Erthal
2016-09-19 17:55:09 +02:00
parent fabd30f6b1
commit 5ec990d436
5 changed files with 405 additions and 7 deletions

View File

@@ -0,0 +1,122 @@
package org.sufficientlysecure.keychain.ui.util.recyclerview.fastscroll;
import android.content.Context;
import android.graphics.Canvas;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.StaggeredGridLayoutManager;
import android.util.AttributeSet;
import android.view.MotionEvent;
import com.tonicartos.superslim.LayoutManager;
public class FastScrollRecyclerView extends RecyclerView implements RecyclerView.OnItemTouchListener {
private FastScroller mFastScroller;
private int mLastX;
private int mLastY;
public FastScrollRecyclerView(Context context) {
this(context, null);
}
public FastScrollRecyclerView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public FastScrollRecyclerView(Context context, AttributeSet attributeSet, int defStyleAttr) {
super(context, attributeSet, defStyleAttr);
mFastScroller = new FastScroller(this, attributeSet);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
addOnItemTouchListener(this);
}
@Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
mFastScroller.draw(canvas);
}
@Override
public void onScrolled(int x, int y) {
super.onScrolled(x, y);
mFastScroller.updateThumb(
computeVerticalScrollOffset(),
computeVerticalScrollExtent());
}
@Override
public void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
mFastScroller.updateContainer(top, right, bottom);
mFastScroller.updateThumb(
computeVerticalScrollOffset(),
computeVerticalScrollExtent());
}
@Override
public void onScrollStateChanged(int state) {
switch (state) {
case SCROLL_STATE_IDLE:
mFastScroller.hideBar();
break;
case SCROLL_STATE_DRAGGING:
mFastScroller.showBar();
break;
}
}
@Override
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
if(mFastScroller.onInterceptTouchEvent(e)) {
onTouchEvent(rv, e);
return true;
} else {
return false;
}
}
@Override
public void onTouchEvent(RecyclerView rv, MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
mFastScroller.handleTouchEvent(event.getAction(), x, y, mLastX, mLastY);
mLastX = x;
mLastY = y;
}
@Override
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
}
private int getItemCount() {
return getAdapter() != null ?
getAdapter().getItemCount() : 0;
}
public void scrollToFraction(float fraction) {
int count = getItemCount();
if (count > 0) {
stopScroll();
scrollToPosition((int) ((count - 1) * fraction));
}
}
public void scrollByFraction(float fraction) {
int count = getItemCount();
if (count > 0) {
stopScroll();
int pixelsToScroll = (int) (computeVerticalScrollRange() * fraction);
System.out.println("ScrollBy Fraction: " + fraction + ", Pixel: " + pixelsToScroll);
scrollBy(0, pixelsToScroll);
}
}
}

View File

@@ -0,0 +1,268 @@
package org.sufficientlysecure.keychain.ui.util.recyclerview.fastscroll;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.support.v4.view.animation.FastOutLinearInInterpolator;
import android.support.v4.view.animation.FastOutSlowInInterpolator;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.ui.util.FormattingUtils;
public class FastScroller {
private static final int DEFAULT_AUTO_HIDE_DELAY = 1500;
private static final boolean DEFAULT_AUTO_HIDE_ENABLED = true;
private Paint mTrack;
private Paint mThumb;
private int mPosX;
private int mPosY;
private int mWidth;
private int mHeight;
private Rect mContainer;
private boolean mIsDragging;
private int mAutoHideDelay;
private boolean mAutoHideEnabled;
private final int mTrackColorNormal;
private final int mTrackColorDragging;
private final int mThumbColorNormal;
private final int mThumbColorDragging;
private ValueAnimator mBarAnimator;
private final FastScrollRecyclerView mRecyclerView;
private final int mTouchSlop;
private final int mTouchInset;
private final int[] mKeyFrames;
//private final Runnable mHideRunnable;
public FastScroller(final FastScrollRecyclerView recyclerView, AttributeSet attributeSet) {
Context context = recyclerView.getContext();
mRecyclerView = recyclerView;
mTrack = new Paint(Paint.ANTI_ALIAS_FLAG);
mThumb = new Paint(Paint.ANTI_ALIAS_FLAG);
mKeyFrames = new int[2];
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
mTouchInset = FormattingUtils.dpToPx(context, 8);
TypedArray typedArray = context.getTheme().obtainStyledAttributes(
attributeSet, R.styleable.FastScroller, 0, 0);
try {
mWidth = typedArray.getDimensionPixelSize(
R.styleable.FastScroller_fastScrollScrollBarWidth, FormattingUtils.dpToPx(context, 6));
mAutoHideDelay = typedArray.getInteger(
R.styleable.FastScroller_fastScrollAutoHideDelay, DEFAULT_AUTO_HIDE_DELAY);
mAutoHideEnabled = typedArray.getBoolean(
R.styleable.FastScroller_fastScrollAutoHideEnabled, DEFAULT_AUTO_HIDE_ENABLED);
mTrackColorNormal = typedArray.getColor(
R.styleable.FastScroller_fastScrollTrackColorNormal, Color.LTGRAY);
mTrackColorDragging = typedArray.getColor(
R.styleable.FastScroller_fastScrollTrackColorDragging, Color.GRAY);
mThumbColorNormal = typedArray.getColor(
R.styleable.FastScroller_fastScrollThumbColorNormal, Color.GRAY);
mThumbColorDragging = typedArray.getColor(
R.styleable.FastScroller_fastScrollThumbColorDragging, Color.BLUE);
mTrack.setColor(mTrackColorNormal);
mThumb.setColor(mThumbColorNormal);
} finally {
typedArray.recycle();
}
}
public void hideBar() {
if(mPosX >= mWidth || !mAutoHideEnabled) {
return;
}
mKeyFrames[0] = mPosX;
mKeyFrames[1] = mWidth;
prepareAnimator();
mBarAnimator.setIntValues(mKeyFrames);
mBarAnimator.setStartDelay(mAutoHideDelay);
mBarAnimator.setInterpolator(new FastOutLinearInInterpolator());
mBarAnimator.start();
}
public void showBar() {
if(mPosX < 1) {
return;
}
mKeyFrames[0] = mPosX;
mKeyFrames[1] = 0;
prepareAnimator();
mBarAnimator.setStartDelay(0);
mBarAnimator.setIntValues(mKeyFrames);
mBarAnimator.setInterpolator(new FastOutSlowInInterpolator());
mBarAnimator.start();
}
public boolean isAnimatingShow() {
return mKeyFrames[1] == 0 && mBarAnimator.isStarted();
}
public boolean isAnimatingHide() {
return mKeyFrames[1] == mWidth && mBarAnimator.isStarted();
}
public boolean isBarFullyShown() {
return mPosX < 1;
}
public boolean onInterceptTouchEvent(MotionEvent event) {
return mContainer != null
&& (event.getX() < mContainer.right)
&& (event.getX() > mContainer.left - mTouchInset)
&& (event.getY() < mContainer.bottom)
&& (event.getY() > mContainer.top);
}
private boolean isInThumb(int x, int y) {
return x > (mContainer.left + mPosX - mTouchInset)
&& x < (mContainer.right + mTouchInset)
&& y > (mContainer.top + mPosY - mTouchInset)
&& y < (mContainer.top + mPosY + mHeight + mTouchInset);
}
public void draw(Canvas canvas) {
if(mPosX < 0 || mPosX >= mWidth) {
return;
}
if(!mRecyclerView.canScrollVertically(-1)
&& ! mRecyclerView.canScrollVertically(1)) {
return;
}
int topBound = mContainer.top + mPosY;
int leftBound = mContainer.left + mPosX;
canvas.drawRect(leftBound, mContainer.top, mContainer.right, mContainer.bottom, mTrack);
canvas.drawRect(leftBound, topBound, mContainer.right, topBound + mHeight, mThumb);
}
public void updateThumb(int offset, int extent) {
mPosY = offset;
mHeight = extent;
}
public void updateContainer(int top, int right, int bottom) {
mPosX = 0;
mContainer = new Rect(right - mWidth, top, right, bottom);
invalidate();
}
private void invalidate() {
mRecyclerView.invalidate(mContainer);
}
private void prepareAnimator() {
if (mBarAnimator != null) {
mBarAnimator.cancel();
} else {
mBarAnimator = new ValueAnimator();
mBarAnimator.setDuration(150);
mBarAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
mPosX = (Integer) animation.getAnimatedValue();
invalidate();
}
});
}
}
public void handleTouchEvent(int action, int x, int y, int lastX, int lastY) {
if(!mRecyclerView.canScrollVertically(-1)
&& ! mRecyclerView.canScrollVertically(1)) {
return;
}
switch (action) {
case MotionEvent.ACTION_DOWN:
if(isBarFullyShown()) { //
prepareAnimator(); // cancel any pending animations
mTrack.setColor(mTrackColorDragging);
mThumb.setColor(mThumbColorDragging);
if(!isInThumb(x, y)) {
// jump to point
mPosY = Math.min(
Math.max(mContainer.top, y - (mHeight / 2)),
mContainer.bottom - mHeight
);
float range = (mContainer.bottom - mContainer.top) - mHeight;
mRecyclerView.scrollToFraction(mPosY / range);
} else {
invalidate();
}
} else {
if(!isAnimatingShow()) {
showBar();
}
}
break;
case MotionEvent.ACTION_MOVE:
if((!mIsDragging
&& isInThumb(x, y)
&& Math.abs(y - lastY) > mTouchSlop)
|| mIsDragging) {
if(!mIsDragging) {
mIsDragging = true;
}
float dist = y - lastY;
float range = (mContainer.bottom - mContainer.top) - mHeight;
if(mRecyclerView.canScrollVertically(dist < 0 ? -1 : 1)) {
mRecyclerView.scrollByFraction(dist / range);
}
}
break;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
if(mIsDragging) {
mIsDragging = false;
}
mTrack.setColor(mTrackColorNormal);
mThumb.setColor(mThumbColorNormal);
if(!mBarAnimator.isRunning()
&& mRecyclerView.getScrollState()
== RecyclerView.SCROLL_STATE_IDLE) {
hideBar();
invalidate();
}
break;
}
}
}