Add rotate & scale fling animation (#499)

This commit is contained in:
Gustl22 2018-02-08 14:00:09 +01:00 committed by Emux
parent 0e855cb47f
commit 3ed6c43161
No known key found for this signature in database
GPG Key ID: 89C6921D7AF2BDD0
3 changed files with 172 additions and 32 deletions

View File

@ -3,6 +3,7 @@
* Copyright 2016-2017 devemux86 * Copyright 2016-2017 devemux86
* Copyright 2016 Andrey Novikov * Copyright 2016 Andrey Novikov
* Copyright 2016 Longri * Copyright 2016 Longri
* Copyright 2018 Gustl22
* *
* This file is part of the OpenScienceMap project (http://www.opensciencemap.org). * This file is part of the OpenScienceMap project (http://www.opensciencemap.org).
* *
@ -70,6 +71,9 @@ public class MapEventLayer extends AbstractMapEventLayer implements InputListene
private float mPrevX2; private float mPrevX2;
private float mPrevY2; private float mPrevY2;
private float mPivotX;
private float mPivotY;
private double mAngle; private double mAngle;
private double mPrevPinchWidth; private double mPrevPinchWidth;
private long mStartMove; private long mStartMove;
@ -95,13 +99,17 @@ public class MapEventLayer extends AbstractMapEventLayer implements InputListene
*/ */
private static final float FLING_MIN_THREHSHOLD = 100; private static final float FLING_MIN_THREHSHOLD = 100;
private final VelocityTracker mTracker; private final VelocityTracker mScrollTracker;
private final VelocityTracker mScaleTracker;
private final VelocityTracker mRotateTracker;
private final MapPosition mapPosition = new MapPosition(); private final MapPosition mapPosition = new MapPosition();
public MapEventLayer(Map map) { public MapEventLayer(Map map) {
super(map); super(map);
mTracker = new VelocityTracker(); mScrollTracker = new VelocityTracker();
mScaleTracker = new VelocityTracker();
mRotateTracker = new VelocityTracker();
} }
@Override @Override
@ -198,9 +206,9 @@ public class MapEventLayer extends AbstractMapEventLayer implements InputListene
} else if (mStartMove > 0) { } else if (mStartMove > 0) {
/* handle fling gesture */ /* handle fling gesture */
mTracker.update(e.getX(), e.getY(), e.getTime()); mScrollTracker.update(e.getX(), e.getY(), e.getTime());
float vx = mTracker.getVelocityX(); float vx = mScrollTracker.getVelocityX();
float vy = mTracker.getVelocityY(); float vy = mScrollTracker.getVelocityY();
/* reduce velocity for short moves */ /* reduce velocity for short moves */
float t = e.getTime() - mStartMove; float t = e.getTime() - mStartMove;
@ -209,8 +217,22 @@ public class MapEventLayer extends AbstractMapEventLayer implements InputListene
vy *= t * t; vy *= t * t;
vx *= t * t; vx *= t * t;
} }
doFling(vx, vy); doFlingScroll(vx, vy);
} }
if (Parameters.ANIMATOR2) {
if (mRotateTracker.mNumSamples >= 0) {
mDoRotate = mCanRotate = false;
((Animator2) mMap.animator()).animateFlingRotate(mRotateTracker.getVelocityX(), mPivotX, mPivotY);
mRotateTracker.mNumSamples = -1; // Reset tracker
}
if (mScaleTracker.mNumSamples >= 0) {
mDoScale = mCanScale = false;
((Animator2) mMap.animator()).animateFlingZoom(mScaleTracker.getVelocityX(), mPivotX, mPivotY);
mScaleTracker.mNumSamples = -1; // Reset tracker
}
}
return true; return true;
} }
if (action == MotionEvent.ACTION_CANCEL) { if (action == MotionEvent.ACTION_CANCEL) {
@ -286,11 +308,11 @@ public class MapEventLayer extends AbstractMapEventLayer implements InputListene
} }
mStartMove = e.getTime(); mStartMove = e.getTime();
mTracker.start(x1, y1, mStartMove); mScrollTracker.start(x1, y1, mStartMove);
return; return;
} }
mViewport.moveMap(mx, my); mViewport.moveMap(mx, my);
mTracker.update(x1, y1, e.getTime()); mScrollTracker.update(x1, y1, e.getTime());
mMap.updateMap(true); mMap.updateMap(true);
if (mMap.viewport().getMapPosition(mapPosition)) if (mMap.viewport().getMapPosition(mapPosition))
mMap.events.fire(Map.MOVE_EVENT, mapPosition); mMap.events.fire(Map.MOVE_EVENT, mapPosition);
@ -342,6 +364,13 @@ public class MapEventLayer extends AbstractMapEventLayer implements InputListene
mAngle = rad; mAngle = rad;
deltaPinch = 0; deltaPinch = 0;
if (Parameters.ANIMATOR2) {
if (mRotateTracker.mNumSamples < 0)
mRotateTracker.start(mRotateTracker.mLastX + (float) da, 0, e.getTime());
else
mRotateTracker.update(mRotateTracker.mLastX + (float) da, 0, e.getTime());
}
} }
} else { } else {
r = Math.abs(r); r = Math.abs(r);
@ -394,25 +423,32 @@ public class MapEventLayer extends AbstractMapEventLayer implements InputListene
if (mDoScale || mDoRotate) { if (mDoScale || mDoRotate) {
scaleBy = (float) (pinchWidth / mPrevPinchWidth); scaleBy = (float) (pinchWidth / mPrevPinchWidth);
mPrevPinchWidth = pinchWidth; mPrevPinchWidth = pinchWidth;
if (Parameters.ANIMATOR2) {
if (mDoScale && scaleBy != 1f) {
if (mScaleTracker.mNumSamples < 0)
mScaleTracker.start((float) pinchWidth, 0, e.getTime());
else
mScaleTracker.update((float) pinchWidth, 0, e.getTime());
}
}
} }
} }
if (!(mDoRotate || mDoScale || mDoTilt)) if (!(mDoRotate || mDoScale || mDoTilt))
return; return;
float pivotX = 0, pivotY = 0;
if (!mFixOnCenter) { if (!mFixOnCenter) {
pivotX = (x2 + x1) / 2 - width / 2; mPivotX = (x2 + x1) / 2 - width / 2;
pivotY = (y2 + y1) / 2 - height / 2; mPivotY = (y2 + y1) / 2 - height / 2;
} }
synchronized (mViewport) { synchronized (mViewport) {
if (!mDoTilt) { if (!mDoTilt) {
if (rotateBy != 0) if (rotateBy != 0)
mViewport.rotateMap(rotateBy, pivotX, pivotY); mViewport.rotateMap(rotateBy, mPivotX, mPivotY);
if (scaleBy != 1) if (scaleBy != 1)
mViewport.scaleMap(scaleBy, pivotX, pivotY); mViewport.scaleMap(scaleBy, mPivotX, mPivotY);
if (!mFixOnCenter) if (!mFixOnCenter)
mViewport.moveMap(mx, my); mViewport.moveMap(mx, my);
@ -468,7 +504,7 @@ public class MapEventLayer extends AbstractMapEventLayer implements InputListene
return !withinSquaredDist(mx, my, minSlop * minSlop); return !withinSquaredDist(mx, my, minSlop * minSlop);
} }
private boolean doFling(float velocityX, float velocityY) { private boolean doFlingScroll(float velocityX, float velocityY) {
int w = Tile.SIZE * 5; int w = Tile.SIZE * 5;
int h = Tile.SIZE * 5; int h = Tile.SIZE * 5;
@ -493,7 +529,7 @@ public class MapEventLayer extends AbstractMapEventLayer implements InputListene
return false; return false;
} }
private static class VelocityTracker { private class VelocityTracker {
/* sample window, 200ms */ /* sample window, 200ms */
private static final int MAX_MS = 200; private static final int MAX_MS = 200;
private static final int SAMPLES = 32; private static final int SAMPLES = 32;
@ -563,5 +599,12 @@ public class MapEventLayer extends AbstractMapEventLayer implements InputListene
float getVelocityX() { float getVelocityX() {
return getVelocity(mMeanX); return getVelocity(mMeanX);
} }
@Override
public String toString() {
return "VelocityX: " + getVelocityX()
+ "\tVelocityY: " + getVelocityY()
+ "\tNumSamples: " + mNumSamples;
}
} }
} }

View File

@ -74,6 +74,9 @@ public class MapEventLayer2 extends AbstractMapEventLayer implements InputListen
private float mPrevX2; private float mPrevX2;
private float mPrevY2; private float mPrevY2;
private float mPivotX;
private float mPivotY;
private double mAngle; private double mAngle;
private double mPrevPinchWidth; private double mPrevPinchWidth;
private long mStartMove; private long mStartMove;
@ -107,14 +110,19 @@ public class MapEventLayer2 extends AbstractMapEventLayer implements InputListen
private static final long DOUBLE_TAP_THRESHOLD = 300; private static final long DOUBLE_TAP_THRESHOLD = 300;
private static final long LONG_PRESS_THRESHOLD = 500; private static final long LONG_PRESS_THRESHOLD = 500;
private final VelocityTracker mTracker; private final VelocityTracker mScrollTracker;
private final VelocityTracker mScaleTracker;
private final VelocityTracker mRotateTracker;
private Task mGestureTask; private Task mGestureTask;
private final MapPosition mapPosition = new MapPosition(); private final MapPosition mapPosition = new MapPosition();
public MapEventLayer2(Map map) { public MapEventLayer2(Map map) {
super(map); super(map);
mTracker = new VelocityTracker(); mScrollTracker = new VelocityTracker();
mScaleTracker = new VelocityTracker();
mRotateTracker = new VelocityTracker();
} }
@Override @Override
@ -231,9 +239,9 @@ public class MapEventLayer2 extends AbstractMapEventLayer implements InputListen
} }
if (mStartMove > 0) { if (mStartMove > 0) {
/* handle fling gesture */ /* handle fling gesture */
mTracker.update(e.getX(), e.getY(), e.getTime()); mScrollTracker.update(e.getX(), e.getY(), e.getTime());
float vx = mTracker.getVelocityX(); float vx = mScrollTracker.getVelocityX();
float vy = mTracker.getVelocityY(); float vy = mScrollTracker.getVelocityY();
/* reduce velocity for short moves */ /* reduce velocity for short moves */
float t = e.getTime() - mStartMove; float t = e.getTime() - mStartMove;
@ -243,7 +251,20 @@ public class MapEventLayer2 extends AbstractMapEventLayer implements InputListen
vx *= t * t; vx *= t * t;
} }
if (mEnableMove) if (mEnableMove)
doFling(vx, vy); doFlingScroll(vx, vy);
}
if (Parameters.ANIMATOR2) {
if (mRotateTracker.mNumSamples >= 0) {
mDoRotate = mCanRotate = false;
((Animator2) mMap.animator()).animateFlingRotate(mRotateTracker.getVelocityX(), mPivotX, mPivotY);
mRotateTracker.mNumSamples = -1; // Reset tracker
}
if (mScaleTracker.mNumSamples >= 0) {
mDoScale = mCanScale = false;
((Animator2) mMap.animator()).animateFlingZoom(mScaleTracker.getVelocityX(), mPivotX, mPivotY);
mScaleTracker.mNumSamples = -1; // Reset tracker
}
} }
if (time - mStartDown > LONG_PRESS_THRESHOLD) { if (time - mStartDown > LONG_PRESS_THRESHOLD) {
@ -378,11 +399,11 @@ public class MapEventLayer2 extends AbstractMapEventLayer implements InputListen
} }
mStartMove = e.getTime(); mStartMove = e.getTime();
mTracker.start(x1, y1, mStartMove); mScrollTracker.start(x1, y1, mStartMove);
return; return;
} }
mViewport.moveMap(mx, my); mViewport.moveMap(mx, my);
mTracker.update(x1, y1, e.getTime()); mScrollTracker.update(x1, y1, e.getTime());
mMap.updateMap(true); mMap.updateMap(true);
if (mMap.viewport().getMapPosition(mapPosition)) if (mMap.viewport().getMapPosition(mapPosition))
mMap.events.fire(Map.MOVE_EVENT, mapPosition); mMap.events.fire(Map.MOVE_EVENT, mapPosition);
@ -434,6 +455,13 @@ public class MapEventLayer2 extends AbstractMapEventLayer implements InputListen
mAngle = rad; mAngle = rad;
deltaPinch = 0; deltaPinch = 0;
if (Parameters.ANIMATOR2) {
if (mRotateTracker.mNumSamples < 0)
mRotateTracker.start(mRotateTracker.mLastX + (float) da, 0, e.getTime());
else
mRotateTracker.update(mRotateTracker.mLastX + (float) da, 0, e.getTime());
}
} }
} else { } else {
r = Math.abs(r); r = Math.abs(r);
@ -486,25 +514,32 @@ public class MapEventLayer2 extends AbstractMapEventLayer implements InputListen
if (mDoScale || mDoRotate) { if (mDoScale || mDoRotate) {
scaleBy = (float) (pinchWidth / mPrevPinchWidth); scaleBy = (float) (pinchWidth / mPrevPinchWidth);
mPrevPinchWidth = pinchWidth; mPrevPinchWidth = pinchWidth;
if (Parameters.ANIMATOR2) {
if (mDoScale && scaleBy != 1f) {
if (mScaleTracker.mNumSamples < 0)
mScaleTracker.start((float) pinchWidth, 0, e.getTime());
else
mScaleTracker.update((float) pinchWidth, 0, e.getTime());
}
}
} }
} }
if (!(mDoRotate || mDoScale || mDoTilt)) if (!(mDoRotate || mDoScale || mDoTilt))
return; return;
float pivotX = 0, pivotY = 0;
if (!mFixOnCenter) { if (!mFixOnCenter) {
pivotX = (x2 + x1) / 2 - width / 2; mPivotX = (x2 + x1) / 2 - width / 2;
pivotY = (y2 + y1) / 2 - height / 2; mPivotY = (y2 + y1) / 2 - height / 2;
} }
synchronized (mViewport) { synchronized (mViewport) {
if (!mDoTilt) { if (!mDoTilt) {
if (rotateBy != 0) if (rotateBy != 0)
mViewport.rotateMap(rotateBy, pivotX, pivotY); mViewport.rotateMap(rotateBy, mPivotX, mPivotY);
if (scaleBy != 1) if (scaleBy != 1)
mViewport.scaleMap(scaleBy, pivotX, pivotY); mViewport.scaleMap(scaleBy, mPivotX, mPivotY);
if (!mFixOnCenter) if (!mFixOnCenter)
mViewport.moveMap(mx, my); mViewport.moveMap(mx, my);
@ -567,7 +602,7 @@ public class MapEventLayer2 extends AbstractMapEventLayer implements InputListen
return !withinSquaredDist(mx, my, minSlop * minSlop); return !withinSquaredDist(mx, my, minSlop * minSlop);
} }
private boolean doFling(float velocityX, float velocityY) { private boolean doFlingScroll(float velocityX, float velocityY) {
int w = Tile.SIZE * 5; int w = Tile.SIZE * 5;
int h = Tile.SIZE * 5; int h = Tile.SIZE * 5;
@ -583,7 +618,7 @@ public class MapEventLayer2 extends AbstractMapEventLayer implements InputListen
return true; return true;
} }
private static class VelocityTracker { private class VelocityTracker {
/* sample window, 200ms */ /* sample window, 200ms */
private static final int MAX_MS = 200; private static final int MAX_MS = 200;
private static final int SAMPLES = 32; private static final int SAMPLES = 32;
@ -653,5 +688,12 @@ public class MapEventLayer2 extends AbstractMapEventLayer implements InputListen
float getVelocityX() { float getVelocityX() {
return getVelocity(mMeanX); return getVelocity(mMeanX);
} }
@Override
public String toString() {
return "VelocityX: " + getVelocityX()
+ "\tVelocityY: " + getVelocityY()
+ "\tNumSamples: " + mNumSamples;
}
} }
} }

View File

@ -41,9 +41,13 @@ public class Animator2 extends Animator {
/** /**
* The minimum changes that are pleasant for users. * The minimum changes that are pleasant for users.
*/ */
private static final float DEFAULT_MIN_VISIBLE_CHANGE_DEGREE = 0.001f;
private static final float DEFAULT_MIN_VISIBLE_CHANGE_PIXELS = 0.5f; private static final float DEFAULT_MIN_VISIBLE_CHANGE_PIXELS = 0.5f;
private static final float DEFAULT_MIN_VISIBLE_CHANGE_SCALE = 1f;
private static final float FLING_FRICTION_MOVE = 0.9f; private static final float FLING_FRICTION_MOVE = 0.9f;
private static final float FLING_FRICTION_ROTATE = 1.0f;
private static final float FLING_FRICTION_SCALE = 1.2f;
private final DragForce mFlingRotateForce = new DragForce(); private final DragForce mFlingRotateForce = new DragForce();
private final DragForce mFlingScaleForce = new DragForce(); private final DragForce mFlingScaleForce = new DragForce();
@ -60,6 +64,29 @@ public class Animator2 extends Animator {
} }
/** /**
* Animates a physical fling for rotations.
*/
public void animateFlingRotate(float angularVelocity, float pivotX, float pivotY) {
ThreadUtils.assertMainThread();
mMap.getMapPosition(mStartPos);
mPivot.x = pivotX;
mPivot.y = pivotY;
float flingFactor = -0.4f; // Can be changed but should be standardized for all callers
angularVelocity *= flingFactor;
mFlingRotateForce.setValueThreshold(DEFAULT_MIN_VISIBLE_CHANGE_DEGREE);
mFlingRotateForce.setFrictionScalar(FLING_FRICTION_ROTATE);
mFlingRotateForce.setValueAndVelocity(0f, angularVelocity);
animFlingStart(ANIM_ROTATE);
}
/**
* Animates a physical fling for scrolls.
*
* @param velocityX the x velocity depends on screen resolution * @param velocityX the x velocity depends on screen resolution
* @param velocityY the y velocity depends on screen resolution * @param velocityY the y velocity depends on screen resolution
*/ */
@ -92,6 +119,30 @@ public class Animator2 extends Animator {
animFlingStart(ANIM_MOVE); animFlingStart(ANIM_MOVE);
} }
/**
* Animates a physical fling for zooms.
*
* @param scaleVelocity the scale velocity depends on screen resolution
*/
public void animateFlingZoom(float scaleVelocity, float pivotX, float pivotY) {
ThreadUtils.assertMainThread();
mMap.getMapPosition(mStartPos);
mPivot.x = pivotX;
mPivot.y = pivotY;
float flingFactor = -1.0f; // Can be changed but should be standardized for all callers
float screenFactor = CanvasAdapter.DEFAULT_DPI / CanvasAdapter.dpi;
scaleVelocity *= flingFactor * screenFactor;
mFlingScaleForce.setValueThreshold(DEFAULT_MIN_VISIBLE_CHANGE_SCALE);
mFlingScaleForce.setFrictionScalar(FLING_FRICTION_SCALE);
mFlingScaleForce.setValueAndVelocity(0f, scaleVelocity);
animFlingStart(ANIM_SCALE);
}
private void animFlingStart(int state) { private void animFlingStart(int state) {
if (!isActive()) if (!isActive())
mMap.events.fire(Map.ANIM_START, mMap.mMapPosition); mMap.events.fire(Map.ANIM_START, mMap.mMapPosition);
@ -102,6 +153,9 @@ public class Animator2 extends Animator {
} }
/** /**
* Alternative implementation of Animator's <code>animateFling</code>.
* Uses scheme of predictable animations using mDeltaPos.
*
* @param velocityX the x velocity depends on screen resolution * @param velocityX the x velocity depends on screen resolution
* @param velocityY the y velocity depends on screen resolution * @param velocityY the y velocity depends on screen resolution
*/ */
@ -171,6 +225,7 @@ public class Animator2 extends Animator {
} }
if ((mState & ANIM_KINETIC) != 0) { if ((mState & ANIM_KINETIC) != 0) {
// Reduce value to simulate kinetic behaviour
adv = (float) Math.sqrt(adv); adv = (float) Math.sqrt(adv);
} }