fix scroll jittering

This commit is contained in:
yausername 2019-10-02 06:29:20 +05:30
parent 5d9b5a063b
commit 0593aaa0c1
1 changed files with 61 additions and 97 deletions

View File

@ -1,116 +1,80 @@
package android.support.design.widget; package android.support.design.widget;
import android.animation.ValueAnimator;
import android.content.Context; import android.content.Context;
import android.support.annotation.NonNull; import android.support.annotation.Nullable;
import android.support.design.animation.AnimationUtils;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.View; import android.view.MotionEvent;
import android.widget.OverScroller;
// check this https://github.com/ToDou/appbarlayout-spring-behavior/blob/master/appbarspring/src/main/java/android/support/design/widget/AppBarFlingFixBehavior.java import java.lang.reflect.Field;
// check this https://stackoverflow.com/questions/56849221/recyclerview-fling-causes-laggy-while-appbarlayout-is-scrolling/57997489#57997489
public final class FlingBehavior extends AppBarLayout.Behavior { public final class FlingBehavior extends AppBarLayout.Behavior {
private ValueAnimator mOffsetAnimator;
private static final int MAX_OFFSET_ANIMATION_DURATION = 600; // ms
public FlingBehavior() {
}
public FlingBehavior(Context context, AttributeSet attrs) { public FlingBehavior(Context context, AttributeSet attrs) {
super(context, attrs); super(context, attrs);
} }
@Override @Override
public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child, View target, int dx, int dy, int[] consumed, int type) { public boolean onInterceptTouchEvent(CoordinatorLayout parent, AppBarLayout child, MotionEvent ev) {
if (dy != 0) { switch (ev.getActionMasked()) {
int val = child.getBottom(); case MotionEvent.ACTION_DOWN:
if (val != 0) { // remove reference to old nested scrolling child
int min, max; resetNestedScrollingChild();
if (dy < 0) { // Stop fling when your finger touches the screen
// We're scrolling down stopAppBarLayoutFling();
} else { break;
// We're scrolling up default:
if (mOffsetAnimator != null && mOffsetAnimator.isRunning()) { break;
mOffsetAnimator.cancel();
} }
min = -child.getUpNestedPreScrollRange(); return super.onInterceptTouchEvent(parent, child, ev);
max = 0;
consumed[1] = scroll(coordinatorLayout, child, dy, min, max);
} }
@Nullable
private OverScroller getScrollerField() {
try {
Class<?> headerBehaviorType = this.getClass().getSuperclass().getSuperclass().getSuperclass();
if (headerBehaviorType != null) {
Field field = headerBehaviorType.getDeclaredField("scroller");
field.setAccessible(true);
return ((OverScroller) field.get(this));
}
} catch (NoSuchFieldException | IllegalAccessException e) {
// ?
}
return null;
}
@Nullable
private Field getLastNestedScrollingChildRefField() {
try {
Class<?> headerBehaviorType = this.getClass().getSuperclass().getSuperclass();
if (headerBehaviorType != null) {
Field field = headerBehaviorType.getDeclaredField("lastNestedScrollingChildRef");
field.setAccessible(true);
return field;
}
} catch (NoSuchFieldException e) {
// ?
}
return null;
}
private void resetNestedScrollingChild(){
Field field = getLastNestedScrollingChildRefField();
if(field != null){
try {
Object value = field.get(this);
if(value != null) field.set(this, null);
} catch (IllegalAccessException e) {
// ?
} }
} }
} }
@Override private void stopAppBarLayoutFling() {
public boolean onNestedPreFling(@NonNull CoordinatorLayout coordinatorLayout, @NonNull AppBarLayout child, @NonNull View target, float velocityX, float velocityY) { OverScroller scroller = getScrollerField();
if (scroller != null) scroller.forceFinished(true);
if (velocityY != 0) {
if (velocityY < 0) {
// We're flinging down
int val = child.getBottom();
if (val != 0) {
final int targetScroll =
+child.getDownNestedPreScrollRange();
animateOffsetTo(coordinatorLayout, child, targetScroll, velocityY);
} }
} else {
// We're flinging up
int val = child.getBottom();
if (val != 0) {
final int targetScroll = -child.getUpNestedPreScrollRange();
if (getTopBottomOffsetForScrollingSibling() > targetScroll) {
animateOffsetTo(coordinatorLayout, child, targetScroll, velocityY);
}
}
}
}
return super.onNestedPreFling(coordinatorLayout, child, target, velocityX, velocityY);
}
private void animateOffsetTo(final CoordinatorLayout coordinatorLayout,
final AppBarLayout child, final int offset, float velocity) {
final int distance = Math.abs(getTopBottomOffsetForScrollingSibling() - offset);
final int duration;
velocity = Math.abs(velocity);
if (velocity > 0) {
duration = 3 * Math.round(1000 * (distance / velocity));
} else {
final float distanceRatio = (float) distance / child.getHeight();
duration = (int) ((distanceRatio + 1) * 150);
}
animateOffsetWithDuration(coordinatorLayout, child, offset, duration);
}
private void animateOffsetWithDuration(final CoordinatorLayout coordinatorLayout,
final AppBarLayout child, final int offset, final int duration) {
final int currentOffset = getTopBottomOffsetForScrollingSibling();
if (currentOffset == offset) {
if (mOffsetAnimator != null && mOffsetAnimator.isRunning()) {
mOffsetAnimator.cancel();
}
return;
}
if (mOffsetAnimator == null) {
mOffsetAnimator = new ValueAnimator();
mOffsetAnimator.setInterpolator(AnimationUtils.DECELERATE_INTERPOLATOR);
mOffsetAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animator) {
setHeaderTopBottomOffset(coordinatorLayout, child,
(Integer) animator.getAnimatedValue());
}
});
} else {
mOffsetAnimator.cancel();
}
mOffsetAnimator.setDuration(Math.min(duration, MAX_OFFSET_ANIMATION_DURATION));
mOffsetAnimator.setIntValues(currentOffset, offset);
mOffsetAnimator.start();
}
} }