Use fastscroll library
This commit is contained in:
@@ -44,6 +44,7 @@ import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.ViewAnimator;
|
||||
|
||||
import com.futuremind.recyclerviewfastscroll.FastScroller;
|
||||
import com.getbase.floatingactionbutton.FloatingActionButton;
|
||||
import com.getbase.floatingactionbutton.FloatingActionsMenu;
|
||||
import com.tonicartos.superslim.LayoutManager;
|
||||
@@ -268,6 +269,9 @@ public class KeyListFragment extends RecyclerFragment<KeySectionedListAdapter>
|
||||
setAdapter(adapter);
|
||||
setLayoutManager(new LayoutManager(getActivity()));
|
||||
|
||||
FastScroller fastScroller = (FastScroller) getActivity().findViewById(R.id.fastscroll);
|
||||
fastScroller.setRecyclerView(getRecyclerView());
|
||||
|
||||
// Prepare the loader. Either re-connect with an existing one,
|
||||
// or start a new one.
|
||||
getLoaderManager().initLoader(0, null, this);
|
||||
|
||||
@@ -15,6 +15,8 @@ import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.futuremind.recyclerviewfastscroll.SectionTitleProvider;
|
||||
|
||||
import org.openintents.openpgp.util.OpenPgpUtils;
|
||||
import org.sufficientlysecure.keychain.Constants;
|
||||
import org.sufficientlysecure.keychain.R;
|
||||
@@ -28,9 +30,10 @@ import org.sufficientlysecure.keychain.util.Log;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public class KeySectionedListAdapter extends SectionCursorAdapter<KeySectionedListAdapter.KeyListCursor, Character,
|
||||
SectionCursorAdapter.ViewHolder, KeySectionedListAdapter.KeyHeaderViewHolder> {
|
||||
SectionCursorAdapter.ViewHolder, KeySectionedListAdapter.KeyHeaderViewHolder> implements SectionTitleProvider {
|
||||
|
||||
private static final short VIEW_ITEM_TYPE_KEY = 0x0;
|
||||
private static final short VIEW_ITEM_TYPE_DUMMY = 0x1;
|
||||
@@ -61,7 +64,7 @@ public class KeySectionedListAdapter extends SectionCursorAdapter<KeySectionedLi
|
||||
mHasDummy = false;
|
||||
mSelected.clear();
|
||||
|
||||
if(mListener != null) {
|
||||
if (mListener != null) {
|
||||
mListener.onSelectionStateChanged(0);
|
||||
}
|
||||
|
||||
@@ -116,9 +119,9 @@ public class KeySectionedListAdapter extends SectionCursorAdapter<KeySectionedLi
|
||||
|
||||
public long[] getSelectedMasterKeyIds() {
|
||||
long[] keys = new long[mSelected.size()];
|
||||
for(int i = 0; i < keys.length; i++) {
|
||||
for (int i = 0; i < keys.length; i++) {
|
||||
int index = getCursorPositionWithoutSections(mSelected.get(i));
|
||||
if(!moveCursor(index)) {
|
||||
if (!moveCursor(index)) {
|
||||
return keys;
|
||||
}
|
||||
|
||||
@@ -129,13 +132,13 @@ public class KeySectionedListAdapter extends SectionCursorAdapter<KeySectionedLi
|
||||
}
|
||||
|
||||
public boolean isAnySecretKeySelected() {
|
||||
for(int i = 0; i < mSelected.size(); i++) {
|
||||
for (int i = 0; i < mSelected.size(); i++) {
|
||||
int index = getCursorPositionWithoutSections(mSelected.get(i));
|
||||
if(!moveCursor(index)) {
|
||||
if (!moveCursor(index)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(getCursor().isSecret()) {
|
||||
if (getCursor().isSecret()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -146,6 +149,7 @@ public class KeySectionedListAdapter extends SectionCursorAdapter<KeySectionedLi
|
||||
|
||||
/**
|
||||
* Returns the number of database entries displayed.
|
||||
*
|
||||
* @return The item count
|
||||
*/
|
||||
public int getCount() {
|
||||
@@ -171,7 +175,7 @@ public class KeySectionedListAdapter extends SectionCursorAdapter<KeySectionedLi
|
||||
return '#';
|
||||
} else {
|
||||
String userId = cursor.getRawUserId();
|
||||
if(TextUtils.isEmpty(userId)) {
|
||||
if (TextUtils.isEmpty(userId)) {
|
||||
return '?';
|
||||
} else {
|
||||
return Character.toUpperCase(userId.charAt(0));
|
||||
@@ -273,13 +277,40 @@ public class KeySectionedListAdapter extends SectionCursorAdapter<KeySectionedLi
|
||||
|
||||
mSelected.clear();
|
||||
|
||||
for(int i = 0; i < selected.length; i++) {
|
||||
notifyItemChanged(selected[i]);
|
||||
for (Integer aSelected : selected) {
|
||||
notifyItemChanged(aSelected);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSectionTitle(int position) {
|
||||
// this String will be shown in a bubble for specified position
|
||||
if (moveCursor(getCursorPositionWithoutSections(position))) {
|
||||
KeyListCursor cursor = getCursor();
|
||||
|
||||
if (cursor.isSecret()) {
|
||||
if (cursor.getKeyId() == 0L) {
|
||||
mHasDummy = true;
|
||||
}
|
||||
|
||||
return "My";
|
||||
} else {
|
||||
String userId = cursor.getRawUserId();
|
||||
if (TextUtils.isEmpty(userId)) {
|
||||
return null;
|
||||
} else {
|
||||
return userId.substring(0, 1);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Log.w(Constants.TAG, "Unable to determine section title. "
|
||||
+ "Reason: Could not move cursor over dataset.");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private class KeyDummyViewHolder extends SectionCursorAdapter.ViewHolder
|
||||
implements View.OnClickListener{
|
||||
implements View.OnClickListener {
|
||||
|
||||
KeyDummyViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
@@ -291,7 +322,7 @@ public class KeySectionedListAdapter extends SectionCursorAdapter<KeySectionedLi
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
if(mListener != null) {
|
||||
if (mListener != null) {
|
||||
mListener.onKeyDummyItemClicked();
|
||||
}
|
||||
}
|
||||
@@ -546,8 +577,11 @@ public class KeySectionedListAdapter extends SectionCursorAdapter<KeySectionedLi
|
||||
|
||||
public interface KeyListListener {
|
||||
void onKeyDummyItemClicked();
|
||||
|
||||
void onKeyItemClicked(long masterKeyId);
|
||||
|
||||
void onSlingerButtonClicked(long masterKeyId);
|
||||
|
||||
void onSelectionStateChanged(int selectedCount);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
package org.sufficientlysecure.keychain.ui.util.adapter;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.support.v4.util.SparseArrayCompat;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import com.tonicartos.superslim.LayoutManager;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
import java.util.Objects;
|
||||
import org.sufficientlysecure.keychain.util.Log;
|
||||
|
||||
/**
|
||||
* @param <T> section type.
|
||||
|
||||
@@ -1,119 +0,0 @@
|
||||
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);
|
||||
scrollBy(0, pixelsToScroll);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,268 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user