NekoX/TMessagesProj/src/main/java/org/telegram/ui/Components/RecyclerListView.java

2711 lines
113 KiB
Java
Raw Normal View History

2015-05-21 23:27:47 +02:00
/*
2019-01-23 18:03:33 +01:00
* This is the source code of Telegram for Android v. 5.x.x
2015-05-21 23:27:47 +02:00
* It is licensed under GNU GPL v. 2 or later.
* You should have received a copy of the license in this archive (see LICENSE).
*
2019-01-23 18:03:33 +01:00
* Copyright Nikolai Kudashov, 2013-2018.
2015-05-21 23:27:47 +02:00
*/
package org.telegram.ui.Components;
2020-07-26 10:03:38 +02:00
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
2019-05-14 14:08:05 +02:00
import android.annotation.SuppressLint;
2015-05-21 23:27:47 +02:00
import android.content.Context;
import android.content.res.TypedArray;
2017-03-31 01:58:05 +02:00
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
2021-11-05 11:06:49 +01:00
import android.graphics.PorterDuff;
import android.graphics.PorterDuffColorFilter;
2022-08-12 17:23:51 +02:00
import android.graphics.Rect;
2017-03-31 01:58:05 +02:00
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.TransitionDrawable;
import android.os.Build;
2019-01-23 18:03:33 +01:00
import android.os.SystemClock;
2017-03-31 01:58:05 +02:00
import android.text.Layout;
2021-12-30 11:52:40 +01:00
import android.text.SpannableStringBuilder;
2017-03-31 01:58:05 +02:00
import android.text.StaticLayout;
import android.text.TextPaint;
2018-07-30 04:07:02 +02:00
import android.util.SparseIntArray;
2017-03-31 01:58:05 +02:00
import android.util.StateSet;
2015-05-21 23:27:47 +02:00
import android.view.HapticFeedbackConstants;
import android.view.MotionEvent;
import android.view.SoundEffectConstants;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
2020-03-30 14:00:09 +02:00
import android.view.ViewParent;
2020-09-30 15:48:47 +02:00
import android.view.ViewPropertyAnimator;
2019-05-14 14:08:05 +02:00
import android.view.accessibility.AccessibilityEvent;
2021-12-30 11:52:40 +01:00
import android.view.accessibility.AccessibilityNodeInfo;
2019-12-31 14:08:08 +01:00
import android.widget.FrameLayout;
2015-05-21 23:27:47 +02:00
2022-03-11 17:49:54 +01:00
import androidx.annotation.IntDef;
2021-12-30 11:52:40 +01:00
import androidx.core.content.ContextCompat;
import androidx.core.graphics.ColorUtils;
2022-08-12 17:23:51 +02:00
import androidx.core.util.Consumer;
2022-11-05 13:34:47 +01:00
import androidx.recyclerview.widget.DiffUtil;
2021-12-30 11:52:40 +01:00
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
2015-09-24 22:52:02 +02:00
import org.telegram.messenger.AndroidUtilities;
2015-05-21 23:27:47 +02:00
import org.telegram.messenger.FileLog;
2021-12-30 11:52:40 +01:00
import org.telegram.messenger.LocaleController;
2021-11-05 11:06:49 +01:00
import org.telegram.messenger.R;
2022-09-16 20:48:21 +02:00
import org.telegram.messenger.SharedConfig;
2017-03-31 01:58:05 +02:00
import org.telegram.ui.ActionBar.Theme;
2015-05-21 23:27:47 +02:00
2022-03-11 17:49:54 +01:00
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
2015-05-21 23:27:47 +02:00
import java.lang.reflect.Field;
import java.lang.reflect.Method;
2017-03-31 01:58:05 +02:00
import java.util.ArrayList;
2021-07-15 16:24:57 +02:00
import java.util.HashSet;
2022-11-05 13:34:47 +01:00
import java.util.Objects;
2015-05-21 23:27:47 +02:00
2022-11-20 22:12:39 +01:00
@SuppressWarnings("JavaReflectionMemberAccess")
2015-05-21 23:27:47 +02:00
public class RecyclerListView extends RecyclerView {
2022-03-11 17:49:54 +01:00
public final static int SECTIONS_TYPE_SIMPLE = 0,
SECTIONS_TYPE_STICKY_HEADERS = 1,
SECTIONS_TYPE_DATE = 2,
SECTIONS_TYPE_FAST_SCROLL_ONLY = 3;
@Retention(RetentionPolicy.SOURCE)
@IntDef({
SECTIONS_TYPE_SIMPLE,
SECTIONS_TYPE_STICKY_HEADERS,
SECTIONS_TYPE_DATE,
SECTIONS_TYPE_FAST_SCROLL_ONLY
})
public @interface SectionsType {}
2015-05-21 23:27:47 +02:00
2022-11-05 13:34:47 +01:00
public final static int EMPTY_VIEW_ANIMATION_TYPE_ALPHA_SCALE = 1;
public final static int EMPTY_VIEW_ANIMATION_TYPE_ALPHA = 0;
2015-05-21 23:27:47 +02:00
private OnItemClickListener onItemClickListener;
2017-12-08 18:35:59 +01:00
private OnItemClickListenerExtended onItemClickListenerExtended;
2015-05-21 23:27:47 +02:00
private OnItemLongClickListener onItemLongClickListener;
2018-07-30 04:07:02 +02:00
private OnItemLongClickListenerExtended onItemLongClickListenerExtended;
private boolean longPressCalled;
2017-03-31 01:58:05 +02:00
private OnScrollListener onScrollListener;
2015-05-21 23:27:47 +02:00
private OnInterceptTouchListener onInterceptTouchListener;
private View emptyView;
2019-12-31 14:08:08 +01:00
private FrameLayout overlayContainer;
2015-05-21 23:27:47 +02:00
private Runnable selectChildRunnable;
2017-03-31 01:58:05 +02:00
private FastScroll fastScroll;
private SectionsAdapter sectionsAdapter;
2015-05-21 23:27:47 +02:00
2019-12-31 14:08:08 +01:00
private boolean isHidden;
2019-05-14 14:08:05 +02:00
private boolean disableHighlightState;
2019-12-31 14:08:08 +01:00
private boolean allowItemsInteractionDuringAnimation = true;
2019-01-23 18:03:33 +01:00
private Drawable pinnedHeaderShadowDrawable;
private float pinnedHeaderShadowAlpha;
private float pinnedHeaderShadowTargetAlpha;
private long lastAlphaAnimationTime;
2017-03-31 01:58:05 +02:00
private ArrayList<View> headers;
private ArrayList<View> headersCache;
private View pinnedHeader;
private int currentFirst = -1;
private int currentVisible = -1;
private int startSection;
private int sectionsCount;
2019-01-23 18:03:33 +01:00
private int sectionOffset;
2017-03-31 01:58:05 +02:00
2022-03-11 17:49:54 +01:00
@SectionsType
private int sectionsType;
2019-12-31 14:08:08 +01:00
private boolean hideIfEmpty = true;
2020-03-30 14:00:09 +02:00
private boolean drawSelectorBehind;
private int selectorType = 2;
2019-12-31 14:08:08 +01:00
protected Drawable selectorDrawable;
protected int selectorPosition;
2022-11-05 13:34:47 +01:00
protected View selectorView;
2019-12-31 14:08:08 +01:00
protected android.graphics.Rect selectorRect = new android.graphics.Rect();
2017-03-31 01:58:05 +02:00
private boolean isChildViewEnabled;
private boolean selfOnLayout;
2021-11-05 11:06:49 +01:00
public boolean scrollingByUser;
2022-09-16 20:48:21 +02:00
public boolean scrolledByUserOnce;
2019-01-23 18:03:33 +01:00
2021-12-30 11:52:40 +01:00
private GestureDetectorFixDoubleTap gestureDetector;
2015-05-21 23:27:47 +02:00
private View currentChildView;
private int currentChildPosition;
private boolean interceptedByChild;
private boolean wasPressed;
private boolean disallowInterceptTouchEvents;
private boolean instantClick;
2015-12-09 19:27:52 +01:00
private Runnable clickRunnable;
2017-03-31 01:58:05 +02:00
private boolean ignoreOnScroll;
2015-05-21 23:27:47 +02:00
2017-12-08 18:35:59 +01:00
private boolean scrollEnabled = true;
2019-05-14 14:08:05 +02:00
private IntReturnCallback pendingHighlightPosition;
private Runnable removeHighlighSelectionRunnable;
2015-05-21 23:27:47 +02:00
private static int[] attributes;
private static boolean gotAttributes;
2017-12-08 18:35:59 +01:00
private boolean hiddenByEmptyView;
2020-07-26 10:03:38 +02:00
public boolean fastScrollAnimationRunning;
private boolean animateEmptyView;
2020-09-30 15:48:47 +02:00
private int emptyViewAnimationType;
private int selectorRadius;
2020-12-23 08:48:30 +01:00
private int topBottomSelectorRadius;
2021-07-15 16:24:57 +02:00
private int touchSlop;
boolean useRelativePositions;
boolean multiSelectionGesture;
boolean multiSelectionGestureStarted;
int startSelectionFrom;
int currentSelectedPosition;
onMultiSelectionChanged multiSelectionListener;
boolean multiselectScrollRunning;
boolean multiselectScrollToTop;
float lastX = Float.MAX_VALUE;
float lastY = Float.MAX_VALUE;
int[] listPaddings;
HashSet<Integer> selectedPositions;
2021-12-07 14:02:02 +01:00
RecyclerItemsEnterAnimator itemsEnterAnimator;
2017-12-08 18:35:59 +01:00
2022-09-16 20:48:21 +02:00
protected Consumer<Canvas> selectorTransformer;
2022-08-12 17:23:51 +02:00
2021-09-20 07:54:41 +02:00
protected final Theme.ResourcesProvider resourcesProvider;
2021-12-30 11:52:40 +01:00
private boolean accessibilityEnabled = true;
2022-11-20 22:12:39 +01:00
private final static Method initializeScrollbars;
static {
Method notSoFinalInitializeScrollbars;
try {
notSoFinalInitializeScrollbars = android.view.View.class.getDeclaredMethod("initializeScrollbars", TypedArray.class);
} catch (Exception ignored) {
notSoFinalInitializeScrollbars = null;
}
initializeScrollbars = notSoFinalInitializeScrollbars;
}
2021-12-30 11:52:40 +01:00
private AccessibilityDelegate accessibilityDelegate = new AccessibilityDelegate() {
@Override
public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
super.onInitializeAccessibilityNodeInfo(host, info);
if (host.isEnabled()) {
info.addAction(AccessibilityNodeInfo.ACTION_CLICK);
}
}
};
2022-08-12 17:23:51 +02:00
public void setSelectorTransformer(Consumer<Canvas> transformer) {
selectorTransformer = transformer;
}
2021-11-05 11:06:49 +01:00
public FastScroll getFastScroll() {
return fastScroll;
}
2015-05-21 23:27:47 +02:00
public interface OnItemClickListener {
void onItemClick(View view, int position);
}
2017-12-08 18:35:59 +01:00
public interface OnItemClickListenerExtended {
2021-12-30 11:52:40 +01:00
default boolean hasDoubleTap(View view, int position) {
return false;
}
default void onDoubleTap(View view, int position, float x, float y) {
}
2017-12-08 18:35:59 +01:00
void onItemClick(View view, int position, float x, float y);
}
2015-05-21 23:27:47 +02:00
public interface OnItemLongClickListener {
2015-10-29 18:10:07 +01:00
boolean onItemClick(View view, int position);
2015-05-21 23:27:47 +02:00
}
2018-07-30 04:07:02 +02:00
public interface OnItemLongClickListenerExtended {
boolean onItemClick(View view, int position, float x, float y);
2021-12-30 11:52:40 +01:00
default void onMove(float dx, float dy) {
}
default void onLongClickRelease() {
}
2018-07-30 04:07:02 +02:00
}
2015-05-21 23:27:47 +02:00
public interface OnInterceptTouchListener {
boolean onInterceptTouchEvent(MotionEvent event);
}
2017-03-31 01:58:05 +02:00
public abstract static class SelectionAdapter extends Adapter {
public abstract boolean isEnabled(ViewHolder holder);
2019-01-23 18:03:33 +01:00
public int getSelectionBottomPadding(View view) {
return 0;
}
2017-03-31 01:58:05 +02:00
}
public abstract static class FastScrollAdapter extends SelectionAdapter {
public abstract String getLetter(int position);
2021-12-30 11:52:40 +01:00
2021-11-05 11:06:49 +01:00
public abstract void getPositionForScrollProgress(RecyclerListView listView, float progress, int[] position);
2021-12-30 11:52:40 +01:00
2021-11-05 11:06:49 +01:00
public void onStartFastScroll() {
}
2021-12-30 11:52:40 +01:00
2021-11-05 11:06:49 +01:00
public void onFinishFastScroll(RecyclerListView listView) {
}
public int getTotalItemsCount() {
return getItemCount();
}
public float getScrollProgress(RecyclerListView listView) {
return listView.computeVerticalScrollOffset() / ((float) getTotalItemsCount() * listView.getChildAt(0).getMeasuredHeight() - listView.getMeasuredHeight());
}
public boolean fastScrollIsVisible(RecyclerListView listView) {
return true;
}
2021-12-30 11:52:40 +01:00
2021-11-05 11:06:49 +01:00
public void onFastScrollSingleTap() {
}
2017-03-31 01:58:05 +02:00
}
2019-05-14 14:08:05 +02:00
public interface IntReturnCallback {
int run();
}
2017-03-31 01:58:05 +02:00
public abstract static class SectionsAdapter extends FastScrollAdapter {
2018-07-30 04:07:02 +02:00
private SparseIntArray sectionPositionCache;
private SparseIntArray sectionCache;
private SparseIntArray sectionCountCache;
2017-03-31 01:58:05 +02:00
private int sectionCount;
private int count;
2022-11-05 13:34:47 +01:00
private ArrayList<Integer> hashes = new ArrayList<>();
2017-03-31 01:58:05 +02:00
private void cleanupCache() {
2019-01-23 18:03:33 +01:00
if (sectionCache == null) {
sectionCache = new SparseIntArray();
sectionPositionCache = new SparseIntArray();
sectionCountCache = new SparseIntArray();
} else {
sectionCache.clear();
sectionPositionCache.clear();
sectionCountCache.clear();
}
2017-03-31 01:58:05 +02:00
count = -1;
sectionCount = -1;
}
2019-01-23 18:03:33 +01:00
public void notifySectionsChanged() {
cleanupCache();
}
2017-03-31 01:58:05 +02:00
public SectionsAdapter() {
super();
cleanupCache();
}
@Override
public void notifyDataSetChanged() {
2022-11-05 13:34:47 +01:00
update(false);
2017-03-31 01:58:05 +02:00
}
@Override
public boolean isEnabled(ViewHolder holder) {
int position = holder.getAdapterPosition();
2021-06-25 02:43:10 +02:00
return isEnabled(holder, getSectionForPosition(position), getPositionInSectionForPosition(position));
2017-03-31 01:58:05 +02:00
}
@Override
2018-07-30 04:07:02 +02:00
public int getItemCount() {
2017-03-31 01:58:05 +02:00
if (count >= 0) {
return count;
}
count = 0;
2019-01-23 18:03:33 +01:00
for (int i = 0, N = internalGetSectionCount(); i < N; i++) {
2017-03-31 01:58:05 +02:00
count += internalGetCountForSection(i);
}
return count;
}
public final Object getItem(int position) {
return getItem(getSectionForPosition(position), getPositionInSectionForPosition(position));
}
public final int getItemViewType(int position) {
return getItemViewType(getSectionForPosition(position), getPositionInSectionForPosition(position));
}
@Override
public final void onBindViewHolder(ViewHolder holder, int position) {
onBindViewHolder(getSectionForPosition(position), getPositionInSectionForPosition(position), holder);
}
private int internalGetCountForSection(int section) {
2018-07-30 04:07:02 +02:00
int cachedSectionCount = sectionCountCache.get(section, Integer.MAX_VALUE);
if (cachedSectionCount != Integer.MAX_VALUE) {
2017-03-31 01:58:05 +02:00
return cachedSectionCount;
}
int sectionCount = getCountForSection(section);
sectionCountCache.put(section, sectionCount);
return sectionCount;
}
private int internalGetSectionCount() {
if (sectionCount >= 0) {
return sectionCount;
}
sectionCount = getSectionCount();
return sectionCount;
}
public final int getSectionForPosition(int position) {
2018-07-30 04:07:02 +02:00
int cachedSection = sectionCache.get(position, Integer.MAX_VALUE);
if (cachedSection != Integer.MAX_VALUE) {
2017-03-31 01:58:05 +02:00
return cachedSection;
}
int sectionStart = 0;
2019-01-23 18:03:33 +01:00
for (int i = 0, N = internalGetSectionCount(); i < N; i++) {
2017-03-31 01:58:05 +02:00
int sectionCount = internalGetCountForSection(i);
int sectionEnd = sectionStart + sectionCount;
if (position >= sectionStart && position < sectionEnd) {
sectionCache.put(position, i);
return i;
}
sectionStart = sectionEnd;
}
return -1;
}
public int getPositionInSectionForPosition(int position) {
2018-07-30 04:07:02 +02:00
int cachedPosition = sectionPositionCache.get(position, Integer.MAX_VALUE);
if (cachedPosition != Integer.MAX_VALUE) {
2017-03-31 01:58:05 +02:00
return cachedPosition;
}
int sectionStart = 0;
2019-01-23 18:03:33 +01:00
for (int i = 0, N = internalGetSectionCount(); i < N; i++) {
2017-03-31 01:58:05 +02:00
int sectionCount = internalGetCountForSection(i);
int sectionEnd = sectionStart + sectionCount;
if (position >= sectionStart && position < sectionEnd) {
int positionInSection = position - sectionStart;
sectionPositionCache.put(position, positionInSection);
return positionInSection;
}
sectionStart = sectionEnd;
}
return -1;
}
2022-11-05 13:34:47 +01:00
public void update(boolean diff) {
cleanupCache();
ArrayList<Integer> oldHashes = new ArrayList<>(hashes);
hashes.clear();
for (int i = 0, N = internalGetSectionCount(); i < N; i++) {
int count = internalGetCountForSection(i);
for (int j = 0; j < count; ++j) {
hashes.add(Objects.hash(i * -49612, getItem(i, j)));
}
}
if (diff) {
DiffUtil.calculateDiff(new DiffUtil.Callback() {
@Override
public int getOldListSize() {
return oldHashes.size();
}
@Override
public int getNewListSize() {
return hashes.size();
}
@Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
return Objects.equals(oldHashes.get(oldItemPosition), hashes.get(newItemPosition));
}
@Override
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
return areItemsTheSame(oldItemPosition, newItemPosition);
}
}, true).dispatchUpdatesTo(this);
} else {
super.notifyDataSetChanged();
}
}
2017-03-31 01:58:05 +02:00
public abstract int getSectionCount();
2021-12-30 11:52:40 +01:00
2017-03-31 01:58:05 +02:00
public abstract int getCountForSection(int section);
2021-12-30 11:52:40 +01:00
2021-06-25 02:43:10 +02:00
public abstract boolean isEnabled(ViewHolder holder, int section, int row);
2021-12-30 11:52:40 +01:00
2017-03-31 01:58:05 +02:00
public abstract int getItemViewType(int section, int position);
2021-12-30 11:52:40 +01:00
2017-03-31 01:58:05 +02:00
public abstract Object getItem(int section, int position);
2021-12-30 11:52:40 +01:00
2017-03-31 01:58:05 +02:00
public abstract void onBindViewHolder(int section, int position, ViewHolder holder);
2021-12-30 11:52:40 +01:00
2017-03-31 01:58:05 +02:00
public abstract View getSectionHeaderView(int section, View view);
}
public static class Holder extends ViewHolder {
public Holder(View itemView) {
super(itemView);
}
}
2021-11-05 11:06:49 +01:00
public class FastScroll extends View {
public static final int LETTER_TYPE = 0;
public static final int DATE_TYPE = 1;
2017-03-31 01:58:05 +02:00
private RectF rect = new RectF();
private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
2021-11-05 11:06:49 +01:00
private Paint paint2 = new Paint(Paint.ANTI_ALIAS_FLAG);
2017-03-31 01:58:05 +02:00
private float progress;
private float lastY;
private float startDy;
private boolean pressed;
private StaticLayout letterLayout;
private StaticLayout oldLetterLayout;
2021-12-30 11:52:40 +01:00
private StaticLayout outLetterLayout;
private StaticLayout inLetterLayout;
private StaticLayout stableLetterLayout;
private float replaceLayoutProgress = 1f;
private boolean fromTop;
private float lastLetterY;
private float fromWidth;
2017-03-31 01:58:05 +02:00
private TextPaint letterPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
private String currentLetter;
private Path path = new Path();
2021-12-07 14:02:02 +01:00
private Path arrowPath = new Path();
2017-03-31 01:58:05 +02:00
private float[] radii = new float[8];
private float textX;
private float textY;
private float bubbleProgress;
private long lastUpdateTime;
private int scrollX;
2021-11-05 11:06:49 +01:00
private int type;
private int inactiveColor;
private int activeColor;
private boolean floatingDateVisible;
private float floatingDateProgress;
private int[] positionWithOffset = new int[2];
boolean isVisible;
float touchSlop;
2021-12-07 14:02:02 +01:00
Drawable fastScrollShadowDrawable;
2021-11-05 11:06:49 +01:00
Drawable fastScrollBackgroundDrawable;
2021-12-09 17:28:33 +01:00
boolean isRtl;
2021-11-05 11:06:49 +01:00
Runnable hideFloatingDateRunnable = new Runnable() {
@Override
public void run() {
if (pressed) {
AndroidUtilities.cancelRunOnUIThread(hideFloatingDateRunnable);
AndroidUtilities.runOnUIThread(hideFloatingDateRunnable, 4000);
} else {
floatingDateVisible = false;
invalidate();
}
}
};
2017-03-31 01:58:05 +02:00
2021-11-05 11:06:49 +01:00
public FastScroll(Context context, int type) {
2017-03-31 01:58:05 +02:00
super(context);
2021-11-05 11:06:49 +01:00
this.type = type;
if (type == LETTER_TYPE) {
letterPaint.setTextSize(AndroidUtilities.dp(45));
2021-12-09 17:28:33 +01:00
isRtl = LocaleController.isRTL;
2021-11-05 11:06:49 +01:00
} else {
2021-12-09 17:28:33 +01:00
isRtl = false;
2021-11-05 11:06:49 +01:00
letterPaint.setTextSize(AndroidUtilities.dp(13));
letterPaint.setTypeface(AndroidUtilities.getTypeface("fonts/rmedium.ttf"));
paint2.setColor(Theme.getColor(Theme.key_windowBackgroundWhite));
fastScrollBackgroundDrawable = ContextCompat.getDrawable(context, R.drawable.calendar_date).mutate();
2021-12-07 14:02:02 +01:00
fastScrollBackgroundDrawable.setColorFilter(new PorterDuffColorFilter(ColorUtils.blendARGB(Theme.getColor(Theme.key_windowBackgroundWhite), Color.WHITE, 0.1f), PorterDuff.Mode.MULTIPLY));
2021-11-05 11:06:49 +01:00
}
2017-03-31 01:58:05 +02:00
for (int a = 0; a < 8; a++) {
radii[a] = AndroidUtilities.dp(44);
}
2021-12-09 17:28:33 +01:00
scrollX = isRtl ? AndroidUtilities.dp(10) : AndroidUtilities.dp((type == LETTER_TYPE ? 132 : 240) - 15);
2017-03-31 01:58:05 +02:00
updateColors();
2021-11-05 11:06:49 +01:00
setFocusableInTouchMode(true);
ViewConfiguration vc = ViewConfiguration.get(context);
touchSlop = vc.getScaledTouchSlop();
2021-12-07 14:02:02 +01:00
fastScrollShadowDrawable = ContextCompat.getDrawable(context, R.drawable.fast_scroll_shadow);
2017-03-31 01:58:05 +02:00
}
private void updateColors() {
2021-11-05 11:06:49 +01:00
inactiveColor = type == LETTER_TYPE ? Theme.getColor(Theme.key_fastScrollInactive) : ColorUtils.setAlphaComponent(Color.BLACK, (int) (255 * 0.4f));
activeColor = Theme.getColor(Theme.key_fastScrollActive);
paint.setColor(inactiveColor);
if (type == LETTER_TYPE) {
letterPaint.setColor(Theme.getColor(Theme.key_fastScrollText));
} else {
letterPaint.setColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText));
}
2017-03-31 01:58:05 +02:00
invalidate();
}
2021-11-05 11:06:49 +01:00
float startY;
boolean isMoving;
long startTime;
2021-12-07 14:02:02 +01:00
float visibilityAlpha;
float viewAlpha;
2021-11-05 11:06:49 +01:00
2017-03-31 01:58:05 +02:00
@Override
public boolean onTouchEvent(MotionEvent event) {
2021-11-05 11:06:49 +01:00
if (!isVisible) {
pressed = false;
return false;
}
2017-03-31 01:58:05 +02:00
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
float x = event.getX();
2021-11-05 11:06:49 +01:00
startY = lastY = event.getY();
2019-07-18 15:01:39 +02:00
float currentY = (float) Math.ceil((getMeasuredHeight() - AndroidUtilities.dp(24 + 30)) * progress) + AndroidUtilities.dp(12);
2021-12-09 17:28:33 +01:00
if (isRtl && x > AndroidUtilities.dp(25) || !isRtl && x < AndroidUtilities.dp(107) || lastY < currentY || lastY > currentY + AndroidUtilities.dp(30)) {
2017-03-31 01:58:05 +02:00
return false;
}
2021-12-07 14:02:02 +01:00
if (type == DATE_TYPE && !floatingDateVisible) {
2021-12-09 17:28:33 +01:00
if (isRtl && x > AndroidUtilities.dp(25) || !isRtl && x < (getMeasuredWidth() - AndroidUtilities.dp(25)) || lastY < currentY || lastY > currentY + AndroidUtilities.dp(30)) {
2021-12-07 14:02:02 +01:00
return false;
}
}
2019-07-18 15:01:39 +02:00
startDy = lastY - currentY;
2021-11-05 11:06:49 +01:00
startTime = System.currentTimeMillis();
2017-03-31 01:58:05 +02:00
pressed = true;
2021-11-05 11:06:49 +01:00
isMoving = false;
2017-03-31 01:58:05 +02:00
lastUpdateTime = System.currentTimeMillis();
invalidate();
2021-11-05 11:06:49 +01:00
Adapter adapter = getAdapter();
showFloatingDate();
if (adapter instanceof FastScrollAdapter) {
((FastScrollAdapter) adapter).onStartFastScroll();
}
2017-03-31 01:58:05 +02:00
return true;
case MotionEvent.ACTION_MOVE:
if (!pressed) {
return true;
}
2021-11-05 11:06:49 +01:00
if (Math.abs(event.getY() - startY) > touchSlop) {
isMoving = true;
2017-03-31 01:58:05 +02:00
}
2021-11-05 11:06:49 +01:00
if (isMoving) {
float newY = event.getY();
float minY = AndroidUtilities.dp(12) + startDy;
float maxY = getMeasuredHeight() - AndroidUtilities.dp(12 + 30) + startDy;
if (newY < minY) {
newY = minY;
} else if (newY > maxY) {
newY = maxY;
}
float dy = newY - lastY;
lastY = newY;
progress += dy / (getMeasuredHeight() - AndroidUtilities.dp(24 + 30));
if (progress < 0) {
progress = 0;
} else if (progress > 1) {
progress = 1;
}
getCurrentLetter(true);
invalidate();
2017-03-31 01:58:05 +02:00
}
return true;
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
2021-11-05 11:06:49 +01:00
adapter = getAdapter();
if (pressed && !isMoving && System.currentTimeMillis() - startTime < 150) {
if (adapter instanceof FastScrollAdapter) {
((FastScrollAdapter)adapter).onFastScrollSingleTap();
}
}
isMoving = false;
2017-03-31 01:58:05 +02:00
pressed = false;
lastUpdateTime = System.currentTimeMillis();
invalidate();
2021-11-05 11:06:49 +01:00
if (adapter instanceof FastScrollAdapter) {
((FastScrollAdapter) adapter).onFinishFastScroll(RecyclerListView.this);
}
showFloatingDate();
2017-03-31 01:58:05 +02:00
return true;
}
2021-11-05 11:06:49 +01:00
return pressed;
2017-03-31 01:58:05 +02:00
}
2021-11-05 11:06:49 +01:00
private void getCurrentLetter(boolean updatePosition) {
2017-03-31 01:58:05 +02:00
LayoutManager layoutManager = getLayoutManager();
if (layoutManager instanceof LinearLayoutManager) {
LinearLayoutManager linearLayoutManager = (LinearLayoutManager) layoutManager;
if (linearLayoutManager.getOrientation() == LinearLayoutManager.VERTICAL) {
Adapter adapter = getAdapter();
if (adapter instanceof FastScrollAdapter) {
FastScrollAdapter fastScrollAdapter = (FastScrollAdapter) adapter;
2021-11-05 11:06:49 +01:00
fastScrollAdapter.getPositionForScrollProgress(RecyclerListView.this, progress, positionWithOffset);
if (updatePosition) {
linearLayoutManager.scrollToPositionWithOffset(positionWithOffset[0], -positionWithOffset[1] + sectionOffset);
}
String newLetter = fastScrollAdapter.getLetter(positionWithOffset[0]);
2017-03-31 01:58:05 +02:00
if (newLetter == null) {
if (letterLayout != null) {
oldLetterLayout = letterLayout;
}
letterLayout = null;
} else if (!newLetter.equals(currentLetter)) {
2021-12-30 11:52:40 +01:00
currentLetter = newLetter;
2021-11-05 11:06:49 +01:00
if (type == LETTER_TYPE) {
letterLayout = new StaticLayout(newLetter, letterPaint, 1000, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false);
} else {
2021-12-30 11:52:40 +01:00
outLetterLayout = letterLayout;
2021-11-05 11:06:49 +01:00
int w = ((int) letterPaint.measureText(newLetter)) + 1;
letterLayout = new StaticLayout(newLetter, letterPaint, w, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false);
2021-12-30 11:52:40 +01:00
if (outLetterLayout != null) {
String[] newSplits = newLetter.split(" ");
String[] oldSplits = outLetterLayout.getText().toString().split(" ");
if (newSplits != null && oldSplits != null && newSplits.length == 2 && oldSplits.length == 2 && newSplits[1].equals(oldSplits[1])) {
String oldText = outLetterLayout.getText().toString();
SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder(oldText);
spannableStringBuilder.setSpan(new EmptyStubSpan(), oldSplits[0].length(), oldText.length(), 0);
int oldW = ((int) letterPaint.measureText(oldText)) + 1;
outLetterLayout = new StaticLayout(spannableStringBuilder, letterPaint, oldW, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false);
spannableStringBuilder = new SpannableStringBuilder(newLetter);
spannableStringBuilder.setSpan(new EmptyStubSpan(), newSplits[0].length(), newLetter.length(), 0);
inLetterLayout = new StaticLayout(spannableStringBuilder, letterPaint, w, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false);
spannableStringBuilder = new SpannableStringBuilder(newLetter);
spannableStringBuilder.setSpan(new EmptyStubSpan(), 0, newSplits[0].length(), 0);
stableLetterLayout = new StaticLayout(spannableStringBuilder, letterPaint, w, Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, false);
} else {
inLetterLayout = letterLayout;
stableLetterLayout = null;
}
fromWidth = outLetterLayout.getWidth();
replaceLayoutProgress = 0f;
fromTop = getProgress() > lastLetterY;
}
lastLetterY = getProgress();
2021-11-05 11:06:49 +01:00
}
2017-03-31 01:58:05 +02:00
oldLetterLayout = null;
if (letterLayout.getLineCount() > 0) {
2017-07-08 18:32:04 +02:00
float lWidth = letterLayout.getLineWidth(0);
float lleft = letterLayout.getLineLeft(0);
2021-12-09 17:28:33 +01:00
if (isRtl) {
2017-07-08 18:32:04 +02:00
textX = AndroidUtilities.dp(10) + (AndroidUtilities.dp(88) - letterLayout.getLineWidth(0)) / 2 - letterLayout.getLineLeft(0);
2017-03-31 01:58:05 +02:00
} else {
2017-07-08 18:32:04 +02:00
textX = (AndroidUtilities.dp(88) - letterLayout.getLineWidth(0)) / 2 - letterLayout.getLineLeft(0);
2017-03-31 01:58:05 +02:00
}
textY = (AndroidUtilities.dp(88) - letterLayout.getHeight()) / 2;
}
}
}
}
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
2021-11-05 11:06:49 +01:00
setMeasuredDimension(AndroidUtilities.dp(type == LETTER_TYPE ? 132 : 240), MeasureSpec.getSize(heightMeasureSpec));
2021-12-07 14:02:02 +01:00
arrowPath.reset();
arrowPath.setLastPoint(0, 0);
arrowPath.lineTo(AndroidUtilities.dp(4), -AndroidUtilities.dp(4));
arrowPath.lineTo(-AndroidUtilities.dp(4), -AndroidUtilities.dp(4));
arrowPath.close();
2017-03-31 01:58:05 +02:00
}
@Override
protected void onDraw(Canvas canvas) {
2022-03-11 17:49:54 +01:00
int y = getPaddingTop() + (int) Math.ceil((getMeasuredHeight() - getPaddingTop() - AndroidUtilities.dp(24 + 30)) * progress);
2017-03-31 01:58:05 +02:00
rect.set(scrollX, AndroidUtilities.dp(12) + y, scrollX + AndroidUtilities.dp(5), AndroidUtilities.dp(12 + 30) + y);
2021-12-07 14:02:02 +01:00
if (type == LETTER_TYPE) {
paint.setColor(ColorUtils.blendARGB(inactiveColor, activeColor, bubbleProgress));
canvas.drawRoundRect(rect, AndroidUtilities.dp(2), AndroidUtilities.dp(2), paint);
} else {
paint.setColor(ColorUtils.blendARGB(Theme.getColor(Theme.key_windowBackgroundWhite), Color.WHITE, 0.1f));
float cy = y + AndroidUtilities.dp(12 + 15);
fastScrollShadowDrawable.setBounds(getMeasuredWidth() - fastScrollShadowDrawable.getIntrinsicWidth(), (int) (cy - fastScrollShadowDrawable.getIntrinsicHeight() / 2), getMeasuredWidth(), (int) (cy + fastScrollShadowDrawable.getIntrinsicHeight() / 2));
fastScrollShadowDrawable.draw(canvas);
canvas.drawCircle(scrollX + AndroidUtilities.dp(8), y + AndroidUtilities.dp(12 + 15), AndroidUtilities.dp(24), paint);
paint.setColor(Theme.getColor(Theme.key_windowBackgroundWhiteBlackText));
canvas.save();
canvas.translate(scrollX + AndroidUtilities.dp(4), y + AndroidUtilities.dp(12 + 15 + 2 + 5) + AndroidUtilities.dp(2) * bubbleProgress);
canvas.drawPath(arrowPath, paint);
canvas.restore();
canvas.save();
canvas.translate(scrollX + AndroidUtilities.dp(4), y + AndroidUtilities.dp(12 + 15 + 2 - 5) - AndroidUtilities.dp(2) * bubbleProgress);
canvas.rotate(180, 0, -AndroidUtilities.dp(2));
canvas.drawPath(arrowPath, paint);
canvas.restore();
}
2021-11-05 11:06:49 +01:00
if (type == LETTER_TYPE) {
if ((isMoving || bubbleProgress != 0)) {
paint.setAlpha((int) (255 * bubbleProgress));
int progressY = y + AndroidUtilities.dp(30);
y -= AndroidUtilities.dp(46);
float diff = 0;
if (y <= AndroidUtilities.dp(12)) {
diff = AndroidUtilities.dp(12) - y;
y = AndroidUtilities.dp(12);
}
float raduisTop;
float raduisBottom;
canvas.translate(AndroidUtilities.dp(10), y);
if (diff <= AndroidUtilities.dp(29)) {
raduisTop = AndroidUtilities.dp(44);
raduisBottom = AndroidUtilities.dp(4) + (diff / AndroidUtilities.dp(29)) * AndroidUtilities.dp(40);
2017-03-31 01:58:05 +02:00
} else {
2021-11-05 11:06:49 +01:00
diff -= AndroidUtilities.dp(29);
raduisBottom = AndroidUtilities.dp(44);
raduisTop = AndroidUtilities.dp(4) + (1.0f - diff / AndroidUtilities.dp(29)) * AndroidUtilities.dp(40);
}
2021-12-09 17:28:33 +01:00
if (isRtl && (radii[0] != raduisTop || radii[6] != raduisBottom) || !isRtl && (radii[2] != raduisTop || radii[4] != raduisBottom)) {
if (isRtl) {
2021-11-05 11:06:49 +01:00
radii[0] = radii[1] = raduisTop;
radii[6] = radii[7] = raduisBottom;
} else {
radii[2] = radii[3] = raduisTop;
radii[4] = radii[5] = raduisBottom;
}
path.reset();
2021-12-09 17:28:33 +01:00
rect.set(isRtl ? AndroidUtilities.dp(10) : 0, 0, AndroidUtilities.dp(isRtl ? 98 : 88), AndroidUtilities.dp(88));
2021-11-05 11:06:49 +01:00
path.addRoundRect(rect, radii, Path.Direction.CW);
path.close();
}
StaticLayout layoutToDraw = letterLayout != null ? letterLayout : oldLetterLayout;
if (layoutToDraw != null) {
canvas.save();
canvas.scale(bubbleProgress, bubbleProgress, scrollX, progressY - y);
canvas.drawPath(path, paint);
canvas.translate(textX, textY);
layoutToDraw.draw(canvas);
canvas.restore();
2017-03-31 01:58:05 +02:00
}
}
2021-11-05 11:06:49 +01:00
} else if (type == DATE_TYPE) {
if (letterLayout != null && floatingDateProgress != 0) {
2017-03-31 01:58:05 +02:00
canvas.save();
2021-11-05 11:06:49 +01:00
float s = 0.7f + 0.3f * floatingDateProgress;
canvas.scale(s, s, rect.right - AndroidUtilities.dp(12), rect.centerY());
float cy = rect.centerY();
2021-12-07 14:02:02 +01:00
float x = rect.left - AndroidUtilities.dp(30) * bubbleProgress - AndroidUtilities.dp(8);
2021-11-05 11:06:49 +01:00
float r = letterLayout.getHeight() / 2f + AndroidUtilities.dp(6);
2021-12-30 11:52:40 +01:00
float width = replaceLayoutProgress * letterLayout.getWidth() + fromWidth * (1f - replaceLayoutProgress);
rect.set(x - width - AndroidUtilities.dp(36), cy - letterLayout.getHeight() / 2f - AndroidUtilities.dp(8), x - AndroidUtilities.dp(12), cy + letterLayout.getHeight() / 2f + AndroidUtilities.dp(8));
2021-11-05 11:06:49 +01:00
int oldAlpha1 = paint2.getAlpha();
int oldAlpha2 = letterPaint.getAlpha();
paint2.setAlpha((int) (oldAlpha1 * floatingDateProgress));
2021-12-30 11:52:40 +01:00
2021-11-05 11:06:49 +01:00
fastScrollBackgroundDrawable.setBounds((int) rect.left, (int) rect.top, (int) rect.right, (int) rect.bottom);
2021-12-07 14:02:02 +01:00
fastScrollBackgroundDrawable.setAlpha((int) (255 * floatingDateProgress));
2021-11-05 11:06:49 +01:00
fastScrollBackgroundDrawable.draw(canvas);
2021-12-30 11:52:40 +01:00
if (replaceLayoutProgress != 1f) {
replaceLayoutProgress += 16f / 150f;
if (replaceLayoutProgress > 1f) {
replaceLayoutProgress = 1f;
} else {
invalidate();
}
}
if (replaceLayoutProgress != 1f) {
canvas.save();
rect.inset(AndroidUtilities.dp(4), AndroidUtilities.dp(2));
canvas.clipRect(rect);
if (outLetterLayout != null) {
letterPaint.setAlpha((int) (oldAlpha2 * floatingDateProgress * (1f - replaceLayoutProgress)));
canvas.save();
canvas.translate(x - outLetterLayout.getWidth() - AndroidUtilities.dp(24), cy - outLetterLayout.getHeight() / 2f + (fromTop ? -1 : 1) * AndroidUtilities.dp(15) * replaceLayoutProgress);
outLetterLayout.draw(canvas);
canvas.restore();
}
if (inLetterLayout != null) {
letterPaint.setAlpha((int) (oldAlpha2 * floatingDateProgress * replaceLayoutProgress));
canvas.save();
canvas.translate(x - inLetterLayout.getWidth() - AndroidUtilities.dp(24), cy - inLetterLayout.getHeight() / 2f + (fromTop ? 1 : -1) * AndroidUtilities.dp(15) * (1f - replaceLayoutProgress));
inLetterLayout.draw(canvas);
canvas.restore();
}
if (stableLetterLayout != null) {
letterPaint.setAlpha((int) (oldAlpha2 * floatingDateProgress));
canvas.save();
canvas.translate(x - stableLetterLayout.getWidth() - AndroidUtilities.dp(24), cy - stableLetterLayout.getHeight() / 2f);
stableLetterLayout.draw(canvas);
canvas.restore();
}
canvas.restore();
} else {
letterPaint.setAlpha((int) (oldAlpha2 * floatingDateProgress));
canvas.save();
canvas.translate(x - letterLayout.getWidth() - AndroidUtilities.dp(24), cy - letterLayout.getHeight() / 2f + AndroidUtilities.dp(15) * (1f - replaceLayoutProgress));
letterLayout.draw(canvas);
canvas.restore();
}
2021-11-05 11:06:49 +01:00
paint2.setAlpha(oldAlpha1);
letterPaint.setAlpha(oldAlpha2);
2017-03-31 01:58:05 +02:00
canvas.restore();
}
}
2021-11-05 11:06:49 +01:00
long newTime = System.currentTimeMillis();
long dt = (newTime - lastUpdateTime);
if (dt < 0 || dt > 17) {
dt = 17;
}
if ((isMoving && letterLayout != null && bubbleProgress < 1.0f) || (!isMoving || letterLayout == null) && bubbleProgress > 0.0f) {
2017-03-31 01:58:05 +02:00
lastUpdateTime = newTime;
invalidate();
2021-11-05 11:06:49 +01:00
if (isMoving && letterLayout != null) {
2017-03-31 01:58:05 +02:00
bubbleProgress += dt / 120.0f;
if (bubbleProgress > 1.0f) {
bubbleProgress = 1.0f;
}
} else {
bubbleProgress -= dt / 120.0f;
if (bubbleProgress < 0.0f) {
bubbleProgress = 0.0f;
}
}
}
2021-11-05 11:06:49 +01:00
if (floatingDateVisible && floatingDateProgress != 1f) {
floatingDateProgress += dt / 120.0f;
if (floatingDateProgress > 1.0f) {
floatingDateProgress = 1.0f;
}
invalidate();
} else if (!floatingDateVisible && floatingDateProgress != 0) {
floatingDateProgress -= dt / 120.0f;
if (floatingDateProgress < 0.0f) {
floatingDateProgress = 0.0f;
}
invalidate();
}
2017-03-31 01:58:05 +02:00
}
@Override
public void layout(int l, int t, int r, int b) {
if (!selfOnLayout) {
return;
}
super.layout(l, t, r, b);
}
2021-11-05 11:06:49 +01:00
public void setProgress(float value) {
2017-03-31 01:58:05 +02:00
progress = value;
invalidate();
}
2021-11-05 11:06:49 +01:00
@Override
public boolean isPressed() {
return pressed;
}
public void showFloatingDate() {
if (type != DATE_TYPE) {
return;
}
if (!floatingDateVisible) {
floatingDateVisible = true;
invalidate();
}
AndroidUtilities.cancelRunOnUIThread(hideFloatingDateRunnable);
2021-12-07 14:02:02 +01:00
AndroidUtilities.runOnUIThread(hideFloatingDateRunnable, 2000);
2021-11-05 11:06:49 +01:00
}
public void setIsVisible(boolean visible) {
2021-12-07 14:02:02 +01:00
if (isVisible != visible) {
this.isVisible = visible;
visibilityAlpha = visible ? 1f : 0f;
super.setAlpha(viewAlpha * visibilityAlpha);
}
}
public void setVisibilityAlpha(float v) {
if (visibilityAlpha != v) {
visibilityAlpha = v;
super.setAlpha(viewAlpha * visibilityAlpha);
}
}
@Override
public void setAlpha(float alpha) {
if (viewAlpha != alpha) {
viewAlpha = alpha;
super.setAlpha(viewAlpha * visibilityAlpha);
}
}
@Override
public float getAlpha() {
return viewAlpha;
2021-11-05 11:06:49 +01:00
}
public int getScrollBarY() {
return (int) Math.ceil((getMeasuredHeight() - AndroidUtilities.dp(24 + 30)) * progress) + AndroidUtilities.dp(17);
}
public float getProgress() {
return progress;
}
2021-12-07 14:02:02 +01:00
2017-03-31 01:58:05 +02:00
}
private class RecyclerListViewItemClickListener implements OnItemTouchListener {
2015-05-21 23:27:47 +02:00
public RecyclerListViewItemClickListener(Context context) {
2022-02-01 14:00:45 +01:00
gestureDetector = new GestureDetectorFixDoubleTap(context, new GestureDetectorFixDoubleTap.OnGestureListener() {
2021-12-30 11:52:40 +01:00
private View doubleTapView;
2015-05-21 23:27:47 +02:00
@Override
public boolean onSingleTapUp(MotionEvent e) {
2021-12-30 11:52:40 +01:00
if (currentChildView != null) {
if (onItemClickListenerExtended != null && onItemClickListenerExtended.hasDoubleTap(currentChildView, currentChildPosition)) {
doubleTapView = currentChildView;
} else {
onPressItem(currentChildView, e);
2022-02-01 14:00:45 +01:00
return false;
2021-12-30 11:52:40 +01:00
}
}
return false;
}
@Override
public boolean onSingleTapConfirmed(MotionEvent e) {
if (doubleTapView != null && onItemClickListenerExtended != null) {
if (onItemClickListenerExtended.hasDoubleTap(doubleTapView, currentChildPosition)) {
onPressItem(doubleTapView, e);
doubleTapView = null;
return true;
}
}
return false;
}
@Override
public boolean onDoubleTap(MotionEvent e) {
if (doubleTapView != null && onItemClickListenerExtended != null && onItemClickListenerExtended.hasDoubleTap(doubleTapView, currentChildPosition)) {
onItemClickListenerExtended.onDoubleTap(doubleTapView, currentChildPosition, e.getX(), e.getY());
doubleTapView = null;
return true;
}
return false;
}
private void onPressItem(View cv, MotionEvent e) {
if (cv != null && (onItemClickListener != null || onItemClickListenerExtended != null)) {
2017-12-08 18:35:59 +01:00
final float x = e.getX();
final float y = e.getY();
2021-12-30 11:52:40 +01:00
onChildPressed(cv, x, y, true);
final View view = cv;
2019-12-31 14:08:08 +01:00
final int position = currentChildPosition;
2017-03-31 01:58:05 +02:00
if (instantClick && position != -1) {
2015-05-21 23:27:47 +02:00
view.playSoundEffect(SoundEffectConstants.CLICK);
2019-05-14 14:08:05 +02:00
view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
2017-12-08 18:35:59 +01:00
if (onItemClickListener != null) {
onItemClickListener.onItemClick(view, position);
} else if (onItemClickListenerExtended != null) {
2019-05-14 14:08:05 +02:00
onItemClickListenerExtended.onItemClick(view, position, x - view.getX(), y - view.getY());
2017-12-08 18:35:59 +01:00
}
2015-05-21 23:27:47 +02:00
}
2015-12-09 19:27:52 +01:00
AndroidUtilities.runOnUIThread(clickRunnable = new Runnable() {
2015-05-21 23:27:47 +02:00
@Override
public void run() {
2015-12-09 19:27:52 +01:00
if (this == clickRunnable) {
clickRunnable = null;
}
2015-05-21 23:27:47 +02:00
if (view != null) {
2019-12-31 14:08:08 +01:00
onChildPressed(view, 0, 0, false);
2015-05-21 23:27:47 +02:00
if (!instantClick) {
view.playSoundEffect(SoundEffectConstants.CLICK);
2019-05-14 14:08:05 +02:00
view.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
2017-12-08 18:35:59 +01:00
if (position != -1) {
if (onItemClickListener != null) {
onItemClickListener.onItemClick(view, position);
} else if (onItemClickListenerExtended != null) {
2019-05-14 14:08:05 +02:00
onItemClickListenerExtended.onItemClick(view, position, x - view.getX(), y - view.getY());
2017-12-08 18:35:59 +01:00
}
2015-05-21 23:27:47 +02:00
}
}
}
}
}, ViewConfiguration.getPressedStateDuration());
if (selectChildRunnable != null) {
AndroidUtilities.cancelRunOnUIThread(selectChildRunnable);
selectChildRunnable = null;
currentChildView = null;
interceptedByChild = false;
2021-12-30 11:52:40 +01:00
removeSelection(cv, e);
2015-05-21 23:27:47 +02:00
}
}
}
@Override
2015-12-09 19:27:52 +01:00
public void onLongPress(MotionEvent event) {
2018-07-30 04:07:02 +02:00
if (currentChildView == null || currentChildPosition == -1 || onItemLongClickListener == null && onItemLongClickListenerExtended == null) {
return;
}
View child = currentChildView;
if (onItemLongClickListener != null) {
if (onItemLongClickListener.onItemClick(currentChildView, currentChildPosition)) {
child.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
2019-05-14 14:08:05 +02:00
child.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
2018-07-30 04:07:02 +02:00
}
2021-06-25 02:43:10 +02:00
} else {
2019-05-14 14:08:05 +02:00
if (onItemLongClickListenerExtended.onItemClick(currentChildView, currentChildPosition, event.getX() - currentChildView.getX(), event.getY() - currentChildView.getY())) {
2018-07-30 04:07:02 +02:00
child.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
2019-05-14 14:08:05 +02:00
child.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
2018-07-30 04:07:02 +02:00
longPressCalled = true;
2015-10-29 18:10:07 +01:00
}
2015-05-21 23:27:47 +02:00
}
}
2019-05-14 14:08:05 +02:00
@Override
public boolean onDown(MotionEvent e) {
return false;
}
2022-02-01 14:00:45 +01:00
@Override
public boolean hasDoubleTap() {
return onItemLongClickListenerExtended != null;
}
2015-05-21 23:27:47 +02:00
});
2019-03-03 21:40:48 +01:00
gestureDetector.setIsLongpressEnabled(false);
2015-05-21 23:27:47 +02:00
}
@Override
2015-12-09 19:27:52 +01:00
public boolean onInterceptTouchEvent(RecyclerView view, MotionEvent event) {
int action = event.getActionMasked();
2015-05-21 23:27:47 +02:00
boolean isScrollIdle = RecyclerListView.this.getScrollState() == RecyclerListView.SCROLL_STATE_IDLE;
if ((action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_POINTER_DOWN) && currentChildView == null && isScrollIdle) {
2017-12-08 18:35:59 +01:00
float ex = event.getX();
float ey = event.getY();
2018-07-30 04:07:02 +02:00
longPressCalled = false;
2019-12-31 14:08:08 +01:00
ItemAnimator animator = getItemAnimator();
if ((allowItemsInteractionDuringAnimation || animator == null || !animator.isRunning()) && allowSelectChildAtPosition(ex, ey)) {
View v = findChildViewUnder(ex, ey);
if (v != null && allowSelectChildAtPosition(v)) {
currentChildView = v;
}
2017-12-08 18:35:59 +01:00
}
if (currentChildView instanceof ViewGroup) {
float x = event.getX() - currentChildView.getLeft();
float y = event.getY() - currentChildView.getTop();
2017-03-31 01:58:05 +02:00
ViewGroup viewGroup = (ViewGroup) currentChildView;
final int count = viewGroup.getChildCount();
for (int i = count - 1; i >= 0; i--) {
final View child = viewGroup.getChildAt(i);
if (x >= child.getLeft() && x <= child.getRight() && y >= child.getTop() && y <= child.getBottom()) {
if (child.isClickable()) {
currentChildView = null;
break;
}
}
}
}
2015-05-21 23:27:47 +02:00
currentChildPosition = -1;
if (currentChildView != null) {
currentChildPosition = view.getChildPosition(currentChildView);
2015-12-09 19:27:52 +01:00
MotionEvent childEvent = MotionEvent.obtain(0, 0, event.getActionMasked(), event.getX() - currentChildView.getLeft(), event.getY() - currentChildView.getTop(), 0);
2015-05-21 23:27:47 +02:00
if (currentChildView.onTouchEvent(childEvent)) {
interceptedByChild = true;
}
childEvent.recycle();
}
}
if (currentChildView != null && !interceptedByChild) {
try {
2021-06-25 02:43:10 +02:00
gestureDetector.onTouchEvent(event);
2015-12-09 19:27:52 +01:00
} catch (Exception e) {
2017-03-31 01:58:05 +02:00
FileLog.e(e);
2015-05-21 23:27:47 +02:00
}
}
if (action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_POINTER_DOWN) {
if (!interceptedByChild && currentChildView != null) {
2019-12-31 14:08:08 +01:00
float x = event.getX();
float y = event.getY();
2019-01-23 18:03:33 +01:00
selectChildRunnable = () -> {
if (selectChildRunnable != null && currentChildView != null) {
2019-12-31 14:08:08 +01:00
onChildPressed(currentChildView, x, y, true);
2019-01-23 18:03:33 +01:00
selectChildRunnable = null;
2015-05-21 23:27:47 +02:00
}
};
AndroidUtilities.runOnUIThread(selectChildRunnable, ViewConfiguration.getTapTimeout());
2020-03-30 14:00:09 +02:00
if (currentChildView.isEnabled() && canHighlightChildAt(currentChildView, x - currentChildView.getX(), y - currentChildView.getY())) {
2017-03-31 01:58:05 +02:00
positionSelector(currentChildPosition, currentChildView);
if (selectorDrawable != null) {
final Drawable d = selectorDrawable.getCurrent();
2019-05-14 14:08:05 +02:00
if (d instanceof TransitionDrawable) {
2018-07-30 04:07:02 +02:00
if (onItemLongClickListener != null || onItemClickListenerExtended != null) {
2017-03-31 01:58:05 +02:00
((TransitionDrawable) d).startTransition(ViewConfiguration.getLongPressTimeout());
} else {
((TransitionDrawable) d).resetTransition();
}
}
if (Build.VERSION.SDK_INT >= 21) {
selectorDrawable.setHotspot(event.getX(), event.getY());
}
}
updateSelectorState();
} else {
selectorRect.setEmpty();
}
2019-12-31 14:08:08 +01:00
} else {
selectorRect.setEmpty();
2015-05-21 23:27:47 +02:00
}
2017-03-31 01:58:05 +02:00
} else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_POINTER_UP || action == MotionEvent.ACTION_CANCEL || !isScrollIdle) {
if (currentChildView != null) {
if (selectChildRunnable != null) {
AndroidUtilities.cancelRunOnUIThread(selectChildRunnable);
selectChildRunnable = null;
}
View pressedChild = currentChildView;
2019-12-31 14:08:08 +01:00
onChildPressed(currentChildView, 0, 0, false);
2017-03-31 01:58:05 +02:00
currentChildView = null;
interceptedByChild = false;
removeSelection(pressedChild, event);
2018-07-30 04:07:02 +02:00
if ((action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_POINTER_UP || action == MotionEvent.ACTION_CANCEL) && onItemLongClickListenerExtended != null && longPressCalled) {
onItemLongClickListenerExtended.onLongClickRelease();
longPressCalled = false;
}
2015-05-21 23:27:47 +02:00
}
}
return false;
}
@Override
2015-12-09 19:27:52 +01:00
public void onTouchEvent(RecyclerView view, MotionEvent event) {
2015-05-21 23:27:47 +02:00
}
2015-07-22 20:56:37 +02:00
@Override
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
2015-12-09 19:27:52 +01:00
cancelClickRunnables(true);
}
}
2019-05-14 14:08:05 +02:00
@Override
public View findChildViewUnder(float x, float y) {
final int count = getChildCount();
for (int a = 0; a < 2; a++) {
for (int i = count - 1; i >= 0; i--) {
final View child = getChildAt(i);
final float translationX = a == 0 ? child.getTranslationX() : 0;
final float translationY = a == 0 ? child.getTranslationY() : 0;
if (x >= child.getLeft() + translationX
&& x <= child.getRight() + translationX
&& y >= child.getTop() + translationY
&& y <= child.getBottom() + translationY) {
return child;
}
}
}
return null;
}
2020-03-30 14:00:09 +02:00
protected boolean canHighlightChildAt(View child, float x, float y) {
return true;
}
2019-05-14 14:08:05 +02:00
public void setDisableHighlightState(boolean value) {
disableHighlightState = value;
}
2022-09-16 20:48:21 +02:00
public View getPressedChildView() {
2018-07-30 04:07:02 +02:00
return currentChildView;
}
2019-12-31 14:08:08 +01:00
protected void onChildPressed(View child, float x, float y, boolean pressed) {
2021-04-14 03:44:46 +02:00
if (disableHighlightState || child == null) {
2019-05-14 14:08:05 +02:00
return;
}
2017-12-08 18:35:59 +01:00
child.setPressed(pressed);
}
protected boolean allowSelectChildAtPosition(float x, float y) {
return true;
}
2019-12-31 14:08:08 +01:00
protected boolean allowSelectChildAtPosition(View child) {
return true;
}
2017-03-31 01:58:05 +02:00
private void removeSelection(View pressedChild, MotionEvent event) {
2019-12-31 14:08:08 +01:00
if (pressedChild == null || selectorRect.isEmpty()) {
2017-03-31 01:58:05 +02:00
return;
}
2019-12-31 14:08:08 +01:00
if (pressedChild.isEnabled()) {
2017-03-31 01:58:05 +02:00
positionSelector(currentChildPosition, pressedChild);
if (selectorDrawable != null) {
Drawable d = selectorDrawable.getCurrent();
2019-05-14 14:08:05 +02:00
if (d instanceof TransitionDrawable) {
2017-03-31 01:58:05 +02:00
((TransitionDrawable) d).resetTransition();
}
if (event != null && Build.VERSION.SDK_INT >= 21) {
selectorDrawable.setHotspot(event.getX(), event.getY());
}
}
} else {
selectorRect.setEmpty();
}
updateSelectorState();
}
2015-12-09 19:27:52 +01:00
public void cancelClickRunnables(boolean uncheck) {
if (selectChildRunnable != null) {
AndroidUtilities.cancelRunOnUIThread(selectChildRunnable);
selectChildRunnable = null;
}
if (currentChildView != null) {
2017-03-31 01:58:05 +02:00
View child = currentChildView;
2015-12-09 19:27:52 +01:00
if (uncheck) {
2019-12-31 14:08:08 +01:00
onChildPressed(currentChildView, 0, 0, false);
2015-10-29 18:10:07 +01:00
}
2015-12-09 19:27:52 +01:00
currentChildView = null;
2017-03-31 01:58:05 +02:00
removeSelection(child, null);
2015-12-09 19:27:52 +01:00
}
2020-09-30 15:48:47 +02:00
selectorRect.setEmpty();
2015-12-09 19:27:52 +01:00
if (clickRunnable != null) {
AndroidUtilities.cancelRunOnUIThread(clickRunnable);
clickRunnable = null;
2015-07-22 20:56:37 +02:00
}
2015-12-09 19:27:52 +01:00
interceptedByChild = false;
2015-05-21 23:27:47 +02:00
}
2022-04-16 16:43:17 +02:00
private boolean resetSelectorOnChanged = true;
public void setResetSelectorOnChanged(boolean value) {
resetSelectorOnChanged = value;
}
2015-05-21 23:27:47 +02:00
private AdapterDataObserver observer = new AdapterDataObserver() {
@Override
public void onChanged() {
2020-07-26 10:03:38 +02:00
checkIfEmpty(true);
2022-04-16 16:43:17 +02:00
if (resetSelectorOnChanged) {
currentFirst = -1;
if (removeHighlighSelectionRunnable == null) {
selectorRect.setEmpty();
}
2019-05-14 14:08:05 +02:00
}
2017-03-31 01:58:05 +02:00
invalidate();
2015-05-21 23:27:47 +02:00
}
@Override
public void onItemRangeInserted(int positionStart, int itemCount) {
2020-07-26 10:03:38 +02:00
checkIfEmpty(true);
2020-12-26 06:18:43 +01:00
if (pinnedHeader != null && pinnedHeader.getAlpha() == 0) {
currentFirst = -1;
invalidateViews();
}
2015-05-21 23:27:47 +02:00
}
@Override
public void onItemRangeRemoved(int positionStart, int itemCount) {
2020-07-26 10:03:38 +02:00
checkIfEmpty(true);
2015-05-21 23:27:47 +02:00
}
};
public int[] getResourceDeclareStyleableIntArray(String packageName, String name) {
try {
Field f = Class.forName(packageName + ".R$styleable").getField(name);
if (f != null) {
return (int[]) f.get(null);
}
} catch (Throwable t) {
//ignore
}
return null;
}
public RecyclerListView(Context context) {
2021-09-20 07:54:41 +02:00
this(context, null);
}
@SuppressLint("PrivateApi")
public RecyclerListView(Context context, Theme.ResourcesProvider resourcesProvider) {
2015-05-21 23:27:47 +02:00
super(context);
2021-09-20 07:54:41 +02:00
this.resourcesProvider = resourcesProvider;
2015-05-21 23:27:47 +02:00
2021-09-20 07:54:41 +02:00
setGlowColor(getThemedColor(Theme.key_actionBarDefault));
selectorDrawable = Theme.getSelectorDrawable(getThemedColor(Theme.key_listSelector), false);
2017-03-31 01:58:05 +02:00
selectorDrawable.setCallback(this);
2015-05-21 23:27:47 +02:00
try {
if (!gotAttributes) {
attributes = getResourceDeclareStyleableIntArray("com.android.internal", "View");
2021-11-05 11:06:49 +01:00
if (attributes == null) {
attributes = new int[0];
}
2015-05-21 23:27:47 +02:00
gotAttributes = true;
}
TypedArray a = context.getTheme().obtainStyledAttributes(attributes);
2022-11-20 22:12:39 +01:00
if (initializeScrollbars != null) {
initializeScrollbars.invoke(this, a);
}
2015-05-21 23:27:47 +02:00
} catch (Throwable e) {
2017-03-31 01:58:05 +02:00
FileLog.e(e);
2015-05-21 23:27:47 +02:00
}
super.setOnScrollListener(new OnScrollListener() {
2017-03-31 01:58:05 +02:00
2015-05-21 23:27:47 +02:00
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
if (newState != SCROLL_STATE_IDLE && currentChildView != null) {
if (selectChildRunnable != null) {
AndroidUtilities.cancelRunOnUIThread(selectChildRunnable);
selectChildRunnable = null;
}
MotionEvent event = MotionEvent.obtain(0, 0, MotionEvent.ACTION_CANCEL, 0, 0, 0);
try {
2017-03-31 01:58:05 +02:00
gestureDetector.onTouchEvent(event);
2015-05-21 23:27:47 +02:00
} catch (Exception e) {
2017-03-31 01:58:05 +02:00
FileLog.e(e);
2015-05-21 23:27:47 +02:00
}
currentChildView.onTouchEvent(event);
event.recycle();
2017-03-31 01:58:05 +02:00
View child = currentChildView;
2019-12-31 14:08:08 +01:00
onChildPressed(currentChildView, 0, 0, false);
2015-05-21 23:27:47 +02:00
currentChildView = null;
2017-03-31 01:58:05 +02:00
removeSelection(child, null);
2015-05-21 23:27:47 +02:00
interceptedByChild = false;
}
if (onScrollListener != null) {
onScrollListener.onScrollStateChanged(recyclerView, newState);
}
2017-03-31 01:58:05 +02:00
scrollingByUser = newState == SCROLL_STATE_DRAGGING || newState == SCROLL_STATE_SETTLING;
2022-09-16 20:48:21 +02:00
if (scrollingByUser) {
scrolledByUserOnce = true;
}
2015-05-21 23:27:47 +02:00
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
if (onScrollListener != null) {
onScrollListener.onScrolled(recyclerView, dx, dy);
}
2017-03-31 01:58:05 +02:00
if (selectorPosition != NO_POSITION) {
2017-07-08 18:32:04 +02:00
selectorRect.offset(-dx, -dy);
2017-03-31 01:58:05 +02:00
selectorDrawable.setBounds(selectorRect);
invalidate();
} else {
selectorRect.setEmpty();
}
2021-11-05 11:06:49 +01:00
checkSection(false);
if (dy != 0 && fastScroll != null) {
fastScroll.showFloatingDate();
}
2015-05-21 23:27:47 +02:00
}
});
addOnItemTouchListener(new RecyclerListViewItemClickListener(context));
}
@Override
public void setVerticalScrollBarEnabled(boolean verticalScrollBarEnabled) {
if (attributes != null) {
super.setVerticalScrollBarEnabled(verticalScrollBarEnabled);
}
}
2017-03-31 01:58:05 +02:00
@Override
protected void onMeasure(int widthSpec, int heightSpec) {
super.onMeasure(widthSpec, heightSpec);
if (fastScroll != null) {
2019-07-18 15:01:39 +02:00
int height = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();
fastScroll.getLayoutParams().height = height;
fastScroll.measure(MeasureSpec.makeMeasureSpec(AndroidUtilities.dp(132), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
2017-03-31 01:58:05 +02:00
}
2021-07-15 16:24:57 +02:00
touchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
2017-03-31 01:58:05 +02:00
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
if (fastScroll != null) {
selfOnLayout = true;
2019-07-18 15:01:39 +02:00
t += getPaddingTop();
2021-12-09 17:28:33 +01:00
if (fastScroll.isRtl) {
2017-03-31 01:58:05 +02:00
fastScroll.layout(0, t, fastScroll.getMeasuredWidth(), t + fastScroll.getMeasuredHeight());
} else {
int x = getMeasuredWidth() - fastScroll.getMeasuredWidth();
fastScroll.layout(x, t, x + fastScroll.getMeasuredWidth(), t + fastScroll.getMeasuredHeight());
}
selfOnLayout = false;
}
2021-11-05 11:06:49 +01:00
checkSection(false);
2019-05-14 14:08:05 +02:00
if (pendingHighlightPosition != null) {
highlightRowInternal(pendingHighlightPosition, false);
}
2019-01-23 18:03:33 +01:00
}
2020-03-30 14:00:09 +02:00
public void setSelectorType(int type) {
selectorType = type;
}
2020-09-30 15:48:47 +02:00
public void setSelectorRadius(int radius) {
selectorRadius = radius;
}
2020-12-23 08:48:30 +01:00
public void setTopBottomSelectorRadius(int radius) {
topBottomSelectorRadius = radius;
}
2020-03-30 14:00:09 +02:00
public void setDrawSelectorBehind(boolean value) {
drawSelectorBehind = value;
}
2019-06-04 12:14:50 +02:00
public void setSelectorDrawableColor(int color) {
if (selectorDrawable != null) {
selectorDrawable.setCallback(null);
}
2022-04-16 16:43:17 +02:00
if (selectorType == 8) {
selectorDrawable = Theme.createRadSelectorDrawable(color, selectorRadius, 0);
} else if (topBottomSelectorRadius > 0) {
2020-12-23 08:48:30 +01:00
selectorDrawable = Theme.createRadSelectorDrawable(color, topBottomSelectorRadius, topBottomSelectorRadius);
} else if (selectorRadius > 0) {
2020-09-30 15:48:47 +02:00
selectorDrawable = Theme.createSimpleSelectorRoundRectDrawable(selectorRadius, 0, color, 0xff000000);
} else if (selectorType == 2) {
2020-03-30 14:00:09 +02:00
selectorDrawable = Theme.getSelectorDrawable(color, false);
} else {
selectorDrawable = Theme.createSelectorDrawable(color, selectorType);
}
2019-06-04 12:14:50 +02:00
selectorDrawable.setCallback(this);
}
2022-06-21 04:51:00 +02:00
public Drawable getSelectorDrawable() {
return selectorDrawable;
}
2021-11-05 11:06:49 +01:00
public void checkSection(boolean force) {
2022-03-11 17:49:54 +01:00
if ((scrollingByUser || force) && fastScroll != null || sectionsType != SECTIONS_TYPE_SIMPLE && sectionsAdapter != null) {
2019-01-23 18:03:33 +01:00
LayoutManager layoutManager = getLayoutManager();
if (layoutManager instanceof LinearLayoutManager) {
LinearLayoutManager linearLayoutManager = (LinearLayoutManager) layoutManager;
if (linearLayoutManager.getOrientation() == LinearLayoutManager.VERTICAL) {
if (sectionsAdapter != null) {
2019-07-18 15:01:39 +02:00
int paddingTop = getPaddingTop();
2022-03-11 17:49:54 +01:00
if (sectionsType == SECTIONS_TYPE_STICKY_HEADERS || sectionsType == SECTIONS_TYPE_FAST_SCROLL_ONLY) {
2019-07-18 15:01:39 +02:00
int childCount = getChildCount();
int maxBottom = 0;
int minBottom = Integer.MAX_VALUE;
View minChild = null;
int minBottomSection = Integer.MAX_VALUE;
for (int a = 0; a < childCount; a++) {
View child = getChildAt(a);
int bottom = child.getBottom();
if (bottom <= sectionOffset + paddingTop) {
continue;
}
if (bottom < minBottom) {
minBottom = bottom;
minChild = child;
}
maxBottom = Math.max(maxBottom, bottom);
if (bottom < sectionOffset + paddingTop + AndroidUtilities.dp(32)) {
continue;
}
if (bottom < minBottomSection) {
minBottomSection = bottom;
}
}
if (minChild == null) {
return;
}
ViewHolder holder = getChildViewHolder(minChild);
if (holder == null) {
2019-01-23 18:03:33 +01:00
return;
}
2019-07-18 15:01:39 +02:00
int firstVisibleItem = holder.getAdapterPosition();
int lastVisibleItem = linearLayoutManager.findLastVisibleItemPosition();
int visibleItemCount = Math.abs(lastVisibleItem - firstVisibleItem) + 1;
2021-11-05 11:06:49 +01:00
if ((scrollingByUser || force) && fastScroll != null && !fastScroll.isPressed()) {
2019-01-23 18:03:33 +01:00
Adapter adapter = getAdapter();
if (adapter instanceof FastScrollAdapter) {
2021-11-05 11:06:49 +01:00
fastScroll.setProgress(Math.min(1.0f, firstVisibleItem / (float) (sectionsAdapter.getTotalItemsCount() - visibleItemCount + 1)));
2019-01-23 18:03:33 +01:00
}
}
headersCache.addAll(headers);
headers.clear();
if (sectionsAdapter.getItemCount() == 0) {
return;
}
if (currentFirst != firstVisibleItem || currentVisible != visibleItemCount) {
currentFirst = firstVisibleItem;
currentVisible = visibleItemCount;
sectionsCount = 1;
startSection = sectionsAdapter.getSectionForPosition(firstVisibleItem);
int itemNum = firstVisibleItem + sectionsAdapter.getCountForSection(startSection) - sectionsAdapter.getPositionInSectionForPosition(firstVisibleItem);
2019-05-14 14:08:05 +02:00
while (itemNum < firstVisibleItem + visibleItemCount) {
2019-01-23 18:03:33 +01:00
itemNum += sectionsAdapter.getCountForSection(startSection + sectionsCount);
sectionsCount++;
}
}
2022-03-11 17:49:54 +01:00
if (sectionsType != SECTIONS_TYPE_FAST_SCROLL_ONLY) {
int itemNum = firstVisibleItem;
for (int a = startSection; a < startSection + sectionsCount; a++) {
View header = null;
if (!headersCache.isEmpty()) {
header = headersCache.get(0);
headersCache.remove(0);
}
header = getSectionHeaderView(a, header);
headers.add(header);
int count = sectionsAdapter.getCountForSection(a);
if (a == startSection) {
int pos = sectionsAdapter.getPositionInSectionForPosition(itemNum);
if (pos == count - 1) {
header.setTag(-header.getHeight() + paddingTop);
} else if (pos == count - 2) {
View child = getChildAt(itemNum - firstVisibleItem);
int headerTop;
if (child != null) {
headerTop = child.getTop() + paddingTop;
} else {
headerTop = -AndroidUtilities.dp(100);
}
header.setTag(Math.min(headerTop, 0));
} else {
header.setTag(0);
}
itemNum += count - sectionsAdapter.getPositionInSectionForPosition(firstVisibleItem);
} else {
2019-01-23 18:03:33 +01:00
View child = getChildAt(itemNum - firstVisibleItem);
if (child != null) {
2022-03-11 17:49:54 +01:00
header.setTag(child.getTop() + paddingTop);
2019-01-23 18:03:33 +01:00
} else {
2022-03-11 17:49:54 +01:00
header.setTag(-AndroidUtilities.dp(100));
2019-01-23 18:03:33 +01:00
}
2022-03-11 17:49:54 +01:00
itemNum += count;
2019-01-23 18:03:33 +01:00
}
}
}
2022-03-11 17:49:54 +01:00
} else if (sectionsType == SECTIONS_TYPE_DATE) {
2019-01-23 18:03:33 +01:00
pinnedHeaderShadowTargetAlpha = 0.0f;
if (sectionsAdapter.getItemCount() == 0) {
return;
}
int childCount = getChildCount();
int maxBottom = 0;
int minBottom = Integer.MAX_VALUE;
View minChild = null;
int minBottomSection = Integer.MAX_VALUE;
View minChildSection = null;
for (int a = 0; a < childCount; a++) {
View child = getChildAt(a);
int bottom = child.getBottom();
2019-07-18 15:01:39 +02:00
if (bottom <= sectionOffset + paddingTop) {
2019-01-23 18:03:33 +01:00
continue;
}
if (bottom < minBottom) {
minBottom = bottom;
minChild = child;
}
maxBottom = Math.max(maxBottom, bottom);
2019-07-18 15:01:39 +02:00
if (bottom < sectionOffset + paddingTop + AndroidUtilities.dp(32)) {
2019-01-23 18:03:33 +01:00
continue;
}
if (bottom < minBottomSection) {
minBottomSection = bottom;
minChildSection = child;
}
}
if (minChild == null) {
return;
}
ViewHolder holder = getChildViewHolder(minChild);
if (holder == null) {
return;
}
int firstVisibleItem = holder.getAdapterPosition();
int startSection = sectionsAdapter.getSectionForPosition(firstVisibleItem);
if (startSection < 0) {
return;
}
if (currentFirst != startSection || pinnedHeader == null) {
pinnedHeader = getSectionHeaderView(startSection, pinnedHeader);
2019-12-31 14:08:08 +01:00
pinnedHeader.measure(MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.UNSPECIFIED));
pinnedHeader.layout(0, 0, pinnedHeader.getMeasuredWidth(), pinnedHeader.getMeasuredHeight());
2019-01-23 18:03:33 +01:00
currentFirst = startSection;
}
if (pinnedHeader != null && minChildSection != null && minChildSection.getClass() != pinnedHeader.getClass()) {
pinnedHeaderShadowTargetAlpha = 1.0f;
}
int count = sectionsAdapter.getCountForSection(startSection);
int pos = sectionsAdapter.getPositionInSectionForPosition(firstVisibleItem);
int sectionOffsetY = maxBottom != 0 && maxBottom < (getMeasuredHeight() - getPaddingBottom()) ? 0 : sectionOffset;
if (pos == count - 1) {
int headerHeight = pinnedHeader.getHeight();
int headerTop = paddingTop;
if (minChild != null) {
int available = minChild.getTop() - paddingTop - sectionOffset + minChild.getHeight();
if (available < headerHeight) {
headerTop = available - headerHeight;
}
} else {
headerTop = -AndroidUtilities.dp(100);
}
if (headerTop < 0) {
pinnedHeader.setTag(paddingTop + sectionOffsetY + headerTop);
} else {
pinnedHeader.setTag(paddingTop + sectionOffsetY);
}
} else {
pinnedHeader.setTag(paddingTop + sectionOffsetY);
}
invalidate();
}
} else {
int firstVisibleItem = linearLayoutManager.findFirstVisibleItemPosition();
int lastVisibleItem = linearLayoutManager.findLastVisibleItemPosition();
int visibleItemCount = Math.abs(lastVisibleItem - firstVisibleItem) + 1;
2021-11-05 11:06:49 +01:00
2019-01-23 18:03:33 +01:00
if (firstVisibleItem == NO_POSITION) {
return;
}
2021-11-05 11:06:49 +01:00
if ((scrollingByUser || force) && fastScroll != null && !fastScroll.isPressed()) {
2019-01-23 18:03:33 +01:00
Adapter adapter = getAdapter();
2021-11-05 11:06:49 +01:00
2019-01-23 18:03:33 +01:00
if (adapter instanceof FastScrollAdapter) {
2021-11-05 11:06:49 +01:00
float p = ((FastScrollAdapter) adapter).getScrollProgress(RecyclerListView.this);
boolean visible = ((FastScrollAdapter) adapter).fastScrollIsVisible(RecyclerListView.this);
fastScroll.setIsVisible(visible);
fastScroll.setProgress(Math.min(1.0f, p));
fastScroll.getCurrentLetter(false);
2019-01-23 18:03:33 +01:00
}
}
}
}
}
}
2017-03-31 01:58:05 +02:00
}
public void setListSelectorColor(int color) {
Theme.setSelectorDrawableColor(selectorDrawable, color, true);
}
2015-05-21 23:27:47 +02:00
public void setOnItemClickListener(OnItemClickListener listener) {
onItemClickListener = listener;
}
2017-12-08 18:35:59 +01:00
public void setOnItemClickListener(OnItemClickListenerExtended listener) {
onItemClickListenerExtended = listener;
}
2019-05-14 14:08:05 +02:00
public OnItemClickListener getOnItemClickListener() {
return onItemClickListener;
}
2022-11-05 13:34:47 +01:00
public void clickItem(View item, int position) {
if (onItemClickListener != null) {
onItemClickListener.onItemClick(item, position);
} else if (onItemClickListenerExtended != null) {
onItemClickListenerExtended.onItemClick(item, position, 0, 0);
}
}
public boolean longClickItem(View item, int position) {
if (onItemLongClickListener != null) {
return onItemLongClickListener.onItemClick(item, position);
} else if (onItemLongClickListenerExtended != null) {
return onItemLongClickListenerExtended.onItemClick(item, position, 0, 0);
}
return false;
}
2015-05-21 23:27:47 +02:00
public void setOnItemLongClickListener(OnItemLongClickListener listener) {
2022-09-16 20:48:21 +02:00
setOnItemLongClickListener(listener, ViewConfiguration.getLongPressTimeout());
}
public void setOnItemLongClickListener(OnItemLongClickListener listener, long duration) {
2015-05-21 23:27:47 +02:00
onItemLongClickListener = listener;
2019-03-03 21:40:48 +01:00
gestureDetector.setIsLongpressEnabled(listener != null);
2022-09-16 20:48:21 +02:00
gestureDetector.setLongpressDuration(duration);
2015-05-21 23:27:47 +02:00
}
2018-07-30 04:07:02 +02:00
public void setOnItemLongClickListener(OnItemLongClickListenerExtended listener) {
2022-09-16 20:48:21 +02:00
setOnItemLongClickListener(listener, ViewConfiguration.getLongPressTimeout());
}
public void setOnItemLongClickListener(OnItemLongClickListenerExtended listener, long duration) {
2018-07-30 04:07:02 +02:00
onItemLongClickListenerExtended = listener;
2019-03-03 21:40:48 +01:00
gestureDetector.setIsLongpressEnabled(listener != null);
2022-09-16 20:48:21 +02:00
gestureDetector.setLongpressDuration(duration);
2018-07-30 04:07:02 +02:00
}
2015-05-21 23:27:47 +02:00
public void setEmptyView(View view) {
if (emptyView == view) {
return;
}
2020-07-26 10:03:38 +02:00
if (emptyView != null) {
emptyView.animate().setListener(null).cancel();
}
2015-05-21 23:27:47 +02:00
emptyView = view;
2020-09-30 15:48:47 +02:00
if (animateEmptyView && emptyView != null) {
emptyView.setVisibility(View.GONE);
}
2019-12-31 14:08:08 +01:00
if (isHidden) {
if (emptyView != null) {
2020-07-26 10:03:38 +02:00
emptyViewAnimateToVisibility = GONE;
2019-12-31 14:08:08 +01:00
emptyView.setVisibility(GONE);
}
} else {
2020-07-26 10:03:38 +02:00
emptyViewAnimateToVisibility = -1;
2021-01-30 07:18:23 +01:00
checkIfEmpty(updateEmptyViewAnimated());
2019-12-31 14:08:08 +01:00
}
2015-05-21 23:27:47 +02:00
}
2021-01-30 07:18:23 +01:00
protected boolean updateEmptyViewAnimated() {
return isAttachedToWindow();
}
2015-05-21 23:27:47 +02:00
public View getEmptyView() {
return emptyView;
}
public void invalidateViews() {
int count = getChildCount();
for (int a = 0; a < count; a++) {
getChildAt(a).invalidate();
}
}
2017-03-31 01:58:05 +02:00
public void updateFastScrollColors() {
if (fastScroll != null) {
fastScroll.updateColors();
}
}
2019-01-23 18:03:33 +01:00
public void setPinnedHeaderShadowDrawable(Drawable drawable) {
pinnedHeaderShadowDrawable = drawable;
}
2017-12-08 18:35:59 +01:00
@Override
public boolean canScrollVertically(int direction) {
return scrollEnabled && super.canScrollVertically(direction);
}
public void setScrollEnabled(boolean value) {
scrollEnabled = value;
}
2019-05-14 14:08:05 +02:00
public void highlightRow(RecyclerListView.IntReturnCallback callback) {
highlightRowInternal(callback, true);
}
private void highlightRowInternal(RecyclerListView.IntReturnCallback callback, boolean canHighlightLater) {
if (removeHighlighSelectionRunnable != null) {
AndroidUtilities.cancelRunOnUIThread(removeHighlighSelectionRunnable);
removeHighlighSelectionRunnable = null;
}
RecyclerView.ViewHolder holder = findViewHolderForAdapterPosition(callback.run());
if (holder != null) {
positionSelector(holder.getLayoutPosition(), holder.itemView);
if (selectorDrawable != null) {
final Drawable d = selectorDrawable.getCurrent();
if (d instanceof TransitionDrawable) {
if (onItemLongClickListener != null || onItemClickListenerExtended != null) {
((TransitionDrawable) d).startTransition(ViewConfiguration.getLongPressTimeout());
} else {
((TransitionDrawable) d).resetTransition();
}
}
if (Build.VERSION.SDK_INT >= 21) {
selectorDrawable.setHotspot(holder.itemView.getMeasuredWidth() / 2, holder.itemView.getMeasuredHeight() / 2);
}
}
if (selectorDrawable != null && selectorDrawable.isStateful()) {
if (selectorDrawable.setState(getDrawableStateForSelector())) {
invalidateDrawable(selectorDrawable);
}
}
AndroidUtilities.runOnUIThread(removeHighlighSelectionRunnable = () -> {
removeHighlighSelectionRunnable = null;
pendingHighlightPosition = null;
if (selectorDrawable != null) {
Drawable d = selectorDrawable.getCurrent();
if (d instanceof TransitionDrawable) {
((TransitionDrawable) d).resetTransition();
}
}
if (selectorDrawable != null && selectorDrawable.isStateful()) {
selectorDrawable.setState(StateSet.NOTHING);
}
}, 700);
} else if (canHighlightLater) {
pendingHighlightPosition = callback;
}
}
2015-05-21 23:27:47 +02:00
@Override
public boolean onInterceptTouchEvent(MotionEvent e) {
2017-07-08 18:32:04 +02:00
if (!isEnabled()) {
return false;
}
2015-05-21 23:27:47 +02:00
if (disallowInterceptTouchEvents) {
requestDisallowInterceptTouchEvent(true);
}
return onInterceptTouchListener != null && onInterceptTouchListener.onInterceptTouchEvent(e) || super.onInterceptTouchEvent(e);
}
2020-01-23 07:15:40 +01:00
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
2022-03-11 17:49:54 +01:00
FastScroll fastScroll = getFastScroll();
if (fastScroll != null && fastScroll.isVisible && fastScroll.isMoving && (ev.getActionMasked() != MotionEvent.ACTION_UP && ev.getActionMasked() != MotionEvent.ACTION_CANCEL)) {
return true;
}
2020-01-23 07:15:40 +01:00
if (sectionsAdapter != null && pinnedHeader != null && pinnedHeader.getAlpha() != 0 && pinnedHeader.dispatchTouchEvent(ev)) {
return true;
}
return super.dispatchTouchEvent(ev);
}
2020-07-26 10:03:38 +02:00
int emptyViewAnimateToVisibility;
2022-11-12 09:41:35 +01:00
public void checkIfEmpty() {
checkIfEmpty(updateEmptyViewAnimated());
}
2020-07-26 10:03:38 +02:00
private void checkIfEmpty(boolean animated) {
2019-12-31 14:08:08 +01:00
if (isHidden) {
return;
}
2017-12-08 18:35:59 +01:00
if (getAdapter() == null || emptyView == null) {
if (hiddenByEmptyView && getVisibility() != VISIBLE) {
setVisibility(VISIBLE);
hiddenByEmptyView = false;
}
2015-05-21 23:27:47 +02:00
return;
}
2020-09-30 15:48:47 +02:00
boolean emptyViewVisible = emptyViewIsVisible();
2019-12-31 14:08:08 +01:00
int newVisibility = emptyViewVisible ? VISIBLE : GONE;
2022-09-16 20:48:21 +02:00
if (!animateEmptyView || !SharedConfig.animationsEnabled()) {
2020-07-26 10:03:38 +02:00
animated = false;
}
if (animated) {
if (emptyViewAnimateToVisibility != newVisibility) {
emptyViewAnimateToVisibility = newVisibility;
if (newVisibility == VISIBLE) {
emptyView.animate().setListener(null).cancel();
if (emptyView.getVisibility() == GONE) {
emptyView.setVisibility(VISIBLE);
emptyView.setAlpha(0);
2020-09-30 15:48:47 +02:00
if (emptyViewAnimationType == 1) {
emptyView.setScaleX(0.7f);
emptyView.setScaleY(0.7f);
}
2020-07-26 10:03:38 +02:00
}
2020-09-30 15:48:47 +02:00
emptyView.animate().alpha(1f).scaleX(1).scaleY(1).setDuration(150).start();
2020-07-26 10:03:38 +02:00
} else {
if (emptyView.getVisibility() != GONE) {
2020-09-30 15:48:47 +02:00
ViewPropertyAnimator animator = emptyView.animate().alpha(0);
if (emptyViewAnimationType == 1) {
animator.scaleY(0.7f).scaleX(0.7f);
}
animator.setDuration(150).setListener(new AnimatorListenerAdapter() {
2020-07-26 10:03:38 +02:00
@Override
public void onAnimationEnd(Animator animation) {
if (emptyView != null) {
emptyView.setVisibility(GONE);
}
}
}).start();
}
}
}
} else {
emptyViewAnimateToVisibility = newVisibility;
2019-12-31 14:08:08 +01:00
emptyView.setVisibility(newVisibility);
2020-07-26 10:03:38 +02:00
emptyView.setAlpha(1f);
2019-12-31 14:08:08 +01:00
}
if (hideIfEmpty) {
newVisibility = emptyViewVisible ? INVISIBLE : VISIBLE;
if (getVisibility() != newVisibility) {
setVisibility(newVisibility);
}
hiddenByEmptyView = true;
}
}
2020-09-30 15:48:47 +02:00
protected boolean emptyViewIsVisible() {
if (getAdapter() == null || isFastScrollAnimationRunning()) {
return false;
}
return getAdapter().getItemCount() == 0;
}
2019-12-31 14:08:08 +01:00
public void hide() {
if (isHidden) {
return;
}
isHidden = true;
if (getVisibility() != GONE) {
setVisibility(GONE);
}
if (emptyView != null && emptyView.getVisibility() != GONE) {
emptyView.setVisibility(GONE);
}
}
public void show() {
if (!isHidden) {
return;
}
isHidden = false;
2020-07-26 10:03:38 +02:00
checkIfEmpty(false);
2017-12-08 18:35:59 +01:00
}
@Override
public void setVisibility(int visibility) {
super.setVisibility(visibility);
if (visibility != VISIBLE) {
hiddenByEmptyView = false;
}
2015-05-21 23:27:47 +02:00
}
@Override
public void setOnScrollListener(OnScrollListener listener) {
onScrollListener = listener;
}
2019-12-31 14:08:08 +01:00
public void setHideIfEmpty(boolean value) {
hideIfEmpty = value;
}
2019-07-18 15:01:39 +02:00
public OnScrollListener getOnScrollListener() {
return onScrollListener;
}
2015-05-21 23:27:47 +02:00
public void setOnInterceptTouchListener(OnInterceptTouchListener listener) {
onInterceptTouchListener = listener;
}
public void setInstantClick(boolean value) {
instantClick = value;
}
public void setDisallowInterceptTouchEvents(boolean value) {
disallowInterceptTouchEvents = value;
}
2021-11-05 11:06:49 +01:00
public void setFastScrollEnabled(int type) {
fastScroll = new FastScroll(getContext(), type);
2017-03-31 01:58:05 +02:00
if (getParent() != null) {
((ViewGroup) getParent()).addView(fastScroll);
}
}
public void setFastScrollVisible(boolean value) {
if (fastScroll == null) {
return;
}
fastScroll.setVisibility(value ? VISIBLE : GONE);
2022-03-11 17:49:54 +01:00
fastScroll.isVisible = value;
2017-03-31 01:58:05 +02:00
}
2022-03-11 17:49:54 +01:00
public void setSectionsType(@SectionsType int type) {
2017-03-31 01:58:05 +02:00
sectionsType = type;
2022-03-11 17:49:54 +01:00
if (sectionsType == SECTIONS_TYPE_STICKY_HEADERS || sectionsType == SECTIONS_TYPE_FAST_SCROLL_ONLY) {
2017-03-31 01:58:05 +02:00
headers = new ArrayList<>();
headersCache = new ArrayList<>();
}
}
2019-01-23 18:03:33 +01:00
public void setPinnedSectionOffsetY(int offset) {
sectionOffset = offset;
invalidate();
}
2017-03-31 01:58:05 +02:00
private void positionSelector(int position, View sel) {
positionSelector(position, sel, false, -1, -1);
}
2022-11-05 13:34:47 +01:00
public void updateSelector() {
if (selectorPosition != NO_POSITION && selectorView != null) {
positionSelector(selectorPosition, selectorView);
invalidate();
}
}
2017-03-31 01:58:05 +02:00
private void positionSelector(int position, View sel, boolean manageHotspot, float x, float y) {
2019-05-14 14:08:05 +02:00
if (removeHighlighSelectionRunnable != null) {
AndroidUtilities.cancelRunOnUIThread(removeHighlighSelectionRunnable);
removeHighlighSelectionRunnable = null;
pendingHighlightPosition = null;
}
2017-03-31 01:58:05 +02:00
if (selectorDrawable == null) {
return;
}
final boolean positionChanged = position != selectorPosition;
2019-01-23 18:03:33 +01:00
int bottomPadding;
if (getAdapter() instanceof SelectionAdapter) {
bottomPadding = ((SelectionAdapter) getAdapter()).getSelectionBottomPadding(sel);
} else {
bottomPadding = 0;
}
2017-03-31 01:58:05 +02:00
if (position != NO_POSITION) {
selectorPosition = position;
}
2022-11-05 13:34:47 +01:00
selectorView = sel;
2022-04-16 16:43:17 +02:00
if (selectorType == 8) {
Theme.setMaskDrawableRad(selectorDrawable, selectorRadius, 0);
} else if (topBottomSelectorRadius > 0 && getAdapter() != null) {
2020-12-23 08:48:30 +01:00
Theme.setMaskDrawableRad(selectorDrawable, position == 0 ? topBottomSelectorRadius : 0, position == getAdapter().getItemCount() - 2 ? topBottomSelectorRadius : 0);
}
2019-01-23 18:03:33 +01:00
selectorRect.set(sel.getLeft(), sel.getTop(), sel.getRight(), sel.getBottom() - bottomPadding);
2022-02-01 14:00:45 +01:00
selectorRect.offset((int) sel.getTranslationX(), (int) sel.getTranslationY());
2017-03-31 01:58:05 +02:00
final boolean enabled = sel.isEnabled();
if (isChildViewEnabled != enabled) {
isChildViewEnabled = enabled;
}
if (positionChanged) {
selectorDrawable.setVisible(false, false);
selectorDrawable.setState(StateSet.NOTHING);
}
selectorDrawable.setBounds(selectorRect);
if (positionChanged) {
if (getVisibility() == VISIBLE) {
selectorDrawable.setVisible(true, false);
}
}
if (Build.VERSION.SDK_INT >= 21 && manageHotspot) {
selectorDrawable.setHotspot(x, y);
}
}
2019-12-31 14:08:08 +01:00
public void setAllowItemsInteractionDuringAnimation(boolean value) {
allowItemsInteractionDuringAnimation = value;
}
public void hideSelector(boolean animated) {
2019-05-14 14:08:05 +02:00
if (currentChildView != null) {
View child = currentChildView;
2019-12-31 14:08:08 +01:00
onChildPressed(currentChildView, 0, 0, false);
2019-05-14 14:08:05 +02:00
currentChildView = null;
2019-12-31 14:08:08 +01:00
if (animated) {
removeSelection(child, null);
}
}
if (!animated) {
selectorDrawable.setState(StateSet.NOTHING);
selectorRect.setEmpty();
2019-05-14 14:08:05 +02:00
}
}
2017-03-31 01:58:05 +02:00
private void updateSelectorState() {
if (selectorDrawable != null && selectorDrawable.isStateful()) {
if (currentChildView != null) {
if (selectorDrawable.setState(getDrawableStateForSelector())) {
invalidateDrawable(selectorDrawable);
}
2019-05-14 14:08:05 +02:00
} else if (removeHighlighSelectionRunnable == null) {
2017-03-31 01:58:05 +02:00
selectorDrawable.setState(StateSet.NOTHING);
}
}
}
private int[] getDrawableStateForSelector() {
final int[] state = onCreateDrawableState(1);
state[state.length - 1] = android.R.attr.state_pressed;
return state;
}
@Override
public void onChildAttachedToWindow(View child) {
if (getAdapter() instanceof SelectionAdapter) {
ViewHolder holder = findContainingViewHolder(child);
if (holder != null) {
child.setEnabled(((SelectionAdapter) getAdapter()).isEnabled(holder));
2021-12-30 11:52:40 +01:00
if (accessibilityEnabled) {
child.setAccessibilityDelegate(accessibilityDelegate);
}
2017-03-31 01:58:05 +02:00
}
} else {
child.setEnabled(false);
2021-12-30 11:52:40 +01:00
child.setAccessibilityDelegate(null);
2017-03-31 01:58:05 +02:00
}
super.onChildAttachedToWindow(child);
}
@Override
protected void drawableStateChanged() {
super.drawableStateChanged();
updateSelectorState();
}
@Override
public boolean verifyDrawable(Drawable drawable) {
return selectorDrawable == drawable || super.verifyDrawable(drawable);
}
@Override
public void jumpDrawablesToCurrentState() {
super.jumpDrawablesToCurrentState();
if (selectorDrawable != null) {
selectorDrawable.jumpToCurrentState();
}
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
if (fastScroll != null && fastScroll.getParent() != getParent()) {
ViewGroup parent = (ViewGroup) fastScroll.getParent();
if (parent != null) {
parent.removeView(fastScroll);
}
parent = (ViewGroup) getParent();
parent.addView(fastScroll);
}
}
2015-05-21 23:27:47 +02:00
@Override
public void setAdapter(Adapter adapter) {
final Adapter oldAdapter = getAdapter();
if (oldAdapter != null) {
oldAdapter.unregisterAdapterDataObserver(observer);
}
2017-03-31 01:58:05 +02:00
if (headers != null) {
headers.clear();
headersCache.clear();
}
2019-01-23 18:03:33 +01:00
currentFirst = -1;
2017-03-31 01:58:05 +02:00
selectorPosition = NO_POSITION;
2022-11-05 13:34:47 +01:00
selectorView = null;
2017-03-31 01:58:05 +02:00
selectorRect.setEmpty();
pinnedHeader = null;
if (adapter instanceof SectionsAdapter) {
sectionsAdapter = (SectionsAdapter) adapter;
} else {
sectionsAdapter = null;
}
2015-05-21 23:27:47 +02:00
super.setAdapter(adapter);
if (adapter != null) {
adapter.registerAdapterDataObserver(observer);
}
2020-07-26 10:03:38 +02:00
checkIfEmpty(false);
2015-05-21 23:27:47 +02:00
}
@Override
public void stopScroll() {
try {
super.stopScroll();
2017-03-31 01:58:05 +02:00
} catch (NullPointerException ignore) {
2015-05-21 23:27:47 +02:00
}
}
2016-05-25 23:49:47 +02:00
2018-07-30 04:07:02 +02:00
@Override
public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow, int type) {
if (longPressCalled) {
if (onItemLongClickListenerExtended != null) {
onItemLongClickListenerExtended.onMove(dx, dy);
}
consumed[0] = dx;
consumed[1] = dy;
return true;
}
return super.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow, type);
}
2016-05-25 23:49:47 +02:00
@Override
public boolean hasOverlappingRendering() {
return false;
}
2017-03-31 01:58:05 +02:00
private View getSectionHeaderView(int section, View oldView) {
boolean shouldLayout = oldView == null;
View view = sectionsAdapter.getSectionHeaderView(section, oldView);
if (shouldLayout) {
ensurePinnedHeaderLayout(view, false);
}
return view;
}
private void ensurePinnedHeaderLayout(View header, boolean forceLayout) {
2019-12-31 14:08:08 +01:00
if (header == null) {
return;
}
2017-03-31 01:58:05 +02:00
if (header.isLayoutRequested() || forceLayout) {
2022-03-11 17:49:54 +01:00
if (sectionsType == SECTIONS_TYPE_STICKY_HEADERS) {
2017-03-31 01:58:05 +02:00
ViewGroup.LayoutParams layoutParams = header.getLayoutParams();
int heightSpec = MeasureSpec.makeMeasureSpec(layoutParams.height, MeasureSpec.EXACTLY);
int widthSpec = MeasureSpec.makeMeasureSpec(layoutParams.width, MeasureSpec.EXACTLY);
try {
header.measure(widthSpec, heightSpec);
} catch (Exception e) {
FileLog.e(e);
}
2022-03-11 17:49:54 +01:00
} else if (sectionsType == SECTIONS_TYPE_DATE) {
2017-03-31 01:58:05 +02:00
int widthSpec = MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY);
int heightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
try {
header.measure(widthSpec, heightSpec);
} catch (Exception e) {
FileLog.e(e);
}
}
header.layout(0, 0, header.getMeasuredWidth(), header.getMeasuredHeight());
}
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
2019-12-31 14:08:08 +01:00
if (overlayContainer != null) {
overlayContainer.requestLayout();
}
2022-03-11 17:49:54 +01:00
if (sectionsType == SECTIONS_TYPE_STICKY_HEADERS) {
2017-03-31 01:58:05 +02:00
if (sectionsAdapter == null || headers.isEmpty()) {
return;
}
for (int a = 0; a < headers.size(); a++) {
View header = headers.get(a);
ensurePinnedHeaderLayout(header, true);
}
2022-03-11 17:49:54 +01:00
} else if (sectionsType == SECTIONS_TYPE_DATE) {
2017-03-31 01:58:05 +02:00
if (sectionsAdapter == null || pinnedHeader == null) {
return;
}
ensurePinnedHeaderLayout(pinnedHeader, true);
}
}
2022-08-12 17:23:51 +02:00
public Rect getSelectorRect() {
return selectorRect;
}
2017-03-31 01:58:05 +02:00
@Override
protected void dispatchDraw(Canvas canvas) {
2021-12-07 14:02:02 +01:00
if (itemsEnterAnimator != null) {
itemsEnterAnimator.dispatchDraw();
}
2020-03-30 14:00:09 +02:00
if (drawSelectorBehind && !selectorRect.isEmpty()) {
selectorDrawable.setBounds(selectorRect);
2022-08-12 17:23:51 +02:00
canvas.save();
if (selectorTransformer != null) {
selectorTransformer.accept(canvas);
}
2020-03-30 14:00:09 +02:00
selectorDrawable.draw(canvas);
2022-08-12 17:23:51 +02:00
canvas.restore();
2020-03-30 14:00:09 +02:00
}
2017-03-31 01:58:05 +02:00
super.dispatchDraw(canvas);
2020-03-30 14:00:09 +02:00
if (!drawSelectorBehind && !selectorRect.isEmpty()) {
2019-12-31 14:08:08 +01:00
selectorDrawable.setBounds(selectorRect);
2022-08-12 17:23:51 +02:00
canvas.save();
if (selectorTransformer != null) {
selectorTransformer.accept(canvas);
}
2019-12-31 14:08:08 +01:00
selectorDrawable.draw(canvas);
2022-08-12 17:23:51 +02:00
canvas.restore();
2019-12-31 14:08:08 +01:00
}
if (overlayContainer != null) {
overlayContainer.draw(canvas);
}
2022-03-11 17:49:54 +01:00
if (sectionsType == SECTIONS_TYPE_STICKY_HEADERS) {
2019-12-31 14:08:08 +01:00
if (sectionsAdapter != null && !headers.isEmpty()) {
for (int a = 0; a < headers.size(); a++) {
View header = headers.get(a);
int saveCount = canvas.save();
int top = (Integer) header.getTag();
canvas.translate(LocaleController.isRTL ? getWidth() - header.getWidth() : 0, top);
canvas.clipRect(0, 0, getWidth(), header.getMeasuredHeight());
header.draw(canvas);
canvas.restoreToCount(saveCount);
}
2017-03-31 01:58:05 +02:00
}
2022-03-11 17:49:54 +01:00
} else if (sectionsType == SECTIONS_TYPE_DATE) {
2019-12-31 14:08:08 +01:00
if (sectionsAdapter != null && pinnedHeader != null && pinnedHeader.getAlpha() != 0) {
int saveCount = canvas.save();
int top = (Integer) pinnedHeader.getTag();
canvas.translate(LocaleController.isRTL ? getWidth() - pinnedHeader.getWidth() : 0, top);
if (pinnedHeaderShadowDrawable != null) {
pinnedHeaderShadowDrawable.setBounds(0, pinnedHeader.getMeasuredHeight(), getWidth(), pinnedHeader.getMeasuredHeight() + pinnedHeaderShadowDrawable.getIntrinsicHeight());
pinnedHeaderShadowDrawable.setAlpha((int) (255 * pinnedHeaderShadowAlpha));
pinnedHeaderShadowDrawable.draw(canvas);
2020-01-23 07:15:40 +01:00
long newTime = SystemClock.elapsedRealtime();
2019-12-31 14:08:08 +01:00
long dt = Math.min(20, newTime - lastAlphaAnimationTime);
lastAlphaAnimationTime = newTime;
2019-01-23 18:03:33 +01:00
if (pinnedHeaderShadowAlpha < pinnedHeaderShadowTargetAlpha) {
2019-12-31 14:08:08 +01:00
pinnedHeaderShadowAlpha += dt / 180.0f;
if (pinnedHeaderShadowAlpha > pinnedHeaderShadowTargetAlpha) {
pinnedHeaderShadowAlpha = pinnedHeaderShadowTargetAlpha;
}
invalidate();
} else if (pinnedHeaderShadowAlpha > pinnedHeaderShadowTargetAlpha) {
pinnedHeaderShadowAlpha -= dt / 180.0f;
if (pinnedHeaderShadowAlpha < pinnedHeaderShadowTargetAlpha) {
pinnedHeaderShadowAlpha = pinnedHeaderShadowTargetAlpha;
}
invalidate();
2019-01-23 18:03:33 +01:00
}
}
2019-12-31 14:08:08 +01:00
canvas.clipRect(0, 0, getWidth(), pinnedHeader.getMeasuredHeight());
pinnedHeader.draw(canvas);
canvas.restoreToCount(saveCount);
2019-01-23 18:03:33 +01:00
}
2017-03-31 01:58:05 +02:00
}
}
2022-11-05 13:34:47 +01:00
public void relayoutPinnedHeader() {
if (pinnedHeader != null) {
pinnedHeader.measure(MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.UNSPECIFIED));
pinnedHeader.layout(0, 0, pinnedHeader.getMeasuredWidth(), pinnedHeader.getMeasuredHeight());
invalidate();
}
}
2017-03-31 01:58:05 +02:00
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
selectorPosition = NO_POSITION;
2022-11-05 13:34:47 +01:00
selectorView = null;
2017-03-31 01:58:05 +02:00
selectorRect.setEmpty();
2021-12-07 14:02:02 +01:00
if (itemsEnterAnimator != null) {
itemsEnterAnimator.onDetached();
}
2017-03-31 01:58:05 +02:00
}
2019-12-31 14:08:08 +01:00
public void addOverlayView(View view, FrameLayout.LayoutParams layoutParams) {
if (overlayContainer == null) {
overlayContainer = new FrameLayout(getContext()) {
@Override
public void requestLayout() {
super.requestLayout();
try {
final int measuredWidth = RecyclerListView.this.getMeasuredWidth();
final int measuredHeight = RecyclerListView.this.getMeasuredHeight();
measure(MeasureSpec.makeMeasureSpec(measuredWidth, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(measuredHeight, MeasureSpec.EXACTLY));
layout(0, 0, overlayContainer.getMeasuredWidth(), overlayContainer.getMeasuredHeight());
} catch (Exception ignored) {
}
}
};
}
overlayContainer.addView(view, layoutParams);
}
public void removeOverlayView(View view) {
if (overlayContainer != null) {
overlayContainer.removeView(view);
}
}
2017-03-31 01:58:05 +02:00
public ArrayList<View> getHeaders() {
return headers;
}
public ArrayList<View> getHeadersCache() {
return headersCache;
}
public View getPinnedHeader() {
return pinnedHeader;
}
2019-12-31 14:08:08 +01:00
2020-07-26 10:03:38 +02:00
public boolean isFastScrollAnimationRunning() {
return fastScrollAnimationRunning;
}
2019-12-31 14:08:08 +01:00
@Override
public void requestLayout() {
2020-07-26 10:03:38 +02:00
if (fastScrollAnimationRunning) {
2019-12-31 14:08:08 +01:00
return;
}
super.requestLayout();
}
2020-03-30 14:00:09 +02:00
2020-09-30 15:48:47 +02:00
public void setAnimateEmptyView(boolean animate, int emptyViewAnimationType) {
2020-07-26 10:03:38 +02:00
animateEmptyView = animate;
2020-09-30 15:48:47 +02:00
this.emptyViewAnimationType = emptyViewAnimationType;
2020-07-26 10:03:38 +02:00
}
2020-03-30 14:00:09 +02:00
public static class FoucsableOnTouchListener implements OnTouchListener {
private float x;
private float y;
private boolean onFocus;
@Override
public boolean onTouch(View v, MotionEvent event) {
ViewParent parent = v.getParent();
if (parent == null) {
return false;
}
if (event.getAction() == MotionEvent.ACTION_DOWN) {
x = event.getX();
y = event.getY();
onFocus = true;
parent.requestDisallowInterceptTouchEvent(true);
2021-12-30 11:52:40 +01:00
}
if (event.getAction() == MotionEvent.ACTION_MOVE) {
2020-03-30 14:00:09 +02:00
float dx = (x - event.getX());
float dy = (y - event.getY());
float touchSlop = ViewConfiguration.get(v.getContext()).getScaledTouchSlop();
2021-12-30 11:52:40 +01:00
if (onFocus && Math.sqrt(dx * dx + dy * dy) > touchSlop) {
2020-03-30 14:00:09 +02:00
onFocus = false;
parent.requestDisallowInterceptTouchEvent(false);
}
} else if (event.getAction() == MotionEvent.ACTION_UP || event.getAction() == MotionEvent.ACTION_CANCEL) {
onFocus = false;
parent.requestDisallowInterceptTouchEvent(false);
}
return false;
}
}
2020-10-30 11:26:29 +01:00
@Override
public void setTranslationY(float translationY) {
super.setTranslationY(translationY);
if (fastScroll != null) {
fastScroll.setTranslationY(translationY);
}
}
2021-07-15 16:24:57 +02:00
public void startMultiselect(int positionFrom, boolean useRelativePositions, onMultiSelectionChanged multiSelectionListener) {
if (!multiSelectionGesture) {
listPaddings = new int[2];
selectedPositions = new HashSet<>();
getParent().requestDisallowInterceptTouchEvent(true);
this.multiSelectionListener = multiSelectionListener;
multiSelectionGesture = true;
startSelectionFrom = currentSelectedPosition = positionFrom;
}
this.useRelativePositions = useRelativePositions;
}
@Override
public boolean onTouchEvent(MotionEvent e) {
2021-11-05 11:06:49 +01:00
if (fastScroll != null && fastScroll.pressed) {
return false;
}
2021-12-30 11:52:40 +01:00
if (multiSelectionGesture && e.getAction() != MotionEvent.ACTION_DOWN && e.getAction() != MotionEvent.ACTION_UP && e.getAction() != MotionEvent.ACTION_CANCEL) {
2021-07-15 16:24:57 +02:00
if (lastX == Float.MAX_VALUE && lastY == Float.MAX_VALUE) {
lastX = e.getX();
lastY = e.getY();
}
if (!multiSelectionGestureStarted && Math.abs(e.getY() - lastY) > touchSlop) {
multiSelectionGestureStarted = true;
getParent().requestDisallowInterceptTouchEvent(true);
}
if (multiSelectionGestureStarted) {
chekMultiselect(e.getX(), e.getY());
multiSelectionListener.getPaddings(listPaddings);
if (e.getY() > getMeasuredHeight() - AndroidUtilities.dp(56) - listPaddings[1] && !(currentSelectedPosition < startSelectionFrom && multiSelectionListener.limitReached())) {
startMultiselectScroll(false);
} else if (e.getY() < AndroidUtilities.dp(56) + listPaddings[0] && !(currentSelectedPosition > startSelectionFrom && multiSelectionListener.limitReached())) {
startMultiselectScroll(true);
} else {
cancelMultiselectScroll();
}
}
return true;
}
lastX = Float.MAX_VALUE;
lastY = Float.MAX_VALUE;
multiSelectionGesture = false;
multiSelectionGestureStarted = false;
getParent().requestDisallowInterceptTouchEvent(false);
cancelMultiselectScroll();
return super.onTouchEvent(e);
}
private boolean chekMultiselect(float x, float y) {
y = Math.min(getMeasuredHeight() - listPaddings[1], Math.max(y, listPaddings[0]));
x = Math.min(getMeasuredWidth(), Math.max(x, 0));
for (int i = 0; i < getChildCount(); i++) {
multiSelectionListener.getPaddings(listPaddings);
if (useRelativePositions) {
} else {
View child = getChildAt(i);
AndroidUtilities.rectTmp.set(child.getLeft(), child.getTop(), child.getLeft() + child.getMeasuredWidth(), child.getTop() + child.getMeasuredHeight());
if (AndroidUtilities.rectTmp.contains(x, y)) {
int position = getChildLayoutPosition(child);
if (currentSelectedPosition != position) {
boolean selectionFromTop = currentSelectedPosition > startSelectionFrom || position > startSelectionFrom;
position = multiSelectionListener.checkPosition(position, selectionFromTop);
if (selectionFromTop) {
if (position > currentSelectedPosition) {
if (!multiSelectionListener.limitReached()) {
for (int k = currentSelectedPosition + 1; k <= position; k++) {
if (k == startSelectionFrom) {
continue;
}
if (multiSelectionListener.canSelect(k)) {
multiSelectionListener.onSelectionChanged(k, true, x, y);
}
}
}
} else {
for (int k = currentSelectedPosition; k > position; k--) {
if (k == startSelectionFrom) {
continue;
}
if (multiSelectionListener.canSelect(k)) {
multiSelectionListener.onSelectionChanged(k, false, x, y);
}
}
}
} else {
if (position > currentSelectedPosition) {
for (int k = currentSelectedPosition; k < position; k++) {
if (k == startSelectionFrom) {
continue;
}
if (multiSelectionListener.canSelect(k)) {
multiSelectionListener.onSelectionChanged(k, false, x, y);
}
}
} else {
if (!multiSelectionListener.limitReached()) {
for (int k = currentSelectedPosition - 1; k >= position; k--) {
if (k == startSelectionFrom) {
continue;
}
if (multiSelectionListener.canSelect(k)) {
multiSelectionListener.onSelectionChanged(k, true, x, y);
}
}
}
}
}
}
if (!multiSelectionListener.limitReached()) {
currentSelectedPosition = position;
}
break;
}
}
}
return true;
}
private void cancelMultiselectScroll() {
multiselectScrollRunning = false;
AndroidUtilities.cancelRunOnUIThread(scroller);
}
Runnable scroller = new Runnable() {
@Override
public void run() {
int dy;
multiSelectionListener.getPaddings(listPaddings);
if (multiselectScrollToTop) {
dy = -AndroidUtilities.dp(12f);
chekMultiselect(0, listPaddings[0]);
} else {
dy = AndroidUtilities.dp(12f);
chekMultiselect(0, getMeasuredHeight() - listPaddings[1]);
}
multiSelectionListener.scrollBy(dy);
if (multiselectScrollRunning) {
AndroidUtilities.runOnUIThread(scroller);
}
}
};
private void startMultiselectScroll(boolean top) {
multiselectScrollToTop = top;
if (!multiselectScrollRunning) {
multiselectScrollRunning = true;
AndroidUtilities.cancelRunOnUIThread(scroller);
AndroidUtilities.runOnUIThread(scroller);
}
}
2021-08-31 21:06:39 +02:00
public boolean isMultiselect() {
return multiSelectionGesture;
}
2021-09-20 07:54:41 +02:00
protected int getThemedColor(String key) {
Integer color = resourcesProvider != null ? resourcesProvider.getColor(key) : null;
return color != null ? color : Theme.getColor(key);
}
protected Drawable getThemedDrawable(String key) {
Drawable drawable = resourcesProvider != null ? resourcesProvider.getDrawable(key) : null;
return drawable != null ? drawable : Theme.getThemeDrawable(key);
}
protected Paint getThemedPaint(String paintKey) {
Paint paint = resourcesProvider != null ? resourcesProvider.getPaint(paintKey) : null;
return paint != null ? paint : Theme.getThemePaint(paintKey);
}
2021-07-15 16:24:57 +02:00
public interface onMultiSelectionChanged {
void onSelectionChanged(int position, boolean selected, float x, float y);
boolean canSelect(int position);
int checkPosition(int position, boolean selectionFromTop);
boolean limitReached();
void getPaddings(int paddings[]);
void scrollBy(int dy);
}
2021-12-07 14:02:02 +01:00
public void setItemsEnterAnimator(RecyclerItemsEnterAnimator itemsEnterAnimator) {
this.itemsEnterAnimator = itemsEnterAnimator;
}
2021-12-30 11:52:40 +01:00
public void setAccessibilityEnabled(boolean accessibilityEnabled) {
this.accessibilityEnabled = accessibilityEnabled;
}
}