Use fastscroll library

This commit is contained in:
Dominik Schürmann
2016-11-30 15:07:16 +01:00
parent efb1868791
commit cecacb79e5
8 changed files with 116 additions and 464 deletions

View File

@@ -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);

View File

@@ -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);
}
}

View File

@@ -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.

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:fab="http://schemas.android.com/apk/res-auto"
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:fab="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
xmlns:custom="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
@@ -10,10 +10,10 @@
<LinearLayout
android:id="@android:id/progress"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center">
android:gravity="center"
android:orientation="vertical">
<ProgressBar
style="?android:attr/progressBarStyleLarge"
@@ -25,8 +25,8 @@
<RelativeLayout
android:id="@android:id/widget_frame"
android:layout_height="match_parent"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone">
<!--rebuild functionality of ListFragment -->
@@ -34,27 +34,37 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<org.sufficientlysecure.keychain.ui.util.recyclerview.fastscroll.FastScrollRecyclerView
android:id="@android:id/list"
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="16dp"
android:paddingStart="16dp"
android:paddingRight="32dp"
android:paddingEnd="32dp"
android:paddingBottom="72dp"
android:clipToPadding="false"
custom:fastScrollScrollBarWidth="8dp"
custom:fastScrollThumbColorNormal="@color/selected_gray"
custom:fastScrollThumbColorDragging="@color/accent" />
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:paddingLeft="16dp"
android:paddingRight="32dp"
android:paddingStart="16dp" />
<com.futuremind.recyclerviewfastscroll.FastScroller
android:id="@+id/fastscroll"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_alignParentRight="true"
android:orientation="vertical"
app:fastscroll__bubbleColor="@color/primary"
app:fastscroll__handleColor="@color/md_white_1000" />
</RelativeLayout>
<LinearLayout
android:id="@android:id/empty"
android:layout_width="match_parent"
android:layout_height="240dp"
android:animateLayoutChanges="true"
android:gravity="center"
android:orientation="vertical"
android:animateLayoutChanges="true" >
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
@@ -64,25 +74,25 @@
android:textAppearance="?android:attr/textAppearanceLarge" />
<org.sufficientlysecure.keychain.ui.widget.ToolableViewAnimator
android:id="@+id/search_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/search_container"
android:inAnimation="@anim/fade_in_delayed"
android:outAnimation="@anim/fade_out"
android:measureAllChildren="true"
custom:initialView="1">
android:outAnimation="@anim/fade_out"
app:initialView="1">
<Space
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<Button
android:id="@+id/search_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:id="@+id/search_button"
android:gravity="center"
tools:text="@string/btn_search_for_query"/>
tools:text="@string/btn_search_for_query" />
</org.sufficientlysecure.keychain.ui.widget.ToolableViewAnimator>
@@ -93,48 +103,48 @@
android:id="@+id/fab_main"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
android:layout_alignParentBottom="true"
android:layout_alignParentEnd="true"
android:layout_alignParentRight="true"
android:layout_marginBottom="8dp"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
fab:fab_addButtonColorNormal="?attr/colorPrimary"
fab:fab_addButtonColorPressed="?attr/colorPrimaryDark"
fab:fab_addButtonSize="normal"
fab:fab_addButtonPlusIconColor="@color/icons"
fab:fab_addButtonSize="normal"
fab:fab_expandDirection="up"
fab:fab_labelStyle="@style/FabMenuStyle"
android:layout_marginBottom="8dp"
android:layout_marginRight="16dp"
android:layout_marginEnd="16dp">
fab:fab_labelStyle="@style/FabMenuStyle">
<com.getbase.floatingactionbutton.FloatingActionButton
android:id="@+id/fab_add_qr_code"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
fab:fab_icon="@drawable/ic_qrcode_white_24dp"
fab:fab_colorNormal="?attr/colorPrimary"
fab:fab_colorPressed="?attr/colorPrimaryDark"
fab:fab_title="@string/key_list_fab_qr_code"
fab:fab_size="mini" />
fab:fab_icon="@drawable/ic_qrcode_white_24dp"
fab:fab_size="mini"
fab:fab_title="@string/key_list_fab_qr_code" />
<com.getbase.floatingactionbutton.FloatingActionButton
android:id="@+id/fab_add_cloud"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
fab:fab_icon="@drawable/ic_cloud_search_24dp"
fab:fab_colorNormal="?attr/colorPrimary"
fab:fab_colorPressed="?attr/colorPrimaryDark"
fab:fab_title="@string/key_list_fab_search"
fab:fab_size="mini" />
fab:fab_icon="@drawable/ic_cloud_search_24dp"
fab:fab_size="mini"
fab:fab_title="@string/key_list_fab_search" />
<com.getbase.floatingactionbutton.FloatingActionButton
android:id="@+id/fab_add_file"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
fab:fab_icon="@drawable/ic_folder_white_24dp"
fab:fab_colorNormal="?attr/colorPrimary"
fab:fab_colorPressed="?attr/colorPrimaryDark"
fab:fab_title="@string/key_list_fab_import"
fab:fab_size="mini" />
fab:fab_icon="@drawable/ic_folder_white_24dp"
fab:fab_size="mini"
fab:fab_title="@string/key_list_fab_import" />
</com.getbase.floatingactionbutton.FloatingActionsMenu>
</RelativeLayout>

View File

@@ -28,14 +28,4 @@
<attr name="prefix" format="string" />
</declare-styleable>
<declare-styleable name="FastScroller">
<attr name="fastScrollScrollBarWidth" format="dimension"/>
<attr name="fastScrollAutoHideEnabled" format="boolean" />
<attr name="fastScrollAutoHideDelay" format="integer" />
<attr name="fastScrollThumbColorNormal" format="color" />
<attr name="fastScrollTrackColorNormal" format="color" />
<attr name="fastScrollThumbColorDragging" format="color" />
<attr name="fastScrollTrackColorDragging" format="color" />
</declare-styleable>
</resources>