Clean up controller swiping related stuff

multisite
Floens 10 years ago
parent ea63fd16c8
commit ff04e2d942
  1. 2
      Clover/app/src/main/java/org/floens/chan/controller/Controller.java
  2. 4
      Clover/app/src/main/java/org/floens/chan/controller/NavigationController.java
  3. 238
      Clover/app/src/main/java/org/floens/chan/controller/ui/NavigationControllerContainerLayout.java
  4. 3
      Clover/app/src/main/java/org/floens/chan/core/settings/ChanSettings.java
  5. 18
      Clover/app/src/main/java/org/floens/chan/ui/activity/StartActivity.java
  6. 12
      Clover/app/src/main/java/org/floens/chan/ui/controller/StyledToolbarNavigationController.java
  7. 7
      Clover/app/src/main/java/org/floens/chan/ui/controller/ToolbarNavigationController.java
  8. 55
      Clover/app/src/main/java/org/floens/chan/ui/toolbar/Toolbar.java

@ -36,7 +36,7 @@ import java.util.ArrayList;
import java.util.List;
public abstract class Controller {
private static final boolean LOG_STATES = false;
private static final boolean LOG_STATES = true;
public Context context;
public ViewGroup view;

@ -89,6 +89,8 @@ public abstract class NavigationController extends Controller {
throw new IllegalArgumentException("Cannot transition while another transition is in progress.");
}
blockingInput = true;
to.onShow();
return true;
@ -110,7 +112,7 @@ public abstract class NavigationController extends Controller {
}
public void transition(final Controller from, final Controller to, final boolean pushing, ControllerTransition controllerTransition) {
if (this.controllerTransition != null) {
if (this.controllerTransition != null || blockingInput) {
throw new IllegalArgumentException("Cannot transition while another transition is in progress.");
}

@ -18,6 +18,7 @@
package org.floens.chan.controller.ui;
import android.content.Context;
import android.content.res.Configuration;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
@ -32,30 +33,57 @@ import android.widget.Scroller;
import org.floens.chan.controller.Controller;
import org.floens.chan.controller.NavigationController;
import org.floens.chan.utils.Logger;
import org.floens.chan.utils.Time;
import static org.floens.chan.utils.AndroidUtils.dp;
public class NavigationControllerContainerLayout extends FrameLayout {
// The shadow starts at this alpha and goes up to 1f
public static final float SHADOW_MIN_ALPHA = 0.5f;
private NavigationController navigationController;
private int slopPixels;
private int minimalMovedPixels;
private int flingPixels;
private int maxFlingPixels;
private boolean swipeEnabled = true;
private MotionEvent downEvent;
private boolean dontStartSwiping = false;
private MotionEvent swipeStartEvent;
// The event used in onInterceptTouchEvent to track the initial down event
private MotionEvent interceptedEvent;
// The tracking is blocked when the user has moved too much in the y direction
private boolean blockTracking = false;
// Is the top controller being tracked and moved
private boolean tracking = false;
// The controller being tracked, corresponds with tracking
private Controller trackingController;
// The controller behind the tracking controller
private Controller behindTrackingController;
// The position of the touch after tracking has started, used to calculate the total offset from
private int trackStartPosition;
// Tracks the motion when tracking
private VelocityTracker velocityTracker;
// Used to fling and scroll the tracking view
private Scroller scroller;
private boolean popAfterSwipe = false;
// Indicate if the controller should be popped after the animation ends
private boolean finishTransitionAfterAnimation = false;
// Paint, draw rect and position for drawing the shadow
// The shadow is only drawn when tracking is true
private Paint shadowPaint;
private Rect shadowRect = new Rect();
private boolean drawShadow;
private int swipePosition;
private boolean swiping = false;
private Controller swipingController;
private int shadowPosition;
public NavigationControllerContainerLayout(Context context) {
super(context);
@ -74,7 +102,8 @@ public class NavigationControllerContainerLayout extends FrameLayout {
private void init() {
ViewConfiguration viewConfiguration = ViewConfiguration.get(getContext());
slopPixels = (int) (viewConfiguration.getScaledTouchSlop() * 0.5f);
slopPixels = viewConfiguration.getScaledTouchSlop();
minimalMovedPixels = dp(3);
flingPixels = viewConfiguration.getScaledMinimumFlingVelocity();
maxFlingPixels = viewConfiguration.getScaledMaximumFlingVelocity();
@ -93,51 +122,34 @@ public class NavigationControllerContainerLayout extends FrameLayout {
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
if (!swipeEnabled || swiping) {
if (!swipeEnabled || tracking || navigationController.isBlockingInput() || !navigationController.getTop().navigationItem.swipeable || getBelowTop() == null) {
return false;
}
int actionMasked = event.getActionMasked();
if (actionMasked != MotionEvent.ACTION_DOWN && downEvent == null) {
if (actionMasked != MotionEvent.ACTION_DOWN && interceptedEvent == null) {
// Action down wasn't called here, ignore
return false;
}
if (!navigationController.getTop().navigationItem.swipeable) {
return false;
}
if (getBelowTop() == null) {
// Cannot swipe now
return false;
}
switch (actionMasked) {
case MotionEvent.ACTION_DOWN:
// Logger.test("onInterceptTouchEvent down");
downEvent = MotionEvent.obtain(event);
interceptedEvent = MotionEvent.obtain(event);
break;
case MotionEvent.ACTION_MOVE: {
// Logger.test("onInterceptTouchEvent move");
float x = (event.getX() - downEvent.getX());
float y = (event.getY() - downEvent.getY());
float x = (event.getX() - interceptedEvent.getX());
float y = (event.getY() - interceptedEvent.getY());
if (Math.abs(y) >= slopPixels) {
// Logger.test("dontStartSwiping = true");
dontStartSwiping = true;
// Logger.test("blockTracking = true");
blockTracking = true;
}
if (!dontStartSwiping && Math.abs(x) > Math.abs(y) && x >= slopPixels && !navigationController.isBlockingInput()) {
// Logger.test("Start tracking swipe");
downEvent.recycle();
downEvent = null;
swipeStartEvent = MotionEvent.obtain(event);
velocityTracker = VelocityTracker.obtain();
velocityTracker.addMovement(event);
swiping = true;
if (!blockTracking && x >= minimalMovedPixels && Math.abs(x) > Math.abs(y)) {
startTracking(event);
return true;
}
@ -146,9 +158,9 @@ public class NavigationControllerContainerLayout extends FrameLayout {
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP: {
// Logger.test("onInterceptTouchEvent cancel/up");
downEvent.recycle();
downEvent = null;
dontStartSwiping = false;
interceptedEvent.recycle();
interceptedEvent = null;
blockTracking = false;
break;
}
}
@ -159,41 +171,34 @@ public class NavigationControllerContainerLayout extends FrameLayout {
@Override
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
if (disallowIntercept) {
if (downEvent != null) {
downEvent.recycle();
downEvent = null;
if (interceptedEvent != null) {
interceptedEvent.recycle();
interceptedEvent = null;
}
blockTracking = false;
if (tracking) {
endTracking(false);
}
dontStartSwiping = false;
}
super.requestDisallowInterceptTouchEvent(disallowIntercept);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// This touch wasn't initiated with onInterceptTouchEvent
if (swipeStartEvent == null) {
return false;
}
if (swipingController == null) {
// Start of swipe
// Logger.test("Start of swipe");
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
swipingController = navigationController.getTop();
drawShadow = true;
long start = Time.startTiming();
Controller below = getBelowTop();
navigationController.beginSwipeTransition(swipingController, below);
// endTracking();
}
Time.endTiming("attach", start);
@Override
public boolean onTouchEvent(MotionEvent event) {
if (!tracking) {
return false;
}
float translationX = Math.max(0, event.getX() - swipeStartEvent.getX());
setTopControllerTranslation((int) translationX);
int translationX = Math.max(0, ((int) event.getX()) - trackStartPosition);
setTopControllerTranslation(translationX);
velocityTracker.addMovement(event);
@ -206,37 +211,42 @@ public class NavigationControllerContainerLayout extends FrameLayout {
velocityTracker.addMovement(event);
velocityTracker.computeCurrentVelocity(1000);
float velocity = velocityTracker.getXVelocity();
int velocity = (int) velocityTracker.getXVelocity();
if (translationX > 0) {
boolean doFlingAway = false;
if (translationX > 0f) {
boolean isFling = false;
Logger.test("velocity = %d", velocity);
if (velocity > 0f && Math.abs(velocity) > flingPixels && Math.abs(velocity) < maxFlingPixels) {
scroller.fling((int) translationX, 0, (int) velocity, 0, 0, Integer.MAX_VALUE, 0, 0);
if ((velocity > 0 && Math.abs(velocity) > 2500 && Math.abs(velocity) < maxFlingPixels) || translationX >= getWidth() * 3 / 4) {
// int left = getWidth() - translationX;
// int flingVelocity = Math.max(velocity, 0);
if (scroller.getFinalX() >= getWidth()) {
isFling = true;
scroller.fling(translationX, 0, velocity, 0, 0, Integer.MAX_VALUE, 0, 0);
Logger.test("finalX = %d getWidth = %d", scroller.getFinalX(), getWidth());
// Make sure the animation always goes past the end
if (scroller.getFinalX() < getWidth()) {
scroller.startScroll(translationX, 0, getWidth(), 0, 2000);
}
doFlingAway = true;
Logger.test("Flinging away with velocity = %d", velocity);
}
if (isFling) {
// Logger.test("Flinging with velocity = " + velocity);
popAfterSwipe = true;
ViewCompat.postOnAnimation(this, flingRunnable);
if (doFlingAway) {
startFlingAnimation(true);
} else {
// Logger.test("Snapping back!");
Logger.test("Snapping back");
scroller.forceFinished(true);
scroller.startScroll((int) translationX, 0, -((int) translationX), 0, 300);
popAfterSwipe = false;
ViewCompat.postOnAnimation(this, flingRunnable);
scroller.startScroll(translationX, 0, -translationX, 0, 250);
startFlingAnimation(false);
}
} else {
finishSwipe();
// User swiped back to the left
endTracking(false);
}
swipeStartEvent.recycle();
swipeStartEvent = null;
velocityTracker.recycle();
velocityTracker = null;
@ -251,26 +261,64 @@ public class NavigationControllerContainerLayout extends FrameLayout {
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
if (drawShadow) {
float alpha = Math.min(1f, Math.max(0f, 0.5f - (swipePosition / (float) getWidth()) * 0.5f));
if (tracking) {
float alpha = Math.min(1f, Math.max(0f, SHADOW_MIN_ALPHA - (shadowPosition / (float) getWidth()) * SHADOW_MIN_ALPHA));
shadowPaint.setColor(Color.argb((int) (alpha * 255f), 0, 0, 0));
shadowRect.set(0, 0, swipePosition, getHeight());
shadowRect.set(0, 0, shadowPosition, getHeight());
canvas.drawRect(shadowRect, shadowPaint);
}
}
private void finishSwipe() {
Controller below = getBelowTop();
private void startTracking(MotionEvent startEvent) {
if (tracking) {
throw new IllegalStateException("startTracking called but already tracking");
}
tracking = true;
trackingController = navigationController.getTop();
behindTrackingController = getBelowTop();
interceptedEvent.recycle();
interceptedEvent = null;
trackStartPosition = (int) startEvent.getX();
velocityTracker = VelocityTracker.obtain();
velocityTracker.addMovement(startEvent);
long start = Time.startTiming();
navigationController.beginSwipeTransition(trackingController, behindTrackingController);
Time.endTiming("attach", start);
Logger.test("Start tracking " + trackingController.getClass().getSimpleName());
}
private void endTracking(boolean finishTransition) {
Logger.test("endTracking finishTransition = " + finishTransition);
navigationController.endSwipeTransition(swipingController, below, popAfterSwipe);
swipingController = null;
drawShadow = false;
swiping = false;
if (!tracking) {
throw new IllegalStateException("endTracking called but was not tracking");
}
navigationController.endSwipeTransition(trackingController, behindTrackingController, finishTransition);
tracking = false;
trackingController = null;
behindTrackingController = null;
}
private void startFlingAnimation(boolean finishTransitionAfterAnimation) {
this.finishTransitionAfterAnimation = finishTransitionAfterAnimation;
ViewCompat.postOnAnimation(this, flingRunnable);
}
private Runnable flingRunnable = new Runnable() {
@Override
public void run() {
if (!tracking) {
throw new IllegalStateException("fling animation running while not tracking");
}
boolean finished = false;
if (scroller.computeScrollOffset()) {
@ -289,15 +337,15 @@ public class NavigationControllerContainerLayout extends FrameLayout {
if (!finished) {
ViewCompat.postOnAnimation(NavigationControllerContainerLayout.this, flingRunnable);
} else {
finishSwipe();
endTracking(finishTransitionAfterAnimation);
}
}
};
private void setTopControllerTranslation(int translationX) {
swipePosition = translationX;
swipingController.view.setTranslationX(swipePosition);
navigationController.swipeTransitionProgress(swipePosition / (float) getWidth());
shadowPosition = translationX;
trackingController.view.setTranslationX(translationX);
navigationController.swipeTransitionProgress(translationX / (float) getWidth());
invalidate();
}

@ -21,7 +21,6 @@ import android.content.SharedPreferences;
import android.os.Environment;
import org.floens.chan.Chan;
import org.floens.chan.R;
import org.floens.chan.chan.ChanUrls;
import org.floens.chan.ui.adapter.PostsFilter;
import org.floens.chan.ui.cell.PostCellInterface;
@ -118,7 +117,7 @@ public class ChanSettings {
theme = new StringSetting(p, "preference_theme", "light");
boolean tablet = AndroidUtils.getRes().getBoolean(R.bool.is_tablet);
boolean tablet = false;
fontSize = new StringSetting(p, "preference_font", tablet ? "16" : "14");
openLinkConfirmation = new BooleanSetting(p, "preference_open_link_confirmation", true);

@ -302,23 +302,28 @@ public class StartActivity extends AppCompatActivity implements NfcAdapter.Creat
.setPositiveButton(R.string.exit, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
stackTop().onHide();
StartActivity.super.onBackPressed();
}
})
.show();
} else {
// Don't destroy the view, let Android do that or it'll create artifacts
stackTop().onHide();
super.onBackPressed();
}
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
runtimePermissionsHelper.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
@Override
protected void onDestroy() {
super.onDestroy();
stackTop().onHide();
stackTop().onDestroy();
stack.clear();
}
@ -344,13 +349,6 @@ public class StartActivity extends AppCompatActivity implements NfcAdapter.Creat
imagePickDelegate.onActivityResult(requestCode, resultCode, data);
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
runtimePermissionsHelper.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
private Controller stackTop() {
return stack.get(stack.size() - 1);
}

@ -55,6 +55,18 @@ public class StyledToolbarNavigationController extends ToolbarNavigationControll
}
}
@Override
public void endSwipeTransition(Controller from, Controller to, boolean finish) {
super.endSwipeTransition(from, to, finish);
if (finish) {
DrawerController drawerController = getDrawerController();
if (drawerController != null) {
drawerController.setDrawerEnabled(to.navigationItem.hasDrawer);
}
}
}
@Override
public boolean onBack() {
if (super.onBack()) {

@ -75,12 +75,7 @@ public abstract class ToolbarNavigationController extends NavigationController i
super.endSwipeTransition(from, to, finish);
toolbar.finishTransition(finish);
if (finish) {
updateToolbarCollapse(to, controllerTransition != null);
} else {
updateToolbarCollapse(from, controllerTransition != null);
}
updateToolbarCollapse(finish ? to : from, controllerTransition != null);
}
@Override

@ -49,7 +49,7 @@ import static org.floens.chan.utils.AndroidUtils.dp;
import static org.floens.chan.utils.AndroidUtils.getAttrColor;
import static org.floens.chan.utils.AndroidUtils.setRoundItemBackground;
public class Toolbar extends LinearLayout implements View.OnClickListener, LoadView.Listener {
public class Toolbar extends LinearLayout implements View.OnClickListener {
public static final int TOOLBAR_COLLAPSE_HIDE = 1000000;
public static final int TOOLBAR_COLLAPSE_SHOW = -1000000;
@ -83,6 +83,7 @@ public class Toolbar extends LinearLayout implements View.OnClickListener, LoadV
private int lastScrollDeltaOffset;
private int scrollOffset;
private boolean transitioning = false;
private NavigationItem fromItem;
private NavigationItem toItem;
@ -156,12 +157,22 @@ public class Toolbar extends LinearLayout implements View.OnClickListener, LoadV
}
public void beginTransition(NavigationItem newItem) {
if (transitioning) {
throw new IllegalStateException("beginTransition called when already transitioning");
}
attachNavigationItem(newItem);
navigationItemContainer.addView(toItem.view, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
transitioning = true;
}
public void transitionProgress(float progress, boolean pushing) {
if (!transitioning) {
throw new IllegalStateException("transitionProgress called while not transitioning");
}
final int offset = dp(16);
toItem.view.setTranslationY((pushing ? offset : -offset) * (1f - progress));
@ -179,9 +190,16 @@ public class Toolbar extends LinearLayout implements View.OnClickListener, LoadV
}
public void finishTransition(boolean finished) {
if (!transitioning) {
throw new IllegalStateException("finishTransition called when not transitioning");
}
if (finished) {
if (fromItem != null) {
removeNavigationItem(fromItem);
// From a search otherwise
if (fromItem != toItem) {
removeNavigationItem(fromItem);
}
}
setArrowMenuProgress(toItem.hasBack || toItem.search ? 1f : 0f);
} else {
@ -191,6 +209,7 @@ public class Toolbar extends LinearLayout implements View.OnClickListener, LoadV
}
fromItem = null;
transitioning = false;
}
public void setCallback(ToolbarCallback callback) {
@ -212,14 +231,6 @@ public class Toolbar extends LinearLayout implements View.OnClickListener, LoadV
return arrowMenuDrawable;
}
@Override
public void onLoadViewRemoved(View view) {
// Remove the menu from the navigation item
if (view instanceof ViewGroup) {
((ViewGroup) view).removeAllViews();
}
}
void setTitle(NavigationItem navigationItem) {
if (navigationItem.view != null) {
TextView titleView = (TextView) navigationItem.view.findViewById(R.id.title);
@ -254,7 +265,6 @@ public class Toolbar extends LinearLayout implements View.OnClickListener, LoadV
leftButtonContainer.addView(arrowMenuView, new FrameLayout.LayoutParams(getResources().getDimensionPixelSize(R.dimen.toolbar_height), FrameLayout.LayoutParams.MATCH_PARENT, Gravity.CENTER_VERTICAL));
navigationItemContainer = new LoadView(getContext());
navigationItemContainer.setListener(this);
addView(navigationItemContainer, new LayoutParams(0, LayoutParams.MATCH_PARENT, 1f));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
@ -287,10 +297,25 @@ public class Toolbar extends LinearLayout implements View.OnClickListener, LoadV
}
private void setNavigationItemInternal(boolean animate, final boolean pushing, NavigationItem newItem) {
if (transitioning) {
throw new IllegalStateException("setNavigationItemInternal called when already transitioning");
}
attachNavigationItem(newItem);
transitioning = true;
if (fromItem == toItem) {
// Search toggled
navigationItemContainer.setListener(new LoadView.Listener() {
@Override
public void onLoadViewRemoved(View view) {
// Remove the menu from the navigation item
((ViewGroup) view).removeAllViews();
finishTransition(true);
navigationItemContainer.setListener(null);
}
});
navigationItemContainer.setView(toItem.view, animate);
animateArrow(toItem.hasBack || toItem.search);
@ -327,6 +352,10 @@ public class Toolbar extends LinearLayout implements View.OnClickListener, LoadV
}
private void attachNavigationItem(NavigationItem newItem) {
if (transitioning) {
throw new IllegalStateException("attachNavigationItem called while transitioning");
}
fromItem = toItem;
toItem = newItem;
@ -344,6 +373,10 @@ public class Toolbar extends LinearLayout implements View.OnClickListener, LoadV
}
private void removeNavigationItem(NavigationItem item) {
if (!transitioning) {
throw new IllegalStateException("removeNavigationItem called while not transitioning");
}
item.view.removeAllViews();
navigationItemContainer.removeView(item.view);
item.view = null;

Loading…
Cancel
Save