mirror of https://github.com/kurisufriend/Clover
the scroller is attached to the thread recyclerview. it is a modified version of the FastScroller recently added to the support library, that adds support for padding and some other tweaks. the scrollbar looks just as thin as the normal scrollbar, but the grab area is bigger than the visible bar.refactor-toolbar
parent
3c7f68e21d
commit
10155cf644
@ -0,0 +1,662 @@ |
||||
/* |
||||
* Copyright (C) 2017 The Android Open Source Project |
||||
* |
||||
* 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 org.floens.chan.ui.view; |
||||
|
||||
import android.animation.Animator; |
||||
import android.animation.AnimatorListenerAdapter; |
||||
import android.animation.ValueAnimator; |
||||
import android.animation.ValueAnimator.AnimatorUpdateListener; |
||||
import android.graphics.Canvas; |
||||
import android.graphics.drawable.Drawable; |
||||
import android.graphics.drawable.StateListDrawable; |
||||
import android.support.annotation.IntDef; |
||||
import android.support.annotation.Nullable; |
||||
import android.support.annotation.VisibleForTesting; |
||||
import android.support.v4.view.ViewCompat; |
||||
import android.support.v7.widget.RecyclerView; |
||||
import android.support.v7.widget.RecyclerView.ItemDecoration; |
||||
import android.support.v7.widget.RecyclerView.OnItemTouchListener; |
||||
import android.support.v7.widget.RecyclerView.OnScrollListener; |
||||
import android.view.MotionEvent; |
||||
|
||||
import java.lang.annotation.Retention; |
||||
import java.lang.annotation.RetentionPolicy; |
||||
|
||||
/** |
||||
* Class responsible to animate and provide a fast scroller. |
||||
* <p> |
||||
* Clover changed: the original FastScroller didn't account for the recyclerview top padding we |
||||
* require. A minimum thumb length parameter was also added. |
||||
*/ |
||||
public class FastScroller extends ItemDecoration implements OnItemTouchListener { |
||||
@IntDef({STATE_HIDDEN, STATE_VISIBLE, STATE_DRAGGING}) |
||||
@Retention(RetentionPolicy.SOURCE) |
||||
private @interface State { |
||||
} |
||||
|
||||
// Scroll thumb not showing
|
||||
private static final int STATE_HIDDEN = 0; |
||||
// Scroll thumb visible and moving along with the scrollbar
|
||||
private static final int STATE_VISIBLE = 1; |
||||
// Scroll thumb being dragged by user
|
||||
private static final int STATE_DRAGGING = 2; |
||||
|
||||
@IntDef({DRAG_X, DRAG_Y, DRAG_NONE}) |
||||
@Retention(RetentionPolicy.SOURCE) |
||||
private @interface DragState { |
||||
} |
||||
|
||||
private static final int DRAG_NONE = 0; |
||||
private static final int DRAG_X = 1; |
||||
private static final int DRAG_Y = 2; |
||||
|
||||
@IntDef({ANIMATION_STATE_OUT, ANIMATION_STATE_FADING_IN, ANIMATION_STATE_IN, |
||||
ANIMATION_STATE_FADING_OUT}) |
||||
@Retention(RetentionPolicy.SOURCE) |
||||
private @interface AnimationState { |
||||
} |
||||
|
||||
private static final int ANIMATION_STATE_OUT = 0; |
||||
private static final int ANIMATION_STATE_FADING_IN = 1; |
||||
private static final int ANIMATION_STATE_IN = 2; |
||||
private static final int ANIMATION_STATE_FADING_OUT = 3; |
||||
|
||||
private static final int SHOW_DURATION_MS = 500; |
||||
private static final int HIDE_DELAY_AFTER_VISIBLE_MS = 1500; |
||||
private static final int HIDE_DELAY_AFTER_DRAGGING_MS = 1200; |
||||
private static final int HIDE_DURATION_MS = 500; |
||||
private static final int SCROLLBAR_FULL_OPAQUE = 255; |
||||
|
||||
private static final int[] PRESSED_STATE_SET = new int[]{android.R.attr.state_pressed}; |
||||
private static final int[] EMPTY_STATE_SET = new int[]{}; |
||||
|
||||
private final int mScrollbarMinimumRange; |
||||
private final int mMargin; |
||||
private final int mThumbMinLength; |
||||
private final int mTargetWidth; |
||||
|
||||
// Final values for the vertical scroll bar
|
||||
private final StateListDrawable mVerticalThumbDrawable; |
||||
private final Drawable mVerticalTrackDrawable; |
||||
private final int mVerticalThumbWidth; |
||||
private final int mVerticalTrackWidth; |
||||
|
||||
// Final values for the horizontal scroll bar
|
||||
private final StateListDrawable mHorizontalThumbDrawable; |
||||
private final Drawable mHorizontalTrackDrawable; |
||||
private final int mHorizontalThumbHeight; |
||||
private final int mHorizontalTrackHeight; |
||||
|
||||
// Dynamic values for the vertical scroll bar
|
||||
int mVerticalThumbHeight; |
||||
int mVerticalThumbCenterY; |
||||
float mVerticalDragY; |
||||
int mVerticalDragThumbHeight; |
||||
|
||||
// Dynamic values for the horizontal scroll bar
|
||||
int mHorizontalThumbWidth; |
||||
int mHorizontalThumbCenterX; |
||||
float mHorizontalDragX; |
||||
int mHorizontalDragThumbWidth; |
||||
|
||||
private int mRecyclerViewWidth = 0; |
||||
private int mRecyclerViewHeight = 0; |
||||
|
||||
private int mRecyclerViewLeftPadding = 0; |
||||
private int mRecyclerViewTopPadding = 0; |
||||
private int mRecyclerViewRightPadding = 0; |
||||
private int mRecyclerViewBottomPadding = 0; |
||||
|
||||
private RecyclerView mRecyclerView; |
||||
/** |
||||
* Whether the document is long/wide enough to require scrolling. If not, we don't show the |
||||
* relevant scroller. |
||||
*/ |
||||
private boolean mNeedVerticalScrollbar = false; |
||||
private boolean mNeedHorizontalScrollbar = false; |
||||
@State |
||||
private int mState = STATE_HIDDEN; |
||||
@DragState |
||||
private int mDragState = DRAG_NONE; |
||||
|
||||
private final int[] mVerticalRange = new int[2]; |
||||
private final int[] mHorizontalRange = new int[2]; |
||||
private final ValueAnimator mShowHideAnimator = ValueAnimator.ofFloat(0, 1); |
||||
@AnimationState |
||||
private int mAnimationState = ANIMATION_STATE_OUT; |
||||
private final Runnable mHideRunnable = new Runnable() { |
||||
@Override |
||||
public void run() { |
||||
hide(HIDE_DURATION_MS); |
||||
} |
||||
}; |
||||
private final OnScrollListener mOnScrollListener = new OnScrollListener() { |
||||
@Override |
||||
public void onScrolled(RecyclerView recyclerView, int dx, int dy) { |
||||
updateScrollPosition(recyclerView.computeHorizontalScrollOffset(), |
||||
recyclerView.computeVerticalScrollOffset()); |
||||
} |
||||
}; |
||||
|
||||
public FastScroller(RecyclerView recyclerView, StateListDrawable verticalThumbDrawable, |
||||
Drawable verticalTrackDrawable, StateListDrawable horizontalThumbDrawable, |
||||
Drawable horizontalTrackDrawable, int defaultWidth, int scrollbarMinimumRange, |
||||
int margin, int thumbMinLength, int targetWidth) { |
||||
mVerticalThumbDrawable = verticalThumbDrawable; |
||||
mVerticalTrackDrawable = verticalTrackDrawable; |
||||
mHorizontalThumbDrawable = horizontalThumbDrawable; |
||||
mHorizontalTrackDrawable = horizontalTrackDrawable; |
||||
mVerticalThumbWidth = Math.max(defaultWidth, verticalThumbDrawable.getIntrinsicWidth()); |
||||
mVerticalTrackWidth = Math.max(defaultWidth, verticalTrackDrawable.getIntrinsicWidth()); |
||||
mHorizontalThumbHeight = Math |
||||
.max(defaultWidth, horizontalThumbDrawable.getIntrinsicWidth()); |
||||
mHorizontalTrackHeight = Math |
||||
.max(defaultWidth, horizontalTrackDrawable.getIntrinsicWidth()); |
||||
mScrollbarMinimumRange = scrollbarMinimumRange; |
||||
mMargin = margin; |
||||
mThumbMinLength = thumbMinLength; |
||||
mTargetWidth = targetWidth; |
||||
mVerticalThumbDrawable.setAlpha(SCROLLBAR_FULL_OPAQUE); |
||||
mVerticalTrackDrawable.setAlpha(SCROLLBAR_FULL_OPAQUE); |
||||
|
||||
mShowHideAnimator.addListener(new AnimatorListener()); |
||||
mShowHideAnimator.addUpdateListener(new AnimatorUpdater()); |
||||
|
||||
attachToRecyclerView(recyclerView); |
||||
} |
||||
|
||||
public void attachToRecyclerView(@Nullable RecyclerView recyclerView) { |
||||
if (mRecyclerView == recyclerView) { |
||||
return; // nothing to do
|
||||
} |
||||
if (mRecyclerView != null) { |
||||
destroyCallbacks(); |
||||
} |
||||
mRecyclerView = recyclerView; |
||||
if (mRecyclerView != null) { |
||||
setupCallbacks(); |
||||
} |
||||
} |
||||
|
||||
private void setupCallbacks() { |
||||
mRecyclerView.addItemDecoration(this); |
||||
mRecyclerView.addOnItemTouchListener(this); |
||||
mRecyclerView.addOnScrollListener(mOnScrollListener); |
||||
} |
||||
|
||||
private void destroyCallbacks() { |
||||
mRecyclerView.removeItemDecoration(this); |
||||
mRecyclerView.removeOnItemTouchListener(this); |
||||
mRecyclerView.removeOnScrollListener(mOnScrollListener); |
||||
cancelHide(); |
||||
} |
||||
|
||||
private void requestRedraw() { |
||||
mRecyclerView.invalidate(); |
||||
} |
||||
|
||||
private void setState(@State int state) { |
||||
if (state == STATE_DRAGGING && mState != STATE_DRAGGING) { |
||||
mVerticalThumbDrawable.setState(PRESSED_STATE_SET); |
||||
mVerticalTrackDrawable.setState(PRESSED_STATE_SET); |
||||
cancelHide(); |
||||
} |
||||
|
||||
if (state == STATE_HIDDEN) { |
||||
requestRedraw(); |
||||
} else { |
||||
show(); |
||||
} |
||||
|
||||
if (mState == STATE_DRAGGING && state != STATE_DRAGGING) { |
||||
mVerticalThumbDrawable.setState(EMPTY_STATE_SET); |
||||
mVerticalTrackDrawable.setState(EMPTY_STATE_SET); |
||||
resetHideDelay(HIDE_DELAY_AFTER_DRAGGING_MS); |
||||
} else if (state == STATE_VISIBLE) { |
||||
resetHideDelay(HIDE_DELAY_AFTER_VISIBLE_MS); |
||||
} |
||||
mState = state; |
||||
} |
||||
|
||||
private boolean isLayoutRTL() { |
||||
return ViewCompat.getLayoutDirection(mRecyclerView) == ViewCompat.LAYOUT_DIRECTION_RTL; |
||||
} |
||||
|
||||
public boolean isDragging() { |
||||
return mState == STATE_DRAGGING; |
||||
} |
||||
|
||||
@VisibleForTesting |
||||
boolean isVisible() { |
||||
return mState == STATE_VISIBLE; |
||||
} |
||||
|
||||
@VisibleForTesting |
||||
boolean isHidden() { |
||||
return mState == STATE_HIDDEN; |
||||
} |
||||
|
||||
|
||||
public void show() { |
||||
switch (mAnimationState) { |
||||
case ANIMATION_STATE_FADING_OUT: |
||||
mShowHideAnimator.cancel(); |
||||
// fall through
|
||||
case ANIMATION_STATE_OUT: |
||||
mAnimationState = ANIMATION_STATE_FADING_IN; |
||||
mShowHideAnimator.setFloatValues((float) mShowHideAnimator.getAnimatedValue(), 1); |
||||
mShowHideAnimator.setDuration(SHOW_DURATION_MS); |
||||
mShowHideAnimator.setStartDelay(0); |
||||
mShowHideAnimator.start(); |
||||
break; |
||||
} |
||||
} |
||||
|
||||
public void hide() { |
||||
hide(0); |
||||
} |
||||
|
||||
@VisibleForTesting |
||||
void hide(int duration) { |
||||
switch (mAnimationState) { |
||||
case ANIMATION_STATE_FADING_IN: |
||||
mShowHideAnimator.cancel(); |
||||
// fall through
|
||||
case ANIMATION_STATE_IN: |
||||
mAnimationState = ANIMATION_STATE_FADING_OUT; |
||||
mShowHideAnimator.setFloatValues((float) mShowHideAnimator.getAnimatedValue(), 0); |
||||
mShowHideAnimator.setDuration(duration); |
||||
mShowHideAnimator.start(); |
||||
break; |
||||
} |
||||
} |
||||
|
||||
private void cancelHide() { |
||||
mRecyclerView.removeCallbacks(mHideRunnable); |
||||
} |
||||
|
||||
private void resetHideDelay(int delay) { |
||||
cancelHide(); |
||||
mRecyclerView.postDelayed(mHideRunnable, delay); |
||||
} |
||||
|
||||
@Override |
||||
public void onDrawOver(Canvas canvas, RecyclerView parent, RecyclerView.State state) { |
||||
if (mRecyclerViewWidth != getRecyclerViewWidth() |
||||
|| mRecyclerViewHeight != getRecyclerViewHeight()) { |
||||
mRecyclerViewWidth = getRecyclerViewWidth(); |
||||
mRecyclerViewHeight = getRecyclerViewHeight(); |
||||
mRecyclerViewLeftPadding = getRecyclerViewLeftPadding(); |
||||
mRecyclerViewTopPadding = getRecyclerViewTopPadding(); |
||||
mRecyclerViewRightPadding = getRecyclerViewRightPadding(); |
||||
mRecyclerViewBottomPadding = getRecyclerViewBottomPadding(); |
||||
|
||||
// This is due to the different events ordering when keyboard is opened or
|
||||
// retracted vs rotate. Hence to avoid corner cases we just disable the
|
||||
// scroller when size changed, and wait until the scroll position is recomputed
|
||||
// before showing it back.
|
||||
setState(STATE_HIDDEN); |
||||
return; |
||||
} |
||||
|
||||
if (mAnimationState != ANIMATION_STATE_OUT) { |
||||
if (mNeedVerticalScrollbar) { |
||||
drawVerticalScrollbar(canvas); |
||||
} |
||||
if (mNeedHorizontalScrollbar) { |
||||
drawHorizontalScrollbar(canvas); |
||||
} |
||||
} |
||||
} |
||||
|
||||
private int getRecyclerViewWidth() { |
||||
return mNeedVerticalScrollbar ? mRecyclerView.getWidth() : |
||||
mRecyclerView.getWidth() - mRecyclerView.getPaddingLeft() - |
||||
mRecyclerView.getPaddingRight(); |
||||
} |
||||
|
||||
private int getRecyclerViewHeight() { |
||||
return mNeedHorizontalScrollbar ? mRecyclerView.getHeight() : |
||||
mRecyclerView.getHeight() - mRecyclerView.getPaddingTop() - |
||||
mRecyclerView.getPaddingBottom(); |
||||
} |
||||
|
||||
private int getRecyclerViewLeftPadding() { |
||||
return mNeedVerticalScrollbar ? 0 : mRecyclerView.getPaddingLeft(); |
||||
} |
||||
|
||||
private int getRecyclerViewTopPadding() { |
||||
return mNeedHorizontalScrollbar ? 0 : mRecyclerView.getPaddingTop(); |
||||
} |
||||
|
||||
private int getRecyclerViewRightPadding() { |
||||
return mNeedVerticalScrollbar ? 0 : mRecyclerView.getPaddingRight(); |
||||
} |
||||
|
||||
private int getRecyclerViewBottomPadding() { |
||||
return mNeedHorizontalScrollbar ? 0 : mRecyclerView.getPaddingBottom(); |
||||
} |
||||
|
||||
private void drawVerticalScrollbar(Canvas canvas) { |
||||
int viewWidth = mRecyclerViewWidth; |
||||
|
||||
int left = mRecyclerViewLeftPadding + viewWidth - mVerticalThumbWidth; |
||||
int top = mVerticalThumbCenterY - mVerticalThumbHeight / 2; |
||||
mVerticalThumbDrawable.setBounds(0, 0, mVerticalThumbWidth, mVerticalThumbHeight); |
||||
|
||||
int trackLength = mRecyclerViewHeight + mRecyclerViewTopPadding + |
||||
mRecyclerViewBottomPadding; |
||||
mVerticalTrackDrawable |
||||
.setBounds(0, 0, mVerticalTrackWidth, trackLength); |
||||
|
||||
if (isLayoutRTL()) { |
||||
mVerticalTrackDrawable.draw(canvas); |
||||
canvas.translate(mVerticalThumbWidth, top); |
||||
canvas.scale(-1, 1); |
||||
mVerticalThumbDrawable.draw(canvas); |
||||
canvas.scale(1, 1); |
||||
canvas.translate(-mVerticalThumbWidth, -top); |
||||
} else { |
||||
canvas.translate(left, 0); |
||||
mVerticalTrackDrawable.draw(canvas); |
||||
canvas.translate(0, top); |
||||
mVerticalThumbDrawable.draw(canvas); |
||||
canvas.translate(-left, -top); |
||||
} |
||||
} |
||||
|
||||
private void drawHorizontalScrollbar(Canvas canvas) { |
||||
int viewHeight = mRecyclerViewHeight; |
||||
|
||||
int top = mRecyclerViewTopPadding + viewHeight - mHorizontalThumbHeight; |
||||
int left = mHorizontalThumbCenterX - mHorizontalThumbWidth / 2; |
||||
mHorizontalThumbDrawable.setBounds(0, 0, mHorizontalThumbWidth, mHorizontalThumbHeight); |
||||
|
||||
int trackLength = mRecyclerViewWidth + mRecyclerViewLeftPadding + |
||||
mRecyclerViewRightPadding; |
||||
mHorizontalTrackDrawable |
||||
.setBounds(0, 0, trackLength, mHorizontalTrackHeight); |
||||
|
||||
canvas.translate(0, top); |
||||
mHorizontalTrackDrawable.draw(canvas); |
||||
canvas.translate(left, 0); |
||||
mHorizontalThumbDrawable.draw(canvas); |
||||
canvas.translate(-left, -top); |
||||
} |
||||
|
||||
/** |
||||
* Notify the scroller of external change of the scroll, e.g. through dragging or flinging on |
||||
* the view itself. |
||||
* |
||||
* @param offsetX The new scroll X offset. |
||||
* @param offsetY The new scroll Y offset. |
||||
*/ |
||||
void updateScrollPosition(int offsetX, int offsetY) { |
||||
int verticalContentLength = mRecyclerView.computeVerticalScrollRange(); |
||||
int verticalVisibleLength = mRecyclerViewHeight; |
||||
mNeedVerticalScrollbar = verticalContentLength - verticalVisibleLength > 0 |
||||
&& mRecyclerViewHeight >= mScrollbarMinimumRange; |
||||
|
||||
int horizontalContentLength = mRecyclerView.computeHorizontalScrollRange(); |
||||
int horizontalVisibleLength = mRecyclerViewWidth; |
||||
mNeedHorizontalScrollbar = horizontalContentLength - horizontalVisibleLength > 0 |
||||
&& mRecyclerViewWidth >= mScrollbarMinimumRange; |
||||
|
||||
if (!mNeedVerticalScrollbar && !mNeedHorizontalScrollbar) { |
||||
if (mState != STATE_HIDDEN) { |
||||
setState(STATE_HIDDEN); |
||||
} |
||||
return; |
||||
} |
||||
|
||||
if (mNeedVerticalScrollbar) { |
||||
float middleScreenPos = offsetY + verticalVisibleLength / 2.0f; |
||||
mVerticalThumbCenterY = mRecyclerViewTopPadding + |
||||
(int) ((verticalVisibleLength * middleScreenPos) / verticalContentLength); |
||||
int length = Math.min(verticalVisibleLength, |
||||
(verticalVisibleLength * verticalVisibleLength) / verticalContentLength); |
||||
mVerticalThumbHeight = Math.max(mThumbMinLength, length); |
||||
} |
||||
|
||||
if (mNeedHorizontalScrollbar) { |
||||
float middleScreenPos = offsetX + horizontalVisibleLength / 2.0f; |
||||
mHorizontalThumbCenterX = mRecyclerViewLeftPadding + |
||||
(int) ((horizontalVisibleLength * middleScreenPos) / horizontalContentLength); |
||||
int length = Math.min(horizontalVisibleLength, |
||||
(horizontalVisibleLength * horizontalVisibleLength) / horizontalContentLength); |
||||
mHorizontalThumbWidth = Math.max(mThumbMinLength, length); |
||||
} |
||||
|
||||
if (mState == STATE_HIDDEN || mState == STATE_VISIBLE) { |
||||
setState(STATE_VISIBLE); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public boolean onInterceptTouchEvent(RecyclerView recyclerView, MotionEvent ev) { |
||||
final boolean handled; |
||||
if (mState == STATE_VISIBLE) { |
||||
boolean insideVerticalThumb = isPointInsideVerticalThumb(ev.getX(), ev.getY()); |
||||
boolean insideHorizontalThumb = isPointInsideHorizontalThumb(ev.getX(), ev.getY()); |
||||
if (ev.getAction() == MotionEvent.ACTION_DOWN |
||||
&& (insideVerticalThumb || insideHorizontalThumb)) { |
||||
if (insideHorizontalThumb) { |
||||
mDragState = DRAG_X; |
||||
mHorizontalDragX = (int) ev.getX(); |
||||
mHorizontalDragThumbWidth = mHorizontalThumbWidth; |
||||
} else if (insideVerticalThumb) { |
||||
mDragState = DRAG_Y; |
||||
mVerticalDragY = (int) ev.getY(); |
||||
mVerticalDragThumbHeight = mVerticalThumbHeight; |
||||
} |
||||
|
||||
setState(STATE_DRAGGING); |
||||
handled = true; |
||||
} else { |
||||
handled = false; |
||||
} |
||||
} else if (mState == STATE_DRAGGING) { |
||||
handled = true; |
||||
} else { |
||||
handled = false; |
||||
} |
||||
return handled; |
||||
} |
||||
|
||||
@Override |
||||
public void onTouchEvent(RecyclerView recyclerView, MotionEvent me) { |
||||
if (mState == STATE_HIDDEN) { |
||||
return; |
||||
} |
||||
|
||||
if (me.getAction() == MotionEvent.ACTION_DOWN) { |
||||
boolean insideVerticalThumb = isPointInsideVerticalThumb(me.getX(), me.getY()); |
||||
boolean insideHorizontalThumb = isPointInsideHorizontalThumb(me.getX(), me.getY()); |
||||
if (insideVerticalThumb || insideHorizontalThumb) { |
||||
if (insideHorizontalThumb) { |
||||
mDragState = DRAG_X; |
||||
mHorizontalDragX = (int) me.getX(); |
||||
mHorizontalDragThumbWidth = mHorizontalThumbWidth; |
||||
} else if (insideVerticalThumb) { |
||||
mDragState = DRAG_Y; |
||||
mVerticalDragY = (int) me.getY(); |
||||
mVerticalDragThumbHeight = mVerticalThumbHeight; |
||||
} |
||||
setState(STATE_DRAGGING); |
||||
} |
||||
} else if (me.getAction() == MotionEvent.ACTION_UP && mState == STATE_DRAGGING) { |
||||
mVerticalDragY = 0; |
||||
mHorizontalDragX = 0; |
||||
setState(STATE_VISIBLE); |
||||
mDragState = DRAG_NONE; |
||||
} else if (me.getAction() == MotionEvent.ACTION_MOVE && mState == STATE_DRAGGING) { |
||||
show(); |
||||
if (mDragState == DRAG_X) { |
||||
horizontalScrollTo(me.getX()); |
||||
} |
||||
if (mDragState == DRAG_Y) { |
||||
verticalScrollTo(me.getY()); |
||||
} |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) { |
||||
} |
||||
|
||||
private void verticalScrollTo(float y) { |
||||
final int[] scrollbarRange = getVerticalRange(); |
||||
y = Math.max(scrollbarRange[0], Math.min(scrollbarRange[1], y)); |
||||
if (Math.abs(mVerticalThumbCenterY - y) < 2) { |
||||
return; |
||||
} |
||||
int scrollingBy = scrollTo(mVerticalDragY, y, scrollbarRange, |
||||
mRecyclerView.computeVerticalScrollRange(), |
||||
mRecyclerView.computeVerticalScrollOffset(), |
||||
mRecyclerViewHeight, mVerticalDragThumbHeight); |
||||
if (scrollingBy != 0) { |
||||
mRecyclerView.scrollBy(0, scrollingBy); |
||||
} |
||||
mVerticalDragY = y; |
||||
} |
||||
|
||||
private void horizontalScrollTo(float x) { |
||||
final int[] scrollbarRange = getHorizontalRange(); |
||||
x = Math.max(scrollbarRange[0], Math.min(scrollbarRange[1], x)); |
||||
if (Math.abs(mHorizontalThumbCenterX - x) < 2) { |
||||
return; |
||||
} |
||||
|
||||
int scrollingBy = scrollTo(mHorizontalDragX, x, scrollbarRange, |
||||
mRecyclerView.computeHorizontalScrollRange(), |
||||
mRecyclerView.computeHorizontalScrollOffset(), |
||||
mRecyclerViewWidth, mHorizontalDragThumbWidth); |
||||
if (scrollingBy != 0) { |
||||
mRecyclerView.scrollBy(scrollingBy, 0); |
||||
} |
||||
|
||||
mHorizontalDragX = x; |
||||
} |
||||
|
||||
private int scrollTo(float oldDragPos, float newDragPos, int[] scrollbarRange, int scrollRange, |
||||
int scrollOffset, int viewLength, int dragThumbLength) { |
||||
int scrollbarLength = scrollbarRange[1] - scrollbarRange[0] - dragThumbLength; |
||||
if (scrollbarLength == 0) { |
||||
return 0; |
||||
} |
||||
float percentage = ((newDragPos - oldDragPos) / (float) scrollbarLength); |
||||
int totalPossibleOffset = scrollRange - viewLength; |
||||
int scrollingBy = (int) (percentage * totalPossibleOffset); |
||||
int absoluteOffset = scrollOffset + scrollingBy; |
||||
if (absoluteOffset < totalPossibleOffset && absoluteOffset >= 0) { |
||||
return scrollingBy; |
||||
} else { |
||||
return 0; |
||||
} |
||||
} |
||||
|
||||
@VisibleForTesting |
||||
boolean isPointInsideVerticalThumb(float x, float y) { |
||||
// width divided by 2 for rtl? keeping it the same as upstream, but seems illogical.
|
||||
return (isLayoutRTL() ? x <= mRecyclerViewLeftPadding + mTargetWidth / 2 |
||||
: x >= mRecyclerViewLeftPadding + mRecyclerViewWidth - mTargetWidth) |
||||
&& y >= mVerticalThumbCenterY - mVerticalThumbHeight / 2 - mTargetWidth |
||||
&& y <= mVerticalThumbCenterY + mVerticalThumbHeight / 2 + mTargetWidth; |
||||
} |
||||
|
||||
@VisibleForTesting |
||||
boolean isPointInsideHorizontalThumb(float x, float y) { |
||||
return (y >= mRecyclerViewTopPadding + mRecyclerViewHeight - mTargetWidth) |
||||
&& x >= mHorizontalThumbCenterX - mHorizontalThumbWidth / 2 - mTargetWidth |
||||
&& x <= mHorizontalThumbCenterX + mHorizontalThumbWidth / 2 + mTargetWidth; |
||||
} |
||||
|
||||
@VisibleForTesting |
||||
Drawable getHorizontalTrackDrawable() { |
||||
return mHorizontalTrackDrawable; |
||||
} |
||||
|
||||
@VisibleForTesting |
||||
Drawable getHorizontalThumbDrawable() { |
||||
return mHorizontalThumbDrawable; |
||||
} |
||||
|
||||
@VisibleForTesting |
||||
Drawable getVerticalTrackDrawable() { |
||||
return mVerticalTrackDrawable; |
||||
} |
||||
|
||||
@VisibleForTesting |
||||
Drawable getVerticalThumbDrawable() { |
||||
return mVerticalThumbDrawable; |
||||
} |
||||
|
||||
/** |
||||
* Gets the (min, max) vertical positions of the vertical scroll bar. |
||||
*/ |
||||
private int[] getVerticalRange() { |
||||
mVerticalRange[0] = mRecyclerViewTopPadding + mMargin; |
||||
mVerticalRange[1] = mRecyclerViewTopPadding + mRecyclerViewHeight - mMargin; |
||||
return mVerticalRange; |
||||
} |
||||
|
||||
/** |
||||
* Gets the (min, max) horizontal positions of the horizontal scroll bar. |
||||
*/ |
||||
private int[] getHorizontalRange() { |
||||
mHorizontalRange[0] = mRecyclerViewLeftPadding + mMargin; |
||||
mHorizontalRange[1] = mRecyclerViewLeftPadding + mRecyclerViewWidth - mMargin; |
||||
return mHorizontalRange; |
||||
} |
||||
|
||||
private class AnimatorListener extends AnimatorListenerAdapter { |
||||
|
||||
private boolean mCanceled = false; |
||||
|
||||
@Override |
||||
public void onAnimationEnd(Animator animation) { |
||||
// Cancel is always followed by a new directive, so don't update state.
|
||||
if (mCanceled) { |
||||
mCanceled = false; |
||||
return; |
||||
} |
||||
if ((float) mShowHideAnimator.getAnimatedValue() == 0) { |
||||
mAnimationState = ANIMATION_STATE_OUT; |
||||
setState(STATE_HIDDEN); |
||||
} else { |
||||
mAnimationState = ANIMATION_STATE_IN; |
||||
requestRedraw(); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void onAnimationCancel(Animator animation) { |
||||
mCanceled = true; |
||||
} |
||||
} |
||||
|
||||
private class AnimatorUpdater implements AnimatorUpdateListener { |
||||
|
||||
@Override |
||||
public void onAnimationUpdate(ValueAnimator valueAnimator) { |
||||
int alpha = (int) (SCROLLBAR_FULL_OPAQUE * ((float) valueAnimator.getAnimatedValue())); |
||||
mVerticalThumbDrawable.setAlpha(alpha); |
||||
mVerticalTrackDrawable.setAlpha(alpha); |
||||
requestRedraw(); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,29 @@ |
||||
<?xml version="1.0" encoding="utf-8"?><!-- |
||||
Clover - 4chan browser https://github.com/Floens/Clover/ |
||||
Copyright (C) 2014 Floens |
||||
|
||||
This program is free software: you can redistribute it and/or modify |
||||
it under the terms of the GNU General Public License as published by |
||||
the Free Software Foundation, either version 3 of the License, or |
||||
(at your option) any later version. |
||||
|
||||
This program is distributed in the hope that it will be useful, |
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
GNU General Public License for more details. |
||||
|
||||
You should have received a copy of the GNU General Public License |
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. |
||||
--> |
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android"> |
||||
<item android:state_pressed="true"> |
||||
<shape android:shape="rectangle"> |
||||
<solid android:color="#ff009688" /> |
||||
</shape> |
||||
</item> |
||||
<item> |
||||
<shape android:shape="rectangle"> |
||||
<solid android:color="#5a000000" /> |
||||
</shape> |
||||
</item> |
||||
</selector> |
@ -0,0 +1,29 @@ |
||||
<?xml version="1.0" encoding="utf-8"?><!-- |
||||
Clover - 4chan browser https://github.com/Floens/Clover/ |
||||
Copyright (C) 2014 Floens |
||||
|
||||
This program is free software: you can redistribute it and/or modify |
||||
it under the terms of the GNU General Public License as published by |
||||
the Free Software Foundation, either version 3 of the License, or |
||||
(at your option) any later version. |
||||
|
||||
This program is distributed in the hope that it will be useful, |
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||
GNU General Public License for more details. |
||||
|
||||
You should have received a copy of the GNU General Public License |
||||
along with this program. If not, see <http://www.gnu.org/licenses/>. |
||||
--> |
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android"> |
||||
<item android:state_pressed="true"> |
||||
<shape android:shape="rectangle"> |
||||
<solid android:color="#12000000" /> |
||||
</shape> |
||||
</item> |
||||
<item> |
||||
<shape android:shape="rectangle"> |
||||
<solid android:color="#00000000" /> |
||||
</shape> |
||||
</item> |
||||
</selector> |
Loading…
Reference in new issue