package com.reactnativenavigation.views.collapsingToolbar;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
import android.widget.ScrollView;
import com.reactnativenavigation.NavigationApplication;
import com.reactnativenavigation.views.collapsingToolbar.behaviours.CollapseBehaviour;
import com.reactnativenavigation.views.collapsingToolbar.behaviours.TitleBarHideOnScrollBehaviour;
public class CollapseCalculator {
private static final int FLING_DISTANCE_PIXELS_THRESHOLD = 200;
public enum Direction {
Up, Down, None
}
private float collapse;
private MotionEvent previousTouchEvent;
private float touchDownY = -1;
private float previousCollapseY = -1;
private boolean isExpended;
private boolean isCollapsed = true;
private boolean canCollapse = true;
private boolean canExpend = false;
private CollapsingView view;
private CollapseBehaviour collapseBehaviour;
protected ScrollView scrollView;
private GestureDetector flingDetector;
private OnFlingListener flingListener;
private int scrollY = 0;
private float totalCollapse = 0;
private float totalCollapseDeltaSinceTouchDown = 0;
private final int scaledTouchSlop;
private final int minimumFlingVelocity;
public CollapseCalculator(final CollapsingView collapsingView, CollapseBehaviour collapseBehaviour) {
this.view = collapsingView;
this.collapseBehaviour = collapseBehaviour;
ViewConfiguration vc = ViewConfiguration.get(NavigationApplication.instance);
scaledTouchSlop = vc.getScaledTouchSlop();
minimumFlingVelocity = vc.getScaledMinimumFlingVelocity();
setFlingDetector();
}
private void setFlingDetector() {
if (collapseBehaviour.shouldCollapseOnFling()) {
flingDetector =
new GestureDetector(NavigationApplication.instance, new GestureDetector.SimpleOnGestureListener() {
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, final float velocityY) {
final Direction direction = getScrollDirection(e1, e2);
final float diff = Math.abs(e2.getRawY() - e1.getRawY());
if (Math.abs(velocityY) < minimumFlingVelocity || diff < FLING_DISTANCE_PIXELS_THRESHOLD) {
Log.w("FLING", "Consuming fling v: [" + velocityY + "] dy: [" + diff + "]");
return true;
}
if (canCollapse && totalCollapse != 0) {
flingListener.onFling(new CollapseAmount(direction));
if (direction == Direction.Up) {
view.asView().postOnAnimation(new Runnable() {
@Override
public void run() {
Log.v("FLING", "v: [" + velocityY + "] dy: [" + diff + "]");
scrollView.fling((int) Math.abs(velocityY));
}
});
}
return true;
}
return true;
}
private Direction getScrollDirection(MotionEvent e1, MotionEvent e2) {
if (e1.getRawY() == e2.getRawY()) {
return Direction.None;
}
return e1.getRawY() - e2.getRawY() > 0 ? Direction.Up : Direction.Down;
}
});
}
}
void setScrollView(ScrollView scrollView) {
this.scrollView = scrollView;
}
void setFlingListener(OnFlingListener flingListener) {
this.flingListener = flingListener;
}
@NonNull
CollapseAmount calculate(MotionEvent event) {
updateInitialTouchY(event);
final boolean isFling = flingDetector.onTouchEvent(event);
CollapseAmount touchUpCollapse = shouldCollapseOnTouchUp(event, isFling);
if (touchUpCollapse != CollapseAmount.None) {
previousTouchEvent = MotionEvent.obtain(event);
return touchUpCollapse;
}
if (!isMoveEvent(event)) {
previousTouchEvent = MotionEvent.obtain(event);
return CollapseAmount.None;
}
final Direction direction = calculateScrollDirection(event.getRawY());
if (shouldCollapseAfterMoveEvent(direction)) {
return calculateCollapse(event);
} else {
previousCollapseY = -1;
previousTouchEvent = MotionEvent.obtain(event);
return CollapseAmount.None;
}
}
private CollapseAmount shouldCollapseOnTouchUp(MotionEvent event, boolean isFling) {
if (isTouchUp(event) && collapseBehaviour.shouldCollapseOnTouchUp() && !isFling) {
final float visibilityPercentage = view.getCurrentCollapseValue() / view.getFinalCollapseValue();
Direction direction = visibilityPercentage >= 0.5f ? Direction.Up : Direction.Down;
if (canCollapse(direction) && totalCollapse != 0) {
return new CollapseAmount(direction);
}
}
return CollapseAmount.None;
}
private boolean shouldCollapseAfterMoveEvent(Direction direction) {
if (collapseBehaviour instanceof TitleBarHideOnScrollBehaviour && !isScrolling()) {
return false;
}
return canCollapse(direction);
}
private boolean canCollapse(Direction direction) {
if (view == null) {
return false;
}
checkCollapseLimits();
return (isNotCollapsedOrExpended() ||
(canCollapse && isExpendedAndScrollingUp(direction)) ||
(canExpend && isCollapsedAndScrollingDown(direction) && collapseBehaviour.canExpend(scrollView.getScrollY()))
);
}
private boolean isScrolling() {
final int currentScrollY = scrollView.getScrollY();
final boolean isScrolling = currentScrollY != scrollY;
scrollY = currentScrollY;
return isScrolling;
}
private Direction calculateScrollDirection(float y) {
if (y == (previousCollapseY == -1 ? touchDownY : previousCollapseY)) {
return Direction.None;
}
if (previousTouchEvent == null) {
return Direction.None;
}
return y < previousTouchEvent.getRawY() ?
Direction.Up :
Direction.Down;
}
private void checkCollapseLimits() {
float currentCollapse = view.getCurrentCollapseValue();
float finalExpendedTranslation = 0;
isExpended = isExpended(currentCollapse, finalExpendedTranslation);
isCollapsed = isCollapsed(currentCollapse, view.getFinalCollapseValue());
canCollapse = calculateCanCollapse(currentCollapse, finalExpendedTranslation, view.getFinalCollapseValue());
canExpend = calculateCanExpend(currentCollapse, finalExpendedTranslation, view.getFinalCollapseValue());
}
private boolean calculateCanCollapse(float currentTopBarTranslation, float finalExpendedTranslation, float finalCollapsedTranslation) {
return currentTopBarTranslation > finalCollapsedTranslation &&
currentTopBarTranslation <= finalExpendedTranslation;
}
private boolean calculateCanExpend(float currentTopBarTranslation, float finalExpendedTranslation, float finalCollapsedTranslation) {
return currentTopBarTranslation >= finalCollapsedTranslation &&
currentTopBarTranslation < finalExpendedTranslation;
}
private boolean isCollapsedAndScrollingDown(Direction direction) {
return isCollapsed && direction == Direction.Down;
}
private boolean isExpendedAndScrollingUp(Direction direction) {
return isExpended && direction == Direction.Up;
}
private boolean isNotCollapsedOrExpended() {
return canExpend && canCollapse;
}
private boolean isCollapsed(float currentTopBarTranslation, float finalCollapsedTranslation) {
return currentTopBarTranslation == finalCollapsedTranslation;
}
private boolean isExpended(float currentTopBarTranslation, float finalExpendedTranslation) {
return currentTopBarTranslation == finalExpendedTranslation;
}
private CollapseAmount calculateCollapse(MotionEvent event) {
float y = event.getRawY();
if (previousCollapseY == -1) {
previousCollapseY = y;
}
collapse = calculateCollapse(y);
totalCollapse += collapse;
totalCollapseDeltaSinceTouchDown += Math.abs(y - previousCollapseY);
previousCollapseY = y;
previousTouchEvent = MotionEvent.obtain(event);
return totalCollapseDeltaSinceTouchDown < scaledTouchSlop ? CollapseAmount.None : new CollapseAmount(collapse);
}
private float calculateCollapse(float y) {
float translation = y - previousCollapseY + view.getCurrentCollapseValue();
if (translation < view.getFinalCollapseValue()) {
translation = view.getFinalCollapseValue();
}
final float expendedTranslation = 0;
if (translation > expendedTranslation) {
translation = expendedTranslation;
}
return translation;
}
private void updateInitialTouchY(MotionEvent event) {
if (isTouchDown(previousTouchEvent) && isMoveEvent(event)) {
saveInitialTouchY(previousTouchEvent);
} else if (isTouchUp(event) && isMoveEvent(previousTouchEvent)) {
clearInitialTouchY();
}
}
private boolean isMoveEvent(@Nullable MotionEvent event) {
return event != null && event.getActionMasked() == MotionEvent.ACTION_MOVE;
}
private boolean isTouchDown(@Nullable MotionEvent event) {
return event != null && event.getActionMasked() == MotionEvent.ACTION_DOWN;
}
private boolean isTouchUp(@Nullable MotionEvent event) {
return event != null && event.getActionMasked() == MotionEvent.ACTION_UP;
}
private void saveInitialTouchY(MotionEvent event) {
totalCollapse = 0;
totalCollapseDeltaSinceTouchDown = 0;
touchDownY = event.getRawY();
scrollY = scrollView.getScrollY();
previousCollapseY = touchDownY;
}
private void clearInitialTouchY() {
previousCollapseY = -1;
collapse = 0;
}
}