Add E-Ink mode
This commit is contained in:
18 files changed
+2663
-9
No files matched your search
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,401 @@
|
||||
/*
|
||||
* Copyright 2015 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.easyrecyclerview;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Handler;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewConfiguration;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.hippo.ehviewer.R;
|
||||
import com.hippo.yorozuya.AnimationUtils;
|
||||
import com.hippo.yorozuya.LayoutUtils;
|
||||
import com.hippo.yorozuya.MathUtils;
|
||||
import com.hippo.yorozuya.SimpleAnimatorListener;
|
||||
import com.hippo.yorozuya.SimpleHandler;
|
||||
|
||||
public class FastScroller extends View {
|
||||
|
||||
private static final int INVALID = -1;
|
||||
|
||||
private static final int SCROLL_BAR_FADE_DURATION = 500;
|
||||
private static final int SCROLL_BAR_DELAY = 1000;
|
||||
|
||||
private static final int MIN_HANDLER_HEIGHT_DP = 32;
|
||||
|
||||
private Handler mSimpleHandler;
|
||||
|
||||
private boolean mDraggable;
|
||||
|
||||
private int mMinHandlerHeight;
|
||||
|
||||
private RecyclerView mRecyclerView;
|
||||
private RecyclerView.OnScrollListener mOnScrollChangeListener;
|
||||
private RecyclerView.Adapter mAdapter;
|
||||
private RecyclerView.AdapterDataObserver mAdapterDataObserver;
|
||||
|
||||
private Drawable mHandler;
|
||||
private int mHandlerOffset = INVALID;
|
||||
private int mHandlerHeight = INVALID;
|
||||
|
||||
private float mDownX = INVALID;
|
||||
private float mDownY = INVALID;
|
||||
|
||||
private float mLastMotionY = INVALID;
|
||||
|
||||
private boolean mDragged = false;
|
||||
|
||||
private boolean mCantDrag = false;
|
||||
|
||||
private int mTouchSlop;
|
||||
|
||||
private OnDragHandlerListener mListener;
|
||||
|
||||
private ObjectAnimator mShowAnimator;
|
||||
private ObjectAnimator mHideAnimator;
|
||||
|
||||
private final Runnable mHideRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mHideAnimator.start();
|
||||
}
|
||||
};
|
||||
|
||||
public FastScroller(Context context) {
|
||||
super(context);
|
||||
init(context, null, 0);
|
||||
}
|
||||
|
||||
public FastScroller(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init(context, attrs, 0);
|
||||
}
|
||||
|
||||
public FastScroller(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
private void init(Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
mSimpleHandler = SimpleHandler.getInstance();
|
||||
|
||||
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.FastScroller, defStyleAttr, 0);
|
||||
mHandler = a.getDrawable(R.styleable.FastScroller_handler);
|
||||
mDraggable = a.getBoolean(R.styleable.FastScroller_draggable, true);
|
||||
a.recycle();
|
||||
|
||||
setAlpha(0.0f);
|
||||
setVisibility(INVISIBLE);
|
||||
|
||||
mMinHandlerHeight = LayoutUtils.dp2pix(context, MIN_HANDLER_HEIGHT_DP);
|
||||
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
|
||||
|
||||
mShowAnimator = ObjectAnimator.ofFloat(this, "alpha", 1.0f);
|
||||
mShowAnimator.setInterpolator(AnimationUtils.FAST_SLOW_INTERPOLATOR);
|
||||
mShowAnimator.setDuration(SCROLL_BAR_FADE_DURATION);
|
||||
|
||||
mHideAnimator = ObjectAnimator.ofFloat(this, "alpha", 0.0f);
|
||||
mHideAnimator.setInterpolator(AnimationUtils.SLOW_FAST_INTERPOLATOR);
|
||||
mHideAnimator.setDuration(SCROLL_BAR_FADE_DURATION);
|
||||
mHideAnimator.addListener(new SimpleAnimatorListener() {
|
||||
|
||||
private boolean mCancel;
|
||||
|
||||
@Override
|
||||
public void onAnimationCancel(Animator animation) {
|
||||
mCancel = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
if (mCancel) {
|
||||
mCancel = false;
|
||||
} else {
|
||||
setVisibility(INVISIBLE);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void setOnDragHandlerListener(OnDragHandlerListener listener) {
|
||||
mListener = listener;
|
||||
}
|
||||
|
||||
private void updatePosition(boolean show) {
|
||||
if (mRecyclerView == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
int paddingTop = getPaddingTop();
|
||||
int paddingBottom = getPaddingBottom();
|
||||
int height = getHeight() - paddingTop - paddingBottom;
|
||||
int offset = mRecyclerView.computeVerticalScrollOffset();
|
||||
int extent = mRecyclerView.computeVerticalScrollExtent();
|
||||
int range = mRecyclerView.computeVerticalScrollRange();
|
||||
|
||||
if (height <= 0 || extent >= range || extent <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
int endOffset = height * offset / range;
|
||||
int endHeight = height * extent / range;
|
||||
|
||||
endHeight = Math.max(endHeight, mMinHandlerHeight);
|
||||
endOffset = Math.min(endOffset, height - endHeight);
|
||||
|
||||
mHandlerOffset = endOffset + paddingTop;
|
||||
mHandlerHeight = endHeight;
|
||||
|
||||
if (show) {
|
||||
if (mHideAnimator.isRunning()) {
|
||||
mHideAnimator.cancel();
|
||||
mShowAnimator.start();
|
||||
} else if (getVisibility() != VISIBLE && !mShowAnimator.isRunning()) {
|
||||
setVisibility(VISIBLE);
|
||||
mShowAnimator.start();
|
||||
}
|
||||
|
||||
Handler handler = mSimpleHandler;
|
||||
handler.removeCallbacks(mHideRunnable);
|
||||
|
||||
if (!mDragged) {
|
||||
handler.postDelayed(mHideRunnable, SCROLL_BAR_DELAY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setHandlerDrawable(Drawable drawable) {
|
||||
mHandler = drawable;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
public boolean isDraggable() {
|
||||
return mDraggable;
|
||||
}
|
||||
|
||||
public void setDraggable(boolean draggable) {
|
||||
mDraggable = draggable;
|
||||
if (mDragged) {
|
||||
mDragged = false;
|
||||
}
|
||||
mSimpleHandler.removeCallbacks(mHideRunnable);
|
||||
mHideRunnable.run();
|
||||
}
|
||||
|
||||
public boolean isAttached() {
|
||||
return mRecyclerView != null;
|
||||
}
|
||||
|
||||
public void attachToRecyclerView(RecyclerView recyclerView) {
|
||||
if (recyclerView == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mRecyclerView != null) {
|
||||
throw new IllegalStateException("The FastScroller is already attached to a RecyclerView, " +
|
||||
"call detachedFromRecyclerView first");
|
||||
}
|
||||
|
||||
mRecyclerView = recyclerView;
|
||||
mOnScrollChangeListener = new RecyclerView.OnScrollListener() {
|
||||
|
||||
@Override
|
||||
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
|
||||
updatePosition(true);
|
||||
invalidate();
|
||||
}
|
||||
};
|
||||
|
||||
recyclerView.addOnScrollListener(mOnScrollChangeListener);
|
||||
|
||||
mAdapter = recyclerView.getAdapter();
|
||||
if (mAdapter != null) {
|
||||
mAdapterDataObserver = new RecyclerView.AdapterDataObserver() {
|
||||
@Override
|
||||
public void onItemRangeChanged(int positionStart, int itemCount) {
|
||||
super.onItemRangeChanged(positionStart, itemCount);
|
||||
updatePosition(false);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
|
||||
super.onItemRangeChanged(positionStart, itemCount, payload);
|
||||
updatePosition(false);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemRangeInserted(int positionStart, int itemCount) {
|
||||
super.onItemRangeInserted(positionStart, itemCount);
|
||||
updatePosition(false);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemRangeRemoved(int positionStart, int itemCount) {
|
||||
super.onItemRangeRemoved(positionStart, itemCount);
|
||||
updatePosition(false);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
|
||||
super.onItemRangeMoved(fromPosition, toPosition, itemCount);
|
||||
updatePosition(false);
|
||||
invalidate();
|
||||
}
|
||||
};
|
||||
mAdapter.registerAdapterDataObserver(mAdapterDataObserver);
|
||||
}
|
||||
}
|
||||
|
||||
public void detachedFromRecyclerView() {
|
||||
if (mRecyclerView != null && mOnScrollChangeListener != null) {
|
||||
mRecyclerView.removeOnScrollListener(mOnScrollChangeListener);
|
||||
}
|
||||
mRecyclerView = null;
|
||||
mOnScrollChangeListener = null;
|
||||
|
||||
if (mAdapter != null && mAdapterDataObserver != null) {
|
||||
mAdapter.unregisterAdapterDataObserver(mAdapterDataObserver);
|
||||
}
|
||||
mAdapter = null;
|
||||
mAdapterDataObserver = null;
|
||||
|
||||
setAlpha(0.0f);
|
||||
setVisibility(INVISIBLE);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDraw(Canvas canvas) {
|
||||
if (mRecyclerView == null || mHandler == null) {
|
||||
return;
|
||||
}
|
||||
if (mHandlerHeight == INVALID) {
|
||||
updatePosition(false);
|
||||
}
|
||||
if (mHandlerHeight == INVALID) {
|
||||
return;
|
||||
}
|
||||
|
||||
int paddingLeft = getPaddingLeft();
|
||||
int saved = canvas.save();
|
||||
canvas.translate(paddingLeft, mHandlerOffset);
|
||||
mHandler.setBounds(0, 0, getWidth() - paddingLeft - getPaddingRight(), mHandlerHeight);
|
||||
mHandler.draw(canvas);
|
||||
canvas.restoreToCount(saved);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
if (!mDraggable || getVisibility() != VISIBLE || mRecyclerView == null || mHandlerHeight == INVALID) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int action = event.getAction();
|
||||
if (action == MotionEvent.ACTION_DOWN) {
|
||||
mCantDrag = false;
|
||||
}
|
||||
|
||||
if (mCantDrag) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
case MotionEvent.ACTION_DOWN: {
|
||||
mDragged = false;
|
||||
mDownX = event.getX();
|
||||
mDownY = event.getY();
|
||||
if (mDownY < mHandlerOffset || mDownY > mHandlerOffset + mHandlerHeight) {
|
||||
mCantDrag = true;
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MotionEvent.ACTION_MOVE: {
|
||||
if (!mDragged) {
|
||||
float x = event.getX();
|
||||
float y = event.getY();
|
||||
// Check touch slop
|
||||
if (MathUtils.dist(x, y, mDownX, mDownY) < mTouchSlop) {
|
||||
return true;
|
||||
}
|
||||
if (Math.abs(x - mDownX) > Math.abs(y - mDownY) ||
|
||||
y < mHandlerOffset || y > mHandlerOffset + mHandlerHeight) {
|
||||
mCantDrag = true;
|
||||
return false;
|
||||
} else {
|
||||
mDragged = true;
|
||||
mSimpleHandler.removeCallbacks(mHideRunnable);
|
||||
// Update mLastMotionY
|
||||
if (mDownY < mHandlerOffset || mDownY >= mHandlerOffset + mHandlerHeight) {
|
||||
// the point out of handler, make the point in handler center
|
||||
mLastMotionY = mHandlerOffset + mHandlerHeight / 2;
|
||||
} else {
|
||||
mLastMotionY = mDownY;
|
||||
}
|
||||
// Notify
|
||||
if (mListener != null) {
|
||||
mListener.onStartDragHandler();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int range = mRecyclerView.computeVerticalScrollRange();
|
||||
if (range <= 0) {
|
||||
break;
|
||||
}
|
||||
float y = event.getY();
|
||||
int scroll = (int) (range * (y - mLastMotionY) / (getHeight() - getPaddingTop() - getPaddingBottom()));
|
||||
mRecyclerView.scrollBy(0, scroll);
|
||||
mLastMotionY = y;
|
||||
break;
|
||||
}
|
||||
case MotionEvent.ACTION_UP:
|
||||
case MotionEvent.ACTION_CANCEL:
|
||||
// Notify
|
||||
if (mDragged && mListener != null) {
|
||||
mListener.onEndDragHandler();
|
||||
}
|
||||
mDragged = false;
|
||||
mSimpleHandler.postDelayed(mHideRunnable, SCROLL_BAR_DELAY);
|
||||
break;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public interface OnDragHandlerListener {
|
||||
void onStartDragHandler();
|
||||
|
||||
void onEndDragHandler();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright 2015 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.easyrecyclerview;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.ColorFilter;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.drawable.Drawable;
|
||||
|
||||
public class HandlerDrawable extends Drawable {
|
||||
|
||||
private int mColor = Color.BLACK;
|
||||
private final Paint mPaint;
|
||||
private final RectF mTemp = new RectF();
|
||||
|
||||
public HandlerDrawable() {
|
||||
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
mPaint.setColor(mColor);
|
||||
mPaint.setStyle(Paint.Style.FILL);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Canvas canvas) {
|
||||
int width = getBounds().width();
|
||||
int height = getBounds().height();
|
||||
|
||||
if (width > height) {
|
||||
canvas.drawRect(0, 0, width, height, mPaint);
|
||||
} else {
|
||||
mTemp.set(0, 0, width, width);
|
||||
canvas.drawArc(mTemp, -180, 180, true, mPaint);
|
||||
mTemp.set(0, height - width, width, height);
|
||||
canvas.drawArc(mTemp, 0, 180, true, mPaint);
|
||||
float halfWidth = (float) width / 2.0f;
|
||||
canvas.drawRect(0, halfWidth, width, height - halfWidth, mPaint);
|
||||
}
|
||||
}
|
||||
|
||||
public void setColor(int color) {
|
||||
if (mColor != color) {
|
||||
mColor = color;
|
||||
mPaint.setColor(color);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setAlpha(int alpha) {
|
||||
mPaint.setAlpha(alpha);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setColorFilter(ColorFilter colorFilter) {
|
||||
mPaint.setColorFilter(colorFilter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getOpacity() {
|
||||
int alpha = Color.alpha(mColor);
|
||||
if (alpha == 0xff) {
|
||||
return PixelFormat.OPAQUE;
|
||||
} else if (alpha == 0x00) {
|
||||
return PixelFormat.TRANSPARENT;
|
||||
} else {
|
||||
return PixelFormat.TRANSLUCENT;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
* Copyright 2015 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.easyrecyclerview;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.PointF;
|
||||
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.recyclerview.widget.StaggeredGridLayoutManager;
|
||||
|
||||
import com.hippo.yorozuya.MathUtils;
|
||||
import com.hippo.yorozuya.SimpleHandler;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
public final class LayoutManagerUtils {
|
||||
|
||||
private static Method sCsdfp = null;
|
||||
|
||||
static {
|
||||
try {
|
||||
sCsdfp = StaggeredGridLayoutManager.class.getDeclaredMethod("calculateScrollDirectionForPosition", int.class);
|
||||
sCsdfp.setAccessible(true);
|
||||
} catch (NoSuchMethodException e) {
|
||||
// Ignore
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public static void scrollToPositionWithOffset(
|
||||
RecyclerView.LayoutManager layoutManager, int position, int offset) {
|
||||
if (layoutManager instanceof LinearLayoutManager) {
|
||||
((LinearLayoutManager) layoutManager).scrollToPositionWithOffset(position, offset);
|
||||
} else if (layoutManager instanceof StaggeredGridLayoutManager) {
|
||||
((StaggeredGridLayoutManager) layoutManager).scrollToPositionWithOffset(position, offset);
|
||||
} else {
|
||||
throw new IllegalStateException("Can't do scrollToPositionWithOffset for " +
|
||||
layoutManager.getClass().getName());
|
||||
}
|
||||
}
|
||||
|
||||
public static void smoothScrollToPosition(
|
||||
RecyclerView.LayoutManager layoutManager, Context context, int position) {
|
||||
smoothScrollToPosition(layoutManager, context, position, -1);
|
||||
}
|
||||
|
||||
public static void smoothScrollToPosition(
|
||||
RecyclerView.LayoutManager layoutManager, Context context, int position,
|
||||
int millisecondsPerInch) {
|
||||
SimpleSmoothScroller smoothScroller;
|
||||
if (layoutManager instanceof LinearLayoutManager) {
|
||||
final LinearLayoutManager linearLayoutManager = (LinearLayoutManager) layoutManager;
|
||||
smoothScroller = new SimpleSmoothScroller(context, millisecondsPerInch) {
|
||||
@Override
|
||||
public PointF computeScrollVectorForPosition(int targetPosition) {
|
||||
return linearLayoutManager.computeScrollVectorForPosition(targetPosition);
|
||||
}
|
||||
};
|
||||
|
||||
} else if (layoutManager instanceof StaggeredGridLayoutManager) {
|
||||
final StaggeredGridLayoutManager staggeredGridLayoutManager = (StaggeredGridLayoutManager) layoutManager;
|
||||
smoothScroller = new SimpleSmoothScroller(context, millisecondsPerInch) {
|
||||
@Override
|
||||
public PointF computeScrollVectorForPosition(int targetPosition) {
|
||||
int direction = 0;
|
||||
try {
|
||||
direction = (Integer) sCsdfp.invoke(staggeredGridLayoutManager, targetPosition);
|
||||
} catch (IllegalAccessException e) {
|
||||
e.printStackTrace();
|
||||
} catch (InvocationTargetException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
if (direction == 0) {
|
||||
return null;
|
||||
}
|
||||
if (staggeredGridLayoutManager.getOrientation() == StaggeredGridLayoutManager.HORIZONTAL) {
|
||||
return new PointF(direction, 0);
|
||||
} else {
|
||||
return new PointF(0, direction);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} else {
|
||||
throw new IllegalStateException("Can't do smoothScrollToPosition for " +
|
||||
layoutManager.getClass().getName());
|
||||
}
|
||||
smoothScroller.setTargetPosition(position);
|
||||
layoutManager.startSmoothScroll(smoothScroller);
|
||||
}
|
||||
|
||||
public static void scrollToPositionProperly(final RecyclerView.LayoutManager layoutManager,
|
||||
final Context context, final int position, final OnScrollToPositionListener listener) {
|
||||
SimpleHandler.getInstance().postDelayed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
int first = getFirstVisibleItemPosition(layoutManager);
|
||||
int last = getLastVisibleItemPosition(layoutManager);
|
||||
int offset = Math.abs(position - first);
|
||||
int max = last - first;
|
||||
if (offset < max && max > 0) {
|
||||
smoothScrollToPosition(layoutManager, context, position,
|
||||
MathUtils.lerp(100, 25, (offset / max)));
|
||||
} else {
|
||||
scrollToPositionWithOffset(layoutManager, position, 0);
|
||||
if (listener != null) {
|
||||
listener.onScrollToPosition(position);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 200);
|
||||
}
|
||||
|
||||
public static int getFirstVisibleItemPosition(RecyclerView.LayoutManager layoutManager) {
|
||||
if (layoutManager instanceof LinearLayoutManager) {
|
||||
return ((LinearLayoutManager) layoutManager).findFirstVisibleItemPosition();
|
||||
} else if (layoutManager instanceof StaggeredGridLayoutManager) {
|
||||
int[] positions = ((StaggeredGridLayoutManager) layoutManager).findFirstVisibleItemPositions(null);
|
||||
return MathUtils.min(positions);
|
||||
} else {
|
||||
throw new IllegalStateException("Can't do getFirstVisibleItemPosition for " +
|
||||
layoutManager.getClass().getName());
|
||||
}
|
||||
}
|
||||
|
||||
public static int getLastVisibleItemPosition(RecyclerView.LayoutManager layoutManager) {
|
||||
if (layoutManager instanceof LinearLayoutManager) {
|
||||
return ((LinearLayoutManager) layoutManager).findLastVisibleItemPosition();
|
||||
} else if (layoutManager instanceof StaggeredGridLayoutManager) {
|
||||
int[] positions = ((StaggeredGridLayoutManager) layoutManager).findLastVisibleItemPositions(null);
|
||||
return MathUtils.max(positions);
|
||||
} else {
|
||||
throw new IllegalStateException("Can't do getLastVisibleItemPosition for " +
|
||||
layoutManager.getClass().getName());
|
||||
}
|
||||
}
|
||||
|
||||
public interface OnScrollToPositionListener {
|
||||
void onScrollToPosition(int position);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,274 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.easyrecyclerview;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.core.view.ViewCompat;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
// Add overlap
|
||||
|
||||
/**
|
||||
* Only work for {@link androidx.recyclerview.widget.LinearLayoutManager}.<br>
|
||||
* Show divider between item, just like
|
||||
* {@link android.widget.ListView#setDivider(android.graphics.drawable.Drawable)}
|
||||
*/
|
||||
public class LinearDividerItemDecoration extends RecyclerView.ItemDecoration {
|
||||
public static final int HORIZONTAL = LinearLayoutManager.HORIZONTAL;
|
||||
public static final int VERTICAL = LinearLayoutManager.VERTICAL;
|
||||
|
||||
private boolean mShowFirstDivider = false;
|
||||
private boolean mShowLastDivider = false;
|
||||
|
||||
private final Rect mRect;
|
||||
private final Paint mPaint;
|
||||
|
||||
private int mOrientation;
|
||||
private int mThickness;
|
||||
private int mPaddingStart = 0;
|
||||
private int mPaddingEnd = 0;
|
||||
|
||||
private boolean mOverlap = false;
|
||||
|
||||
private ShowDividerHelper mShowDividerHelper;
|
||||
|
||||
public LinearDividerItemDecoration(int orientation, int color, int thickness) {
|
||||
mRect = new Rect();
|
||||
mPaint = new Paint();
|
||||
mPaint.setStyle(Paint.Style.FILL);
|
||||
setOrientation(orientation);
|
||||
setColor(color);
|
||||
setThickness(thickness);
|
||||
}
|
||||
|
||||
public void setShowDividerHelper(ShowDividerHelper showDividerHelper) {
|
||||
mShowDividerHelper = showDividerHelper;
|
||||
}
|
||||
|
||||
public void setOrientation(int orientation) {
|
||||
if (orientation != HORIZONTAL && orientation != VERTICAL) {
|
||||
throw new IllegalArgumentException("invalid orientation");
|
||||
}
|
||||
mOrientation = orientation;
|
||||
}
|
||||
|
||||
public void setColor(int color) {
|
||||
mPaint.setColor(color);
|
||||
}
|
||||
|
||||
public void setThickness(int thickness) {
|
||||
mThickness = thickness;
|
||||
}
|
||||
|
||||
public void setShowFirstDivider(boolean showFirstDivider) {
|
||||
mShowFirstDivider = showFirstDivider;
|
||||
}
|
||||
|
||||
public void setShowLastDivider(boolean showLastDivider) {
|
||||
mShowLastDivider = showLastDivider;
|
||||
}
|
||||
|
||||
public void setPadding(int padding) {
|
||||
setPaddingStart(padding);
|
||||
setPaddingEnd(padding);
|
||||
}
|
||||
|
||||
public void setPaddingStart(int paddingStart) {
|
||||
mPaddingStart = paddingStart;
|
||||
}
|
||||
|
||||
public void setPaddingEnd(int paddingEnd) {
|
||||
mPaddingEnd = paddingEnd;
|
||||
}
|
||||
|
||||
public void setOverlap(boolean overlap) {
|
||||
mOverlap = overlap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getItemOffsets(Rect outRect, View view,
|
||||
RecyclerView parent, RecyclerView.State state) {
|
||||
if (parent.getAdapter() == null) {
|
||||
outRect.set(0, 0, 0, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (mOverlap) {
|
||||
outRect.set(0, 0, 0, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
final int position = parent.getChildLayoutPosition(view);
|
||||
final int itemCount = parent.getAdapter().getItemCount();
|
||||
|
||||
if (mShowDividerHelper != null) {
|
||||
if (mOrientation == VERTICAL) {
|
||||
if (position == 0 && mShowDividerHelper.showDivider(0)) {
|
||||
outRect.top = mThickness;
|
||||
}
|
||||
if (mShowDividerHelper.showDivider(position + 1)) {
|
||||
outRect.bottom = mThickness;
|
||||
}
|
||||
} else {
|
||||
if (position == 0 && mShowDividerHelper.showDivider(0)) {
|
||||
outRect.left = mThickness;
|
||||
}
|
||||
if (mShowDividerHelper.showDivider(position + 1)) {
|
||||
outRect.right = mThickness;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (mOrientation == VERTICAL) {
|
||||
if (position == 0 && mShowFirstDivider) {
|
||||
outRect.top = mThickness;
|
||||
}
|
||||
outRect.bottom = mThickness;
|
||||
if (position == itemCount - 1 && !mShowLastDivider) {
|
||||
outRect.bottom = 0;
|
||||
}
|
||||
} else {
|
||||
if (position == 0 && mShowFirstDivider) {
|
||||
outRect.left = mThickness;
|
||||
}
|
||||
outRect.right = mThickness;
|
||||
if (position == itemCount - 1 && !mShowLastDivider) {
|
||||
outRect.right = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDrawOver(Canvas c, RecyclerView parent,
|
||||
RecyclerView.State state) {
|
||||
RecyclerView.Adapter adapter = parent.getAdapter();
|
||||
if (adapter == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
int itemCount = adapter.getItemCount();
|
||||
boolean overlap = mOverlap;
|
||||
|
||||
if (mOrientation == VERTICAL) {
|
||||
final boolean isRtl = ViewCompat.getLayoutDirection(parent) == ViewCompat.LAYOUT_DIRECTION_RTL;
|
||||
int mPaddingLeft;
|
||||
int mPaddingRight;
|
||||
if (isRtl) {
|
||||
mPaddingLeft = mPaddingEnd;
|
||||
mPaddingRight = mPaddingStart;
|
||||
} else {
|
||||
mPaddingLeft = mPaddingStart;
|
||||
mPaddingRight = mPaddingEnd;
|
||||
}
|
||||
|
||||
final int left = parent.getPaddingLeft() + mPaddingLeft;
|
||||
final int right = parent.getWidth() - parent.getPaddingRight() - mPaddingRight;
|
||||
final int childCount = parent.getChildCount();
|
||||
|
||||
for (int i = 0; i < childCount; i++) {
|
||||
final View child = parent.getChildAt(i);
|
||||
final RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) child.getLayoutParams();
|
||||
final int position = parent.getChildLayoutPosition(child);
|
||||
|
||||
boolean show;
|
||||
if (mShowDividerHelper != null) {
|
||||
show = mShowDividerHelper.showDivider(position + 1);
|
||||
} else {
|
||||
show = position != itemCount - 1 || mShowLastDivider;
|
||||
}
|
||||
if (show) {
|
||||
int top = child.getBottom() + lp.bottomMargin;
|
||||
if (overlap) {
|
||||
top -= mThickness;
|
||||
}
|
||||
final int bottom = top + mThickness;
|
||||
mRect.set(left, top, right, bottom);
|
||||
c.drawRect(mRect, mPaint);
|
||||
}
|
||||
|
||||
if (position == 0) {
|
||||
if (mShowDividerHelper != null) {
|
||||
show = mShowDividerHelper.showDivider(0);
|
||||
} else {
|
||||
show = mShowFirstDivider;
|
||||
}
|
||||
if (show) {
|
||||
int bottom = child.getTop() + lp.topMargin;
|
||||
if (overlap) {
|
||||
bottom += mThickness;
|
||||
}
|
||||
final int top = bottom - mThickness;
|
||||
mRect.set(left, top, right, bottom);
|
||||
c.drawRect(mRect, mPaint);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
final int top = parent.getPaddingTop() + mPaddingStart;
|
||||
final int bottom = parent.getHeight() - parent.getPaddingBottom() - mPaddingEnd;
|
||||
final int childCount = parent.getChildCount();
|
||||
|
||||
for (int i = 0; i < childCount; i++) {
|
||||
final View child = parent.getChildAt(i);
|
||||
final RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) child.getLayoutParams();
|
||||
final int position = parent.getChildLayoutPosition(child);
|
||||
|
||||
boolean show;
|
||||
if (mShowDividerHelper != null) {
|
||||
show = mShowDividerHelper.showDivider(position + 1);
|
||||
} else {
|
||||
show = position != itemCount - 1 || mShowLastDivider;
|
||||
}
|
||||
if (show) {
|
||||
int left = child.getRight() + lp.rightMargin;
|
||||
if (overlap) {
|
||||
left -= mThickness;
|
||||
}
|
||||
final int right = left + mThickness;
|
||||
mRect.set(left, top, right, bottom);
|
||||
c.drawRect(mRect, mPaint);
|
||||
}
|
||||
|
||||
if (position == 0) {
|
||||
if (mShowDividerHelper != null) {
|
||||
show = mShowDividerHelper.showDivider(0);
|
||||
} else {
|
||||
show = mShowFirstDivider;
|
||||
}
|
||||
if (show) {
|
||||
int right = child.getLeft() + lp.leftMargin;
|
||||
if (overlap) {
|
||||
right += mThickness;
|
||||
}
|
||||
final int left = right - mThickness;
|
||||
mRect.set(left, top, right, bottom);
|
||||
c.drawRect(mRect, mPaint);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public interface ShowDividerHelper {
|
||||
boolean showDivider(int index);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.easyrecyclerview;
|
||||
|
||||
import android.graphics.Rect;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
public class MarginItemDecoration extends RecyclerView.ItemDecoration {
|
||||
|
||||
private int mMargin;
|
||||
private int mPaddingLeft;
|
||||
private int mPaddingTop;
|
||||
private int mPaddingRight;
|
||||
private int mPaddingBottom;
|
||||
|
||||
public MarginItemDecoration(int margin, int paddingLeft, int paddingTop,
|
||||
int paddingRight, int paddingBottom) {
|
||||
setMargin(margin, paddingLeft, paddingTop, paddingRight, paddingBottom);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getItemOffsets(Rect outRect, View view,
|
||||
RecyclerView parent, RecyclerView.State state) {
|
||||
outRect.set(mMargin, mMargin, mMargin, mMargin);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param margin gap between two item
|
||||
* @param paddingLeft gap between RecyclerView left and left item left
|
||||
* @param paddingTop gap between RecyclerView top and top item top
|
||||
* @param paddingRight gap between RecyclerView right and right item right
|
||||
* @param paddingBottom gap between RecyclerView bottom and bottom item bottom
|
||||
*/
|
||||
public void setMargin(int margin, int paddingLeft, int paddingTop,
|
||||
int paddingRight, int paddingBottom) {
|
||||
int halfMargin = margin / 2;
|
||||
mMargin = halfMargin;
|
||||
mPaddingLeft = paddingLeft - halfMargin;
|
||||
mPaddingTop = paddingTop - halfMargin;
|
||||
mPaddingRight = paddingRight - halfMargin;
|
||||
mPaddingBottom = paddingBottom - halfMargin;
|
||||
}
|
||||
|
||||
public void applyPaddings(View view) {
|
||||
view.setPadding(mPaddingLeft, mPaddingTop, mPaddingRight, mPaddingBottom);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 2016 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.easyrecyclerview;
|
||||
|
||||
import android.graphics.Rect;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
public class RawMarginItemDecoration extends RecyclerView.ItemDecoration {
|
||||
|
||||
private int mMarginLeft;
|
||||
private int mMarginTop;
|
||||
private int mMarginRight;
|
||||
private int mMarginBottom;
|
||||
|
||||
public RawMarginItemDecoration(int marginLeft, int marginTop,
|
||||
int marginRight, int marginBottom) {
|
||||
setMargin(marginLeft, marginTop, marginRight, marginBottom);
|
||||
}
|
||||
|
||||
public void setMargin(int marginLeft, int marginTop,
|
||||
int marginRight, int marginBottom) {
|
||||
mMarginLeft = marginLeft;
|
||||
mMarginTop = marginTop;
|
||||
mMarginRight = marginRight;
|
||||
mMarginBottom = marginBottom;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getItemOffsets(Rect outRect, View view,
|
||||
RecyclerView parent, RecyclerView.State state) {
|
||||
outRect.set(mMarginLeft, mMarginTop, mMarginRight, mMarginBottom);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.easyrecyclerview;
|
||||
|
||||
import android.view.View;
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
public class SimpleHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
public SimpleHolder(View itemView) {
|
||||
super(itemView);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.hippo.easyrecyclerview;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.recyclerview.widget.LinearSmoothScroller;
|
||||
|
||||
public abstract class SimpleSmoothScroller extends LinearSmoothScroller {
|
||||
|
||||
private final float mMillisecondsPerPx;
|
||||
|
||||
public SimpleSmoothScroller(Context context, float millisecondsPerInch) {
|
||||
super(context);
|
||||
mMillisecondsPerPx = millisecondsPerInch / context.getResources().getDisplayMetrics().densityDpi;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int calculateTimeForScrolling(int dx) {
|
||||
if (mMillisecondsPerPx <= 0) {
|
||||
return super.calculateTimeForScrolling(dx);
|
||||
} else {
|
||||
return (int) Math.ceil(Math.abs(dx) * mMillisecondsPerPx);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -133,6 +133,8 @@ public class Settings {
|
||||
private static final boolean DEFAULT_CELLULAR_NETWORK_WARNING = false;
|
||||
private static final String KEY_NIGHT_MODE = "night_mode";
|
||||
private static final String DEFAULT_NIGHT_MODE = "-1";
|
||||
private static final String KEY_E_INK_MODE = "e_ink_mode";
|
||||
private static final boolean DEFAULT_E_INK_MODE = false;
|
||||
/********************
|
||||
****** Read
|
||||
********************/
|
||||
@@ -560,6 +562,14 @@ public class Settings {
|
||||
putString(KEY_EXCLUDED_LANGUAGES, value);
|
||||
}
|
||||
|
||||
public static void putEInkMode(boolean value) {
|
||||
putBoolean(KEY_E_INK_MODE, value);
|
||||
}
|
||||
|
||||
public static boolean getEInkMode() {
|
||||
return getBoolean(KEY_E_INK_MODE, DEFAULT_E_INK_MODE);
|
||||
}
|
||||
|
||||
public static boolean getCellularNetworkWarning() {
|
||||
return getBoolean(KEY_CELLULAR_NETWORK_WARNING, DEFAULT_CELLULAR_NETWORK_WARNING);
|
||||
}
|
||||
|
||||
@@ -567,12 +567,8 @@ public final class GalleryListScene extends BaseScene
|
||||
|
||||
mAdapter = new GalleryListAdapter(inflater, resources,
|
||||
mRecyclerView, Settings.getListMode());
|
||||
//mRecyclerView.setSelector(Ripple.generateRippleDrawable(context, !AttrResources.getAttrBoolean(context, R.attr.isLightTheme), new ColorDrawable(Color.TRANSPARENT)));
|
||||
//mRecyclerView.setDrawSelectorOnTop(true);
|
||||
mRecyclerView.setClipToPadding(false);
|
||||
mRecyclerView.setClipChildren(false);
|
||||
//mRecyclerView.setOnItemClickListener(this);
|
||||
//mRecyclerView.setOnItemLongClickListener(this);
|
||||
mRecyclerView.addOnScrollListener(mOnScrollListener);
|
||||
|
||||
fastScroller.setPadding(fastScroller.getPaddingLeft(), fastScroller.getPaddingTop() + paddingTopSB,
|
||||
|
||||
@@ -152,7 +152,7 @@ public class SearchBar extends MaterialCardView implements View.OnClickListener,
|
||||
mListView.addItemDecoration(decoration);
|
||||
mListView.setLayoutManager(layoutManager);
|
||||
mListView.setOnItemClickListener((parent, view, position, id) -> {
|
||||
if (position < mSuggestionList.size() - 1) {
|
||||
if (position < mSuggestionList.size()) {
|
||||
mSuggestionList.get(position).onClick();
|
||||
return true;
|
||||
} else {
|
||||
@@ -160,7 +160,7 @@ public class SearchBar extends MaterialCardView implements View.OnClickListener,
|
||||
}
|
||||
});
|
||||
mListView.setOnItemLongClickListener((parent, view, position, id) -> {
|
||||
if (position < mSuggestionList.size() - 1) {
|
||||
if (position < mSuggestionList.size()) {
|
||||
return mSuggestionList.get(position).onLongClick();
|
||||
} else {
|
||||
return false;
|
||||
|
||||
Reference in new issue
Block a user