parent
8296d4a506
commit
29cb92d7ee
@ -0,0 +1,253 @@ |
||||
package xyz.fycz.myreader.widget.scroller; |
||||
|
||||
import android.content.Context; |
||||
import android.util.AttributeSet; |
||||
import android.view.ViewGroup; |
||||
import android.view.ViewParent; |
||||
|
||||
import androidx.annotation.ColorInt; |
||||
import androidx.recyclerview.widget.RecyclerView; |
||||
|
||||
import xyz.fycz.myreader.R; |
||||
|
||||
|
||||
@SuppressWarnings("unused") |
||||
public class FastScrollRecyclerView extends RecyclerView { |
||||
|
||||
|
||||
private FastScroller mFastScroller; |
||||
|
||||
|
||||
public FastScrollRecyclerView(Context context) { |
||||
|
||||
super(context); |
||||
|
||||
layout(context, null); |
||||
|
||||
setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)); |
||||
|
||||
} |
||||
|
||||
|
||||
public FastScrollRecyclerView(Context context, AttributeSet attrs) { |
||||
|
||||
this(context, attrs, 0); |
||||
|
||||
} |
||||
|
||||
|
||||
public FastScrollRecyclerView(Context context, AttributeSet attrs, int defStyleAttr) { |
||||
|
||||
super(context, attrs, defStyleAttr); |
||||
|
||||
layout(context, attrs); |
||||
|
||||
} |
||||
|
||||
|
||||
@Override |
||||
|
||||
public void setAdapter(Adapter adapter) { |
||||
|
||||
super.setAdapter(adapter); |
||||
|
||||
|
||||
if (adapter instanceof FastScroller.SectionIndexer) { |
||||
|
||||
setSectionIndexer((FastScroller.SectionIndexer) adapter); |
||||
|
||||
} else if (adapter == null) { |
||||
|
||||
setSectionIndexer(null); |
||||
|
||||
} |
||||
|
||||
} |
||||
|
||||
|
||||
@Override |
||||
|
||||
public void setVisibility(int visibility) { |
||||
|
||||
super.setVisibility(visibility); |
||||
|
||||
mFastScroller.setVisibility(visibility); |
||||
|
||||
} |
||||
|
||||
|
||||
/** |
||||
* Set the {@link FastScroller.SectionIndexer} for the {@link FastScroller}. |
||||
* |
||||
* @param sectionIndexer The SectionIndexer that provides section text for the FastScroller |
||||
*/ |
||||
|
||||
public void setSectionIndexer(FastScroller.SectionIndexer sectionIndexer) { |
||||
|
||||
mFastScroller.setSectionIndexer(sectionIndexer); |
||||
|
||||
} |
||||
|
||||
|
||||
/** |
||||
* Set the enabled state of fast scrolling. |
||||
* |
||||
* @param enabled True to enable fast scrolling, false otherwise |
||||
*/ |
||||
|
||||
public void setFastScrollEnabled(boolean enabled) { |
||||
|
||||
mFastScroller.setEnabled(enabled); |
||||
|
||||
} |
||||
|
||||
|
||||
/** |
||||
* Hide the scrollbar when not scrolling. |
||||
* |
||||
* @param hideScrollbar True to hide the scrollbar, false to show |
||||
*/ |
||||
|
||||
public void setHideScrollbar(boolean hideScrollbar) { |
||||
|
||||
mFastScroller.setFadeScrollbar(hideScrollbar); |
||||
|
||||
} |
||||
|
||||
|
||||
/** |
||||
* Display a scroll track while scrolling. |
||||
* |
||||
* @param visible True to show scroll track, false to hide |
||||
*/ |
||||
|
||||
public void setTrackVisible(boolean visible) { |
||||
|
||||
mFastScroller.setTrackVisible(visible); |
||||
|
||||
} |
||||
|
||||
|
||||
/** |
||||
* Set the color of the scroll track. |
||||
* |
||||
* @param color The color for the scroll track |
||||
*/ |
||||
|
||||
public void setTrackColor(@ColorInt int color) { |
||||
|
||||
mFastScroller.setTrackColor(color); |
||||
|
||||
} |
||||
|
||||
|
||||
/** |
||||
* Set the color for the scroll handle. |
||||
* |
||||
* @param color The color for the scroll handle |
||||
*/ |
||||
|
||||
public void setHandleColor(@ColorInt int color) { |
||||
|
||||
mFastScroller.setHandleColor(color); |
||||
|
||||
} |
||||
|
||||
|
||||
/** |
||||
* Show the section bubble while scrolling. |
||||
* |
||||
* @param visible True to show the bubble, false to hide |
||||
*/ |
||||
|
||||
public void setBubbleVisible(boolean visible) { |
||||
|
||||
mFastScroller.setBubbleVisible(visible); |
||||
|
||||
} |
||||
|
||||
|
||||
/** |
||||
* Set the background color of the index bubble. |
||||
* |
||||
* @param color The background color for the index bubble |
||||
*/ |
||||
|
||||
public void setBubbleColor(@ColorInt int color) { |
||||
|
||||
mFastScroller.setBubbleColor(color); |
||||
|
||||
} |
||||
|
||||
|
||||
/** |
||||
* Set the text color of the index bubble. |
||||
* |
||||
* @param color The text color for the index bubble |
||||
*/ |
||||
|
||||
public void setBubbleTextColor(@ColorInt int color) { |
||||
|
||||
mFastScroller.setBubbleTextColor(color); |
||||
|
||||
} |
||||
|
||||
|
||||
/** |
||||
* Set the fast scroll state change listener. |
||||
* |
||||
* @param fastScrollStateChangeListener The interface that will listen to fastscroll state change events |
||||
*/ |
||||
|
||||
public void setFastScrollStateChangeListener(FastScrollStateChangeListener fastScrollStateChangeListener) { |
||||
|
||||
mFastScroller.setFastScrollStateChangeListener(fastScrollStateChangeListener); |
||||
|
||||
} |
||||
|
||||
|
||||
@Override |
||||
|
||||
protected void onAttachedToWindow() { |
||||
|
||||
super.onAttachedToWindow(); |
||||
|
||||
mFastScroller.attachRecyclerView(this); |
||||
|
||||
|
||||
ViewParent parent = getParent(); |
||||
|
||||
|
||||
if (parent instanceof ViewGroup) { |
||||
|
||||
ViewGroup viewGroup = (ViewGroup) parent; |
||||
|
||||
viewGroup.addView(mFastScroller); |
||||
|
||||
mFastScroller.setLayoutParams(viewGroup); |
||||
|
||||
} |
||||
|
||||
} |
||||
|
||||
|
||||
@Override |
||||
|
||||
protected void onDetachedFromWindow() { |
||||
|
||||
mFastScroller.detachRecyclerView(); |
||||
|
||||
super.onDetachedFromWindow(); |
||||
|
||||
} |
||||
|
||||
|
||||
private void layout(Context context, AttributeSet attrs) { |
||||
|
||||
mFastScroller = new FastScroller(context, attrs); |
||||
|
||||
mFastScroller.setId(R.id.fast_scroller); |
||||
|
||||
} |
||||
|
||||
} |
@ -0,0 +1,14 @@ |
||||
package xyz.fycz.myreader.widget.scroller; |
||||
|
||||
public interface FastScrollStateChangeListener { |
||||
|
||||
/** |
||||
* Called when fast scrolling begins |
||||
*/ |
||||
void onFastScrollStart(FastScroller fastScroller); |
||||
|
||||
/** |
||||
* Called when fast scrolling ends |
||||
*/ |
||||
void onFastScrollStop(FastScroller fastScroller); |
||||
} |
@ -0,0 +1,531 @@ |
||||
package xyz.fycz.myreader.widget.scroller; |
||||
|
||||
import android.animation.Animator; |
||||
import android.animation.AnimatorListenerAdapter; |
||||
import android.annotation.SuppressLint; |
||||
import android.content.Context; |
||||
import android.content.res.TypedArray; |
||||
import android.graphics.Color; |
||||
import android.graphics.drawable.Drawable; |
||||
import android.util.AttributeSet; |
||||
import android.view.MotionEvent; |
||||
import android.view.View; |
||||
import android.view.ViewGroup; |
||||
import android.view.ViewPropertyAnimator; |
||||
import android.widget.FrameLayout; |
||||
import android.widget.ImageView; |
||||
import android.widget.LinearLayout; |
||||
import android.widget.RelativeLayout; |
||||
import android.widget.TextView; |
||||
|
||||
import androidx.annotation.ColorInt; |
||||
import androidx.annotation.IdRes; |
||||
import androidx.annotation.NonNull; |
||||
import androidx.constraintlayout.widget.ConstraintLayout; |
||||
import androidx.constraintlayout.widget.ConstraintSet; |
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout; |
||||
import androidx.core.content.ContextCompat; |
||||
import androidx.core.graphics.drawable.DrawableCompat; |
||||
import androidx.core.view.GravityCompat; |
||||
import androidx.core.view.ViewCompat; |
||||
import androidx.recyclerview.widget.LinearLayoutManager; |
||||
import androidx.recyclerview.widget.RecyclerView; |
||||
import androidx.recyclerview.widget.StaggeredGridLayoutManager; |
||||
|
||||
import xyz.fycz.myreader.R; |
||||
import xyz.fycz.myreader.util.utils.ColorUtil; |
||||
|
||||
|
||||
public class FastScroller extends LinearLayout { |
||||
private static final int sBubbleAnimDuration = 100; |
||||
private static final int sScrollbarAnimDuration = 300; |
||||
private static final int sScrollbarHideDelay = 1000; |
||||
private static final int sTrackSnapRange = 5; |
||||
@ColorInt |
||||
private int mBubbleColor; |
||||
@ColorInt |
||||
private int mHandleColor; |
||||
private int mBubbleHeight; |
||||
private int mHandleHeight; |
||||
private int mViewHeight; |
||||
private boolean mFadeScrollbar; |
||||
private boolean mShowBubble; |
||||
private SectionIndexer mSectionIndexer; |
||||
private ViewPropertyAnimator mScrollbarAnimator; |
||||
private ViewPropertyAnimator mBubbleAnimator; |
||||
private RecyclerView mRecyclerView; |
||||
private TextView mBubbleView; |
||||
private ImageView mHandleView; |
||||
private ImageView mTrackView; |
||||
private View mScrollbar; |
||||
private Drawable mBubbleImage; |
||||
private Drawable mHandleImage; |
||||
private Drawable mTrackImage; |
||||
private FastScrollStateChangeListener mFastScrollStateChangeListener; |
||||
private Runnable mScrollbarHider = this::hideScrollbar; |
||||
|
||||
private RecyclerView.OnScrollListener mScrollListener = new RecyclerView.OnScrollListener() { |
||||
@Override |
||||
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { |
||||
if (!mHandleView.isSelected() && isEnabled()) { |
||||
setViewPositions(getScrollProportion(recyclerView)); |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) { |
||||
super.onScrollStateChanged(recyclerView, newState); |
||||
if (isEnabled()) { |
||||
switch (newState) { |
||||
case RecyclerView.SCROLL_STATE_DRAGGING: |
||||
getHandler().removeCallbacks(mScrollbarHider); |
||||
cancelAnimation(mScrollbarAnimator); |
||||
if (!isViewVisible(mScrollbar)) { |
||||
showScrollbar(); |
||||
} |
||||
break; |
||||
case RecyclerView.SCROLL_STATE_IDLE: |
||||
if (mFadeScrollbar && !mHandleView.isSelected()) { |
||||
getHandler().postDelayed(mScrollbarHider, sScrollbarHideDelay); |
||||
} |
||||
break; |
||||
} |
||||
} |
||||
} |
||||
}; |
||||
|
||||
public FastScroller(Context context) { |
||||
super(context); |
||||
layout(context, null); |
||||
setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT)); |
||||
} |
||||
|
||||
public FastScroller(Context context, AttributeSet attrs) { |
||||
this(context, attrs, 0); |
||||
} |
||||
|
||||
public FastScroller(Context context, AttributeSet attrs, int defStyleAttr) { |
||||
super(context, attrs, defStyleAttr); |
||||
layout(context, attrs); |
||||
setLayoutParams(generateLayoutParams(attrs)); |
||||
} |
||||
|
||||
@Override |
||||
public void setLayoutParams(@NonNull ViewGroup.LayoutParams params) { |
||||
params.width = LayoutParams.WRAP_CONTENT; |
||||
super.setLayoutParams(params); |
||||
} |
||||
|
||||
public void setLayoutParams(@NonNull ViewGroup viewGroup) { |
||||
@IdRes int recyclerViewId = mRecyclerView != null ? mRecyclerView.getId() : NO_ID; |
||||
int marginTop = getResources().getDimensionPixelSize(R.dimen.fastscroll_scrollbar_margin_top); |
||||
int marginBottom = getResources().getDimensionPixelSize(R.dimen.fastscroll_scrollbar_margin_bottom); |
||||
if (recyclerViewId == NO_ID) { |
||||
throw new IllegalArgumentException("RecyclerView must have a view ID"); |
||||
} |
||||
if (viewGroup instanceof ConstraintLayout) { |
||||
ConstraintSet constraintSet = new ConstraintSet(); |
||||
@IdRes int layoutId = getId(); |
||||
constraintSet.clone((ConstraintLayout) viewGroup); |
||||
constraintSet.connect(layoutId, ConstraintSet.TOP, recyclerViewId, ConstraintSet.TOP); |
||||
constraintSet.connect(layoutId, ConstraintSet.BOTTOM, recyclerViewId, ConstraintSet.BOTTOM); |
||||
constraintSet.connect(layoutId, ConstraintSet.END, recyclerViewId, ConstraintSet.END); |
||||
constraintSet.applyTo((ConstraintLayout) viewGroup); |
||||
ConstraintLayout.LayoutParams layoutParams = (ConstraintLayout.LayoutParams) getLayoutParams(); |
||||
layoutParams.setMargins(0, marginTop, 0, marginBottom); |
||||
setLayoutParams(layoutParams); |
||||
} else if (viewGroup instanceof CoordinatorLayout) { |
||||
CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) getLayoutParams(); |
||||
layoutParams.setAnchorId(recyclerViewId); |
||||
layoutParams.anchorGravity = GravityCompat.END; |
||||
layoutParams.setMargins(0, marginTop, 0, marginBottom); |
||||
setLayoutParams(layoutParams); |
||||
} else if (viewGroup instanceof FrameLayout) { |
||||
FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) getLayoutParams(); |
||||
layoutParams.gravity = GravityCompat.END; |
||||
layoutParams.setMargins(0, marginTop, 0, marginBottom); |
||||
setLayoutParams(layoutParams); |
||||
} else if (viewGroup instanceof RelativeLayout) { |
||||
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) getLayoutParams(); |
||||
int endRule = RelativeLayout.ALIGN_END; |
||||
layoutParams.addRule(RelativeLayout.ALIGN_TOP, recyclerViewId); |
||||
layoutParams.addRule(RelativeLayout.ALIGN_BOTTOM, recyclerViewId); |
||||
layoutParams.addRule(endRule, recyclerViewId); |
||||
layoutParams.setMargins(0, marginTop, 0, marginBottom); |
||||
setLayoutParams(layoutParams); |
||||
} else { |
||||
throw new IllegalArgumentException("Parent ViewGroup must be a ConstraintLayout, CoordinatorLayout, FrameLayout, or RelativeLayout"); |
||||
} |
||||
updateViewHeights(); |
||||
} |
||||
|
||||
public void setSectionIndexer(SectionIndexer sectionIndexer) { |
||||
mSectionIndexer = sectionIndexer; |
||||
} |
||||
|
||||
public void attachRecyclerView(RecyclerView recyclerView) { |
||||
mRecyclerView = recyclerView; |
||||
if (mRecyclerView != null) { |
||||
mRecyclerView.addOnScrollListener(mScrollListener); |
||||
post(() -> { |
||||
// set initial positions for bubble and handle
|
||||
setViewPositions(getScrollProportion(mRecyclerView)); |
||||
}); |
||||
} |
||||
} |
||||
|
||||
public void detachRecyclerView() { |
||||
if (mRecyclerView != null) { |
||||
mRecyclerView.removeOnScrollListener(mScrollListener); |
||||
mRecyclerView = null; |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Hide the scrollbar when not scrolling. |
||||
* |
||||
* @param fadeScrollbar True to hide the scrollbar, false to show |
||||
*/ |
||||
public void setFadeScrollbar(boolean fadeScrollbar) { |
||||
mFadeScrollbar = fadeScrollbar; |
||||
mScrollbar.setVisibility(fadeScrollbar ? GONE : VISIBLE); |
||||
} |
||||
|
||||
/** |
||||
* Show the section bubble while scrolling. |
||||
* |
||||
* @param visible True to show the bubble, false to hide |
||||
*/ |
||||
public void setBubbleVisible(boolean visible) { |
||||
mShowBubble = visible; |
||||
} |
||||
|
||||
/** |
||||
* Display a scroll track while scrolling. |
||||
* |
||||
* @param visible True to show scroll track, false to hide |
||||
*/ |
||||
public void setTrackVisible(boolean visible) { |
||||
mTrackView.setVisibility(visible ? VISIBLE : GONE); |
||||
} |
||||
|
||||
/** |
||||
* Set the color of the scroll track. |
||||
* |
||||
* @param color The color for the scroll track |
||||
*/ |
||||
public void setTrackColor(@ColorInt int color) { |
||||
@ColorInt int trackColor = color; |
||||
if (mTrackImage == null) { |
||||
Drawable drawable = ContextCompat.getDrawable(getContext(), R.drawable.fastscroll_track); |
||||
if (drawable != null) { |
||||
mTrackImage = DrawableCompat.wrap(drawable); |
||||
} |
||||
} |
||||
DrawableCompat.setTint(mTrackImage, trackColor); |
||||
mTrackView.setImageDrawable(mTrackImage); |
||||
} |
||||
|
||||
/** |
||||
* Set the color for the scroll handle. |
||||
* |
||||
* @param color The color for the scroll handle |
||||
*/ |
||||
public void setHandleColor(@ColorInt int color) { |
||||
mHandleColor = color; |
||||
if (mHandleImage == null) { |
||||
Drawable drawable = ContextCompat.getDrawable(getContext(), R.drawable.fastscroll_handle); |
||||
if (drawable != null) { |
||||
mHandleImage = DrawableCompat.wrap(drawable); |
||||
} |
||||
} |
||||
DrawableCompat.setTint(mHandleImage, mHandleColor); |
||||
mHandleView.setImageDrawable(mHandleImage); |
||||
} |
||||
|
||||
/** |
||||
* Set the background color of the index bubble. |
||||
* |
||||
* @param color The background color for the index bubble |
||||
*/ |
||||
public void setBubbleColor(@ColorInt int color) { |
||||
mBubbleColor = color; |
||||
if (mBubbleImage == null) { |
||||
Drawable drawable = ContextCompat.getDrawable(getContext(), R.drawable.fastscroll_bubble); |
||||
if (drawable != null) { |
||||
mBubbleImage = DrawableCompat.wrap(drawable); |
||||
} |
||||
} |
||||
DrawableCompat.setTint(mBubbleImage, mBubbleColor); |
||||
mBubbleView.setBackground(mBubbleImage); |
||||
} |
||||
|
||||
/** |
||||
* Set the text color of the index bubble. |
||||
* |
||||
* @param color The text color for the index bubble |
||||
*/ |
||||
public void setBubbleTextColor(@ColorInt int color) { |
||||
mBubbleView.setTextColor(color); |
||||
} |
||||
|
||||
/** |
||||
* Set the fast scroll state change listener. |
||||
* |
||||
* @param fastScrollStateChangeListener The interface that will listen to fastscroll state change events |
||||
*/ |
||||
public void setFastScrollStateChangeListener(FastScrollStateChangeListener fastScrollStateChangeListener) { |
||||
mFastScrollStateChangeListener = fastScrollStateChangeListener; |
||||
} |
||||
|
||||
@Override |
||||
public void setEnabled(boolean enabled) { |
||||
super.setEnabled(enabled); |
||||
setVisibility(enabled ? VISIBLE : GONE); |
||||
} |
||||
|
||||
@Override |
||||
@SuppressLint("ClickableViewAccessibility") |
||||
public boolean onTouchEvent(MotionEvent event) { |
||||
switch (event.getAction()) { |
||||
case MotionEvent.ACTION_DOWN: |
||||
if (event.getX() < mHandleView.getX() - ViewCompat.getPaddingStart(mHandleView)) { |
||||
return false; |
||||
} |
||||
requestDisallowInterceptTouchEvent(true); |
||||
setHandleSelected(true); |
||||
getHandler().removeCallbacks(mScrollbarHider); |
||||
cancelAnimation(mScrollbarAnimator); |
||||
cancelAnimation(mBubbleAnimator); |
||||
if (!isViewVisible(mScrollbar)) { |
||||
showScrollbar(); |
||||
} |
||||
if (mShowBubble && mSectionIndexer != null) { |
||||
showBubble(); |
||||
} |
||||
if (mFastScrollStateChangeListener != null) { |
||||
mFastScrollStateChangeListener.onFastScrollStart(this); |
||||
} |
||||
case MotionEvent.ACTION_MOVE: |
||||
final float y = event.getY(); |
||||
setViewPositions(y); |
||||
setRecyclerViewPosition(y); |
||||
return true; |
||||
case MotionEvent.ACTION_UP: |
||||
case MotionEvent.ACTION_CANCEL: |
||||
requestDisallowInterceptTouchEvent(false); |
||||
setHandleSelected(false); |
||||
if (mFadeScrollbar) { |
||||
getHandler().postDelayed(mScrollbarHider, sScrollbarHideDelay); |
||||
} |
||||
hideBubble(); |
||||
if (mFastScrollStateChangeListener != null) { |
||||
mFastScrollStateChangeListener.onFastScrollStop(this); |
||||
} |
||||
return true; |
||||
} |
||||
return super.onTouchEvent(event); |
||||
} |
||||
|
||||
@Override |
||||
protected void onSizeChanged(int w, int h, int oldw, int oldh) { |
||||
super.onSizeChanged(w, h, oldw, oldh); |
||||
mViewHeight = h; |
||||
} |
||||
|
||||
private void setRecyclerViewPosition(float y) { |
||||
if (mRecyclerView != null && mRecyclerView.getAdapter() != null) { |
||||
int itemCount = mRecyclerView.getAdapter().getItemCount(); |
||||
float proportion; |
||||
if (mHandleView.getY() == 0) { |
||||
proportion = 0f; |
||||
} else if (mHandleView.getY() + mHandleHeight >= mViewHeight - sTrackSnapRange) { |
||||
proportion = 1f; |
||||
} else { |
||||
proportion = y / (float) mViewHeight; |
||||
} |
||||
int scrolledItemCount = Math.round(proportion * itemCount); |
||||
if (isLayoutReversed(mRecyclerView.getLayoutManager())) { |
||||
scrolledItemCount = itemCount - scrolledItemCount; |
||||
} |
||||
int targetPos = getValueInRange(0, itemCount - 1, scrolledItemCount); |
||||
mRecyclerView.getLayoutManager().scrollToPosition(targetPos); |
||||
if (mShowBubble && mSectionIndexer != null) { |
||||
mBubbleView.setText(mSectionIndexer.getSectionText(targetPos)); |
||||
} |
||||
} |
||||
} |
||||
|
||||
private float getScrollProportion(RecyclerView recyclerView) { |
||||
if (recyclerView == null) { |
||||
return 0; |
||||
} |
||||
final int verticalScrollOffset = recyclerView.computeVerticalScrollOffset(); |
||||
final int verticalScrollRange = recyclerView.computeVerticalScrollRange(); |
||||
final float rangeDiff = verticalScrollRange - mViewHeight; |
||||
float proportion = (float) verticalScrollOffset / (rangeDiff > 0 ? rangeDiff : 1f); |
||||
return mViewHeight * proportion; |
||||
} |
||||
|
||||
@SuppressWarnings("SameParameterValue") |
||||
private int getValueInRange(int min, int max, int value) { |
||||
int minimum = Math.max(min, value); |
||||
return Math.min(minimum, max); |
||||
} |
||||
|
||||
private void setViewPositions(float y) { |
||||
mBubbleHeight = mBubbleView.getHeight(); |
||||
mHandleHeight = mHandleView.getHeight(); |
||||
int bubbleY = getValueInRange(0, mViewHeight - mBubbleHeight - mHandleHeight / 2, (int) (y - mBubbleHeight)); |
||||
int handleY = getValueInRange(0, mViewHeight - mHandleHeight, (int) (y - mHandleHeight / 2)); |
||||
if (mShowBubble) { |
||||
mBubbleView.setY(bubbleY); |
||||
} |
||||
mHandleView.setY(handleY); |
||||
} |
||||
|
||||
private void updateViewHeights() { |
||||
int measureSpec = MeasureSpec.makeMeasureSpec(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED); |
||||
mBubbleView.measure(measureSpec, measureSpec); |
||||
mBubbleHeight = mBubbleView.getMeasuredHeight(); |
||||
mHandleView.measure(measureSpec, measureSpec); |
||||
mHandleHeight = mHandleView.getMeasuredHeight(); |
||||
} |
||||
|
||||
private boolean isLayoutReversed(@NonNull final RecyclerView.LayoutManager layoutManager) { |
||||
if (layoutManager instanceof LinearLayoutManager) { |
||||
return ((LinearLayoutManager) layoutManager).getReverseLayout(); |
||||
} else if (layoutManager instanceof StaggeredGridLayoutManager) { |
||||
return ((StaggeredGridLayoutManager) layoutManager).getReverseLayout(); |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
private boolean isViewVisible(View view) { |
||||
return view != null && view.getVisibility() == VISIBLE; |
||||
} |
||||
|
||||
private void cancelAnimation(ViewPropertyAnimator animator) { |
||||
if (animator != null) { |
||||
animator.cancel(); |
||||
} |
||||
} |
||||
|
||||
private void showBubble() { |
||||
if (!isViewVisible(mBubbleView)) { |
||||
mBubbleView.setVisibility(VISIBLE); |
||||
mBubbleAnimator = mBubbleView.animate().alpha(1f) |
||||
.setDuration(sBubbleAnimDuration) |
||||
.setListener(new AnimatorListenerAdapter() { |
||||
// adapter required for new alpha value to stick
|
||||
}); |
||||
} |
||||
} |
||||
|
||||
private void hideBubble() { |
||||
if (isViewVisible(mBubbleView)) { |
||||
mBubbleAnimator = mBubbleView.animate().alpha(0f) |
||||
.setDuration(sBubbleAnimDuration) |
||||
.setListener(new AnimatorListenerAdapter() { |
||||
@Override |
||||
public void onAnimationEnd(Animator animation) { |
||||
super.onAnimationEnd(animation); |
||||
mBubbleView.setVisibility(GONE); |
||||
mBubbleAnimator = null; |
||||
} |
||||
|
||||
@Override |
||||
public void onAnimationCancel(Animator animation) { |
||||
super.onAnimationCancel(animation); |
||||
mBubbleView.setVisibility(GONE); |
||||
mBubbleAnimator = null; |
||||
} |
||||
}); |
||||
} |
||||
} |
||||
|
||||
private void showScrollbar() { |
||||
if (mRecyclerView.computeVerticalScrollRange() - mViewHeight > 0) { |
||||
float transX = getResources().getDimensionPixelSize(R.dimen.fastscroll_scrollbar_padding_end); |
||||
mScrollbar.setTranslationX(transX); |
||||
mScrollbar.setVisibility(VISIBLE); |
||||
mScrollbarAnimator = mScrollbar.animate().translationX(0f).alpha(1f) |
||||
.setDuration(sScrollbarAnimDuration) |
||||
.setListener(new AnimatorListenerAdapter() { |
||||
// adapter required for new alpha value to stick
|
||||
}); |
||||
} |
||||
} |
||||
|
||||
private void hideScrollbar() { |
||||
float transX = getResources().getDimensionPixelSize(R.dimen.fastscroll_scrollbar_padding_end); |
||||
mScrollbarAnimator = mScrollbar.animate().translationX(transX).alpha(0f) |
||||
.setDuration(sScrollbarAnimDuration) |
||||
.setListener(new AnimatorListenerAdapter() { |
||||
@Override |
||||
public void onAnimationEnd(Animator animation) { |
||||
super.onAnimationEnd(animation); |
||||
mScrollbar.setVisibility(GONE); |
||||
mScrollbarAnimator = null; |
||||
} |
||||
|
||||
@Override |
||||
public void onAnimationCancel(Animator animation) { |
||||
super.onAnimationCancel(animation); |
||||
mScrollbar.setVisibility(GONE); |
||||
mScrollbarAnimator = null; |
||||
} |
||||
}); |
||||
} |
||||
|
||||
private void setHandleSelected(boolean selected) { |
||||
mHandleView.setSelected(selected); |
||||
DrawableCompat.setTint(mHandleImage, selected ? mBubbleColor : mHandleColor); |
||||
} |
||||
|
||||
@SuppressWarnings("ConstantConditions") |
||||
private void layout(Context context, AttributeSet attrs) { |
||||
inflate(context, R.layout.view_fastscroller, this); |
||||
setClipChildren(false); |
||||
setOrientation(HORIZONTAL); |
||||
mBubbleView = findViewById(R.id.fastscroll_bubble); |
||||
mHandleView = findViewById(R.id.fastscroll_handle); |
||||
mTrackView = findViewById(R.id.fastscroll_track); |
||||
mScrollbar = findViewById(R.id.fastscroll_scrollbar); |
||||
@ColorInt int bubbleColor = ColorUtil.adjustAlpha(context.getResources().getColor(R.color.colorAccent), 0.8f); |
||||
@ColorInt int handleColor = context.getResources().getColor(R.color.colorAccent); |
||||
@ColorInt int trackColor = context.getResources().getColor(R.color.transparent30); |
||||
@ColorInt int textColor = ColorUtil.isColorLight(bubbleColor) ? Color.BLACK : Color.WHITE; |
||||
boolean fadeScrollbar = true; |
||||
boolean showBubble = false; |
||||
boolean showTrack = true; |
||||
if (attrs != null) { |
||||
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.FastScroller, 0, 0); |
||||
if (typedArray != null) { |
||||
try { |
||||
bubbleColor = typedArray.getColor(R.styleable.FastScroller_bubbleColor, bubbleColor); |
||||
handleColor = typedArray.getColor(R.styleable.FastScroller_handleColor, handleColor); |
||||
trackColor = typedArray.getColor(R.styleable.FastScroller_trackColor, trackColor); |
||||
textColor = typedArray.getColor(R.styleable.FastScroller_bubbleTextColor, textColor); |
||||
fadeScrollbar = typedArray.getBoolean(R.styleable.FastScroller_fadeScrollbar, fadeScrollbar); |
||||
showBubble = typedArray.getBoolean(R.styleable.FastScroller_showBubble, showBubble); |
||||
showTrack = typedArray.getBoolean(R.styleable.FastScroller_showTrack, showTrack); |
||||
} finally { |
||||
typedArray.recycle(); |
||||
} |
||||
} |
||||
} |
||||
setTrackColor(trackColor); |
||||
setHandleColor(handleColor); |
||||
setBubbleColor(bubbleColor); |
||||
setBubbleTextColor(textColor); |
||||
setFadeScrollbar(fadeScrollbar); |
||||
setBubbleVisible(showBubble); |
||||
setTrackVisible(showTrack); |
||||
} |
||||
|
||||
public interface SectionIndexer { |
||||
String getSectionText(int position); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,39 @@ |
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
<!-- |
||||
~ Copyright 2016 L4 Digital LLC. All rights reserved. |
||||
~ |
||||
~ 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. |
||||
--> |
||||
|
||||
<shape |
||||
xmlns:android="http://schemas.android.com/apk/res/android" |
||||
xmlns:tools="http://schemas.android.com/tools" |
||||
android:shape="rectangle"> |
||||
|
||||
<tools:solid android:color="#777777" /> |
||||
|
||||
<corners |
||||
android:topLeftRadius="@dimen/fastscroll_bubble_radius" |
||||
android:topRightRadius="@dimen/fastscroll_bubble_radius" |
||||
android:bottomLeftRadius="@dimen/fastscroll_bubble_radius" |
||||
android:bottomRightRadius="0dp" /> |
||||
|
||||
<size |
||||
android:height="@dimen/fastscroll_bubble_size" |
||||
android:width="@dimen/fastscroll_bubble_size" /> |
||||
|
||||
<padding |
||||
android:left="@dimen/fastscroll_bubble_padding" |
||||
android:right="@dimen/fastscroll_bubble_padding" /> |
||||
|
||||
</shape> |
@ -0,0 +1,31 @@ |
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
<!-- |
||||
~ Copyright 2016 L4 Digital LLC. All rights reserved. |
||||
~ |
||||
~ 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. |
||||
--> |
||||
|
||||
<shape |
||||
xmlns:android="http://schemas.android.com/apk/res/android" |
||||
xmlns:tools="http://schemas.android.com/tools" |
||||
android:shape="rectangle"> |
||||
|
||||
<tools:solid android:color="#555555" /> |
||||
|
||||
<corners android:radius="@dimen/fastscroll_handle_radius" /> |
||||
|
||||
<size |
||||
android:height="@dimen/fastscroll_handle_height" |
||||
android:width="@dimen/fastscroll_handle_width" /> |
||||
|
||||
</shape> |
@ -0,0 +1,27 @@ |
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
<!-- |
||||
~ Copyright 2016 L4 Digital LLC. All rights reserved. |
||||
~ |
||||
~ 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. |
||||
--> |
||||
|
||||
<shape |
||||
xmlns:android="http://schemas.android.com/apk/res/android" |
||||
xmlns:tools="http://schemas.android.com/tools" |
||||
android:shape="rectangle"> |
||||
|
||||
<tools:solid android:color="#CCCCCC" /> |
||||
|
||||
<size android:width="@dimen/fastscroll_track_width" /> |
||||
|
||||
</shape> |
@ -0,0 +1,50 @@ |
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
|
||||
<merge |
||||
xmlns:android="http://schemas.android.com/apk/res/android" |
||||
xmlns:tools="http://schemas.android.com/tools"> |
||||
|
||||
<TextView |
||||
android:id="@+id/fastscroll_bubble" |
||||
android:layout_width="wrap_content" |
||||
android:layout_height="wrap_content" |
||||
android:layout_gravity="end" |
||||
android:gravity="center" |
||||
android:maxLines="1" |
||||
android:textSize="@dimen/fastscroll_bubble_textsize" |
||||
android:visibility="gone" |
||||
tools:background="@drawable/fastscroll_bubble" |
||||
tools:text="A" |
||||
tools:textColor="#ffffff" |
||||
tools:visibility="visible" /> |
||||
|
||||
<FrameLayout |
||||
android:id="@+id/fastscroll_scrollbar" |
||||
android:layout_width="wrap_content" |
||||
android:layout_height="match_parent" |
||||
android:paddingEnd="@dimen/fastscroll_scrollbar_padding_end" |
||||
android:paddingLeft="@dimen/fastscroll_scrollbar_padding_start" |
||||
android:paddingRight="@dimen/fastscroll_scrollbar_padding_end" |
||||
android:paddingStart="@dimen/fastscroll_scrollbar_padding_start" |
||||
android:visibility="gone" |
||||
tools:visibility="visible"> |
||||
|
||||
<ImageView |
||||
android:id="@+id/fastscroll_track" |
||||
android:layout_width="wrap_content" |
||||
android:layout_height="match_parent" |
||||
android:layout_gravity="center_horizontal" |
||||
tools:ignore="ContentDescription" |
||||
tools:src="@drawable/fastscroll_track" /> |
||||
|
||||
<ImageView |
||||
android:id="@+id/fastscroll_handle" |
||||
android:layout_width="wrap_content" |
||||
android:layout_height="wrap_content" |
||||
android:layout_gravity="center_horizontal" |
||||
tools:ignore="ContentDescription" |
||||
tools:src="@drawable/fastscroll_handle" /> |
||||
|
||||
</FrameLayout> |
||||
|
||||
</merge> |
Loading…
Reference in new issue