That fuck shit the fascists are using
at master 204 lines 7.4 kB view raw
1/** 2 * Modified version of 3 * https://github.com/AndroidDeveloperLB/LollipopContactsRecyclerViewFastScroller 4 * 5 * Their license: 6 * 7 * Licensed under the Apache License, Version 2.0 (the "License"); 8 * you may not use this file except in compliance with the License. 9 * You may obtain a copy of the License at 10 * 11 * http://www.apache.org/licenses/LICENSE-2.0 12 * 13 * Unless required by applicable law or agreed to in writing, software 14 * distributed under the License is distributed on an "AS IS" BASIS, 15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 * See the License for the specific language governing permissions and 17 * limitations under the License. 18 */ 19package org.tm.archive.components; 20 21import android.animation.Animator; 22import android.animation.AnimatorListenerAdapter; 23import android.animation.ObjectAnimator; 24import android.content.Context; 25import android.util.AttributeSet; 26import android.view.MotionEvent; 27import android.view.View; 28import android.view.ViewTreeObserver; 29import android.widget.LinearLayout; 30import android.widget.TextView; 31 32import androidx.annotation.NonNull; 33import androidx.annotation.Nullable; 34import androidx.recyclerview.widget.LinearLayoutManager; 35import androidx.recyclerview.widget.RecyclerView; 36 37import org.tm.archive.R; 38import org.tm.archive.util.Util; 39 40public final class RecyclerViewFastScroller extends LinearLayout { 41 private static final int BUBBLE_ANIMATION_DURATION = 100; 42 private static final int TRACK_SNAP_RANGE = 5; 43 44 @NonNull private final TextView bubble; 45 @NonNull private final View handle; 46 @Nullable private RecyclerView recyclerView; 47 48 private int height; 49 private ObjectAnimator currentAnimator; 50 51 private final RecyclerView.OnScrollListener onScrollListener = new RecyclerView.OnScrollListener() { 52 @Override 53 public void onScrolled(@NonNull final RecyclerView recyclerView, final int dx, final int dy) { 54 if (handle.isSelected()) return; 55 final int offset = recyclerView.computeVerticalScrollOffset(); 56 final int range = recyclerView.computeVerticalScrollRange(); 57 final int extent = recyclerView.computeVerticalScrollExtent(); 58 final int offsetRange = Math.max(range - extent, 1); 59 setBubbleAndHandlePosition((float) Util.clamp(offset, 0, offsetRange) / offsetRange); 60 } 61 }; 62 63 public interface FastScrollAdapter { 64 CharSequence getBubbleText(int position); 65 } 66 67 public RecyclerViewFastScroller(final Context context) { 68 this(context, null); 69 } 70 71 public RecyclerViewFastScroller(final Context context, final AttributeSet attrs) { 72 super(context, attrs); 73 setOrientation(HORIZONTAL); 74 setClipChildren(false); 75 setScrollContainer(true); 76 inflate(context, R.layout.recycler_view_fast_scroller, this); 77 bubble = findViewById(R.id.fastscroller_bubble); 78 handle = findViewById(R.id.fastscroller_handle); 79 } 80 81 @Override 82 protected void onSizeChanged(int w, int h, int oldw, int oldh) { 83 super.onSizeChanged(w, h, oldw, oldh); 84 height = h; 85 } 86 87 @Override 88 public boolean onTouchEvent(@NonNull MotionEvent event) { 89 final int action = event.getAction(); 90 switch (action) { 91 case MotionEvent.ACTION_DOWN: 92 if (event.getX() < handle.getX() - handle.getPaddingLeft() || 93 event.getY() < handle.getY() - handle.getPaddingTop() || 94 event.getY() > handle.getY() + handle.getHeight() + handle.getPaddingBottom()) 95 { 96 return false; 97 } 98 if (currentAnimator != null) { 99 currentAnimator.cancel(); 100 } 101 if (bubble.getVisibility() != VISIBLE) { 102 showBubble(); 103 } 104 handle.setSelected(true); 105 case MotionEvent.ACTION_MOVE: 106 final float y = event.getY(); 107 setBubbleAndHandlePosition(y / height); 108 setRecyclerViewPosition(y); 109 return true; 110 case MotionEvent.ACTION_UP: 111 case MotionEvent.ACTION_CANCEL: 112 handle.setSelected(false); 113 hideBubble(); 114 return true; 115 } 116 return super.onTouchEvent(event); 117 } 118 119 public void setRecyclerView(final @Nullable RecyclerView recyclerView) { 120 if (this.recyclerView != null) { 121 this.recyclerView.removeOnScrollListener(onScrollListener); 122 } 123 this.recyclerView = recyclerView; 124 if (recyclerView != null) { 125 recyclerView.addOnScrollListener(onScrollListener); 126 recyclerView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { 127 @Override 128 public boolean onPreDraw() { 129 recyclerView.getViewTreeObserver().removeOnPreDrawListener(this); 130 if (handle.isSelected()) return true; 131 final int verticalScrollOffset = recyclerView.computeVerticalScrollOffset(); 132 final int verticalScrollRange = recyclerView.computeVerticalScrollRange(); 133 float proportion = (float)verticalScrollOffset / ((float)verticalScrollRange - height); 134 setBubbleAndHandlePosition(height * proportion); 135 return true; 136 } 137 }); 138 } 139 } 140 141 @Override 142 protected void onDetachedFromWindow() { 143 super.onDetachedFromWindow(); 144 if (recyclerView != null) 145 recyclerView.removeOnScrollListener(onScrollListener); 146 } 147 148 private void setRecyclerViewPosition(float y) { 149 if (recyclerView != null) { 150 final int itemCount = recyclerView.getAdapter().getItemCount(); 151 float proportion; 152 if (handle.getY() == 0) { 153 proportion = 0f; 154 } else if (handle.getY() + handle.getHeight() >= height - TRACK_SNAP_RANGE) { 155 proportion = 1f; 156 } else { 157 proportion = y / (float)height; 158 } 159 160 final int targetPos = Util.clamp((int)(proportion * (float)itemCount), 0, itemCount - 1); 161 ((LinearLayoutManager) recyclerView.getLayoutManager()).scrollToPositionWithOffset(targetPos, 0); 162 final CharSequence bubbleText = ((FastScrollAdapter) recyclerView.getAdapter()).getBubbleText(targetPos); 163 bubble.setText(bubbleText); 164 } 165 } 166 167 private void setBubbleAndHandlePosition(float y) { 168 final int handleHeight = handle.getHeight(); 169 final int bubbleHeight = bubble.getHeight(); 170 final int handleY = Util.clamp((int)((height - handleHeight) * y), 0, height - handleHeight); 171 handle.setY(handleY); 172 bubble.setY(Util.clamp(handleY - bubbleHeight - bubble.getPaddingBottom() + handleHeight, 173 0, 174 height - bubbleHeight)); 175 } 176 177 private void showBubble() { 178 bubble.setVisibility(VISIBLE); 179 if (currentAnimator != null) currentAnimator.cancel(); 180 currentAnimator = ObjectAnimator.ofFloat(bubble, "alpha", 0f, 1f).setDuration(BUBBLE_ANIMATION_DURATION); 181 currentAnimator.start(); 182 } 183 184 private void hideBubble() { 185 if (currentAnimator != null) currentAnimator.cancel(); 186 currentAnimator = ObjectAnimator.ofFloat(bubble, "alpha", 1f, 0f).setDuration(BUBBLE_ANIMATION_DURATION); 187 currentAnimator.addListener(new AnimatorListenerAdapter() { 188 @Override 189 public void onAnimationEnd(Animator animation) { 190 super.onAnimationEnd(animation); 191 bubble.setVisibility(INVISIBLE); 192 currentAnimator = null; 193 } 194 195 @Override 196 public void onAnimationCancel(Animator animation) { 197 super.onAnimationCancel(animation); 198 bubble.setVisibility(INVISIBLE); 199 currentAnimator = null; 200 } 201 }); 202 currentAnimator.start(); 203 } 204}