That fuck shit the fascists are using
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}