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

321 lines
12 KiB
Java
Raw Normal View History

2019-12-31 14:08:08 +01:00
package org.telegram.ui.Components;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
2020-03-30 14:00:09 +02:00
import android.util.SparseArray;
2019-12-31 14:08:08 +01:00
import android.view.View;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import org.telegram.ui.Cells.ChatMessageCell;
import java.util.ArrayList;
public class RecyclerAnimationScrollHelper {
public final static int SCROLL_DIRECTION_UNSET = -1;
public final static int SCROLL_DIRECTION_DOWN = 0;
public final static int SCROLL_DIRECTION_UP = 1;
private RecyclerListView recyclerView;
private LinearLayoutManager layoutManager;
private int scrollDirection;
2020-02-13 19:26:53 +01:00
private ValueAnimator animator;
2019-12-31 14:08:08 +01:00
2020-02-13 19:26:53 +01:00
private ScrollListener scrollListener;
2019-12-31 14:08:08 +01:00
2020-02-13 19:26:53 +01:00
private AnimationCallback animationCallback;
2019-12-31 14:08:08 +01:00
2020-03-30 14:00:09 +02:00
public SparseArray<View> positionToOldView = new SparseArray<>();
2019-12-31 14:08:08 +01:00
public RecyclerAnimationScrollHelper(RecyclerListView recyclerView, LinearLayoutManager layoutManager) {
this.recyclerView = recyclerView;
this.layoutManager = layoutManager;
}
public void scrollToPosition(int position, int offset) {
scrollToPosition(position, offset, layoutManager.getReverseLayout(), false);
}
public void scrollToPosition(int position, int offset, boolean bottom) {
scrollToPosition(position, offset, bottom, false);
}
public void scrollToPosition(int position, int offset, final boolean bottom, boolean smooth) {
if (recyclerView.animationRunning) return;
if (!smooth || scrollDirection == SCROLL_DIRECTION_UNSET) {
layoutManager.scrollToPositionWithOffset(position, offset, bottom);
return;
}
int n = recyclerView.getChildCount();
if (n == 0) {
layoutManager.scrollToPositionWithOffset(position, offset, bottom);
return;
}
boolean scrollDown = scrollDirection == SCROLL_DIRECTION_DOWN;
recyclerView.setScrollEnabled(false);
int h = 0;
int t = 0;
final ArrayList<View> oldViews = new ArrayList<>();
2020-03-30 14:00:09 +02:00
positionToOldView.clear();
2019-12-31 14:08:08 +01:00
for (int i = 0; i < n; i++) {
View child = recyclerView.getChildAt(0);
oldViews.add(child);
2020-03-30 14:00:09 +02:00
positionToOldView.put(layoutManager.getPosition(child), child);
2019-12-31 14:08:08 +01:00
int bot = child.getBottom();
int top = child.getTop();
if (bot > h) h = bot;
if (top < t) t = top;
if (child instanceof ChatMessageCell) {
2020-03-30 14:00:09 +02:00
((ChatMessageCell) child).setAnimationRunning(true, true);
2019-12-31 14:08:08 +01:00
}
recyclerView.removeView(child);
}
final int finalHeight = scrollDown ? h : recyclerView.getHeight() - t;
RecyclerView.Adapter adapter = recyclerView.getAdapter();
AnimatableAdapter animatableAdapter = null;
if (adapter instanceof AnimatableAdapter) {
animatableAdapter = (AnimatableAdapter) adapter;
}
layoutManager.scrollToPositionWithOffset(position, offset, bottom);
if (adapter != null) adapter.notifyDataSetChanged();
AnimatableAdapter finalAnimatableAdapter = animatableAdapter;
recyclerView.stopScroll();
recyclerView.setVerticalScrollBarEnabled(false);
if (animationCallback != null) animationCallback.onStartAnimation();
recyclerView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
@Override
public void onLayoutChange(View v, int l, int t, int r, int b, int ol, int ot, int or, int ob) {
final ArrayList<View> incomingViews = new ArrayList<>();
recyclerView.stopScroll();
int n = recyclerView.getChildCount();
int top = 0;
int bottom = 0;
for (int i = 0; i < n; i++) {
View child = recyclerView.getChildAt(i);
incomingViews.add(child);
if (child.getTop() < top)
top = child.getTop();
if (child.getBottom() > bottom)
bottom = child.getBottom();
if (child instanceof ChatMessageCell) {
2020-03-30 14:00:09 +02:00
((ChatMessageCell) child).setAnimationRunning(true, false);
2019-12-31 14:08:08 +01:00
}
}
for (View view : oldViews) {
recyclerView.addView(view);
if (view instanceof ChatMessageCell) {
2020-03-30 14:00:09 +02:00
((ChatMessageCell) view).setAnimationRunning(true, true);
2019-12-31 14:08:08 +01:00
}
}
recyclerView.animationRunning = true;
if (finalAnimatableAdapter != null) finalAnimatableAdapter.onAnimationStart();
final int scrollLength = finalHeight + (scrollDown ? -top : bottom - recyclerView.getHeight());
if (animator != null) {
animator.removeAllListeners();
animator.cancel();
}
animator = ValueAnimator.ofFloat(0, 1f);
animator.addUpdateListener(animation -> {
float value = ((float) animation.getAnimatedValue());
2020-03-30 14:00:09 +02:00
int size = oldViews.size();
for (int i = 0; i < size; i++) {
View view = oldViews.get(i);
float viewTop = view.getY();
float viewBottom = view.getY() + view.getMeasuredHeight();
if (viewBottom < 0 || viewTop > recyclerView.getMeasuredHeight()) {
continue;
}
2019-12-31 14:08:08 +01:00
if (scrollDown) {
view.setTranslationY(-scrollLength * value);
} else {
view.setTranslationY(scrollLength * value);
}
}
2020-03-30 14:00:09 +02:00
size = incomingViews.size();
for (int i = 0; i < size; i++) {
View view = incomingViews.get(i);
2019-12-31 14:08:08 +01:00
if (scrollDown) {
view.setTranslationY((scrollLength) * (1f - value));
} else {
view.setTranslationY(-(scrollLength) * (1f - value));
}
}
recyclerView.invalidate();
2020-03-30 14:00:09 +02:00
if (scrollListener != null) scrollListener.onScroll();
2019-12-31 14:08:08 +01:00
});
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
recyclerView.animationRunning = false;
for (View view : oldViews) {
if (view instanceof ChatMessageCell) {
2020-03-30 14:00:09 +02:00
((ChatMessageCell) view).setAnimationRunning(false, true);
2019-12-31 14:08:08 +01:00
}
view.setTranslationY(0);
2020-03-30 14:00:09 +02:00
recyclerView.removeView(view);
2019-12-31 14:08:08 +01:00
}
recyclerView.setVerticalScrollBarEnabled(true);
int n = recyclerView.getChildCount();
for (int i = 0; i < n; i++) {
View child = recyclerView.getChildAt(i);
if (child instanceof ChatMessageCell) {
2020-03-30 14:00:09 +02:00
((ChatMessageCell) child).setAnimationRunning(false, false);
2019-12-31 14:08:08 +01:00
}
child.setTranslationY(0);
}
2020-01-23 13:58:50 +01:00
2019-12-31 14:08:08 +01:00
if (finalAnimatableAdapter != null) {
finalAnimatableAdapter.onAnimationEnd();
}
2020-01-23 13:58:50 +01:00
if (animationCallback != null) {
animationCallback.onEndAnimation();
}
2020-03-30 14:00:09 +02:00
positionToOldView.clear();
2019-12-31 14:08:08 +01:00
animator = null;
}
});
recyclerView.removeOnLayoutChangeListener(this);
long duration = ((scrollLength / recyclerView.getMeasuredHeight()) + 1) * 200;
duration = Math.min(duration, 1300);
animator.setDuration(duration);
animator.setInterpolator(CubicBezierInterpolator.EASE_OUT_QUINT);
animator.start();
}
});
}
public void cancel() {
if (animator != null) animator.cancel();
clear();
}
private void clear() {
recyclerView.setVerticalScrollBarEnabled(true);
recyclerView.animationRunning = false;
RecyclerView.Adapter adapter = recyclerView.getAdapter();
if (adapter instanceof AnimatableAdapter)
((AnimatableAdapter) adapter).onAnimationEnd();
animator = null;
int n = recyclerView.getChildCount();
for (int i = 0; i < n; i++) {
View child = recyclerView.getChildAt(i);
child.setTranslationY(0f);
if (child instanceof ChatMessageCell) {
2020-03-30 14:00:09 +02:00
((ChatMessageCell) child).setAnimationRunning(false, false);
2019-12-31 14:08:08 +01:00
}
}
}
public void setScrollDirection(int scrollDirection) {
this.scrollDirection = scrollDirection;
}
public void setScrollListener(ScrollListener listener) {
scrollListener = listener;
}
public void setAnimationCallback(AnimationCallback animationCallback) {
this.animationCallback = animationCallback;
}
public int getScrollDirection() {
return scrollDirection;
}
public interface ScrollListener {
void onScroll();
}
public static class AnimationCallback {
public void onStartAnimation() {
}
public void onEndAnimation() {
}
}
public static abstract class AnimatableAdapter extends RecyclerListView.SelectionAdapter {
public boolean animationRunning;
private boolean shouldNotifyDataSetChanged;
private ArrayList<Integer> rangeInserted = new ArrayList<>();
private ArrayList<Integer> rangeRemoved = new ArrayList<>();
@Override
public void notifyDataSetChanged() {
if (!animationRunning) {
super.notifyDataSetChanged();
} else {
2020-01-23 13:58:50 +01:00
shouldNotifyDataSetChanged = true;
2019-12-31 14:08:08 +01:00
}
}
@Override
public void notifyItemRangeInserted(int positionStart, int itemCount) {
if (!animationRunning) {
super.notifyItemRangeInserted(positionStart, itemCount);
} else {
rangeInserted.add(positionStart);
rangeInserted.add(itemCount);
}
}
@Override
public void notifyItemRangeRemoved(int positionStart, int itemCount) {
if (!animationRunning) {
super.notifyItemRangeRemoved(positionStart, itemCount);
} else {
rangeRemoved.add(positionStart);
rangeRemoved.add(itemCount);
}
}
public void onAnimationStart() {
animationRunning = true;
shouldNotifyDataSetChanged = false;
rangeInserted.clear();
rangeRemoved.clear();
}
public void onAnimationEnd() {
animationRunning = false;
2020-01-23 13:58:50 +01:00
if (shouldNotifyDataSetChanged || !rangeInserted.isEmpty() || !rangeRemoved.isEmpty()) {
2019-12-31 14:08:08 +01:00
notifyDataSetChanged();
}
}
}
}