Add rotate & scale fling animation (#499)
This commit is contained in:
parent
0e855cb47f
commit
3ed6c43161
@ -3,6 +3,7 @@
|
||||
* Copyright 2016-2017 devemux86
|
||||
* Copyright 2016 Andrey Novikov
|
||||
* Copyright 2016 Longri
|
||||
* Copyright 2018 Gustl22
|
||||
*
|
||||
* 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 mPrevY2;
|
||||
|
||||
private float mPivotX;
|
||||
private float mPivotY;
|
||||
|
||||
private double mAngle;
|
||||
private double mPrevPinchWidth;
|
||||
private long mStartMove;
|
||||
@ -95,13 +99,17 @@ public class MapEventLayer extends AbstractMapEventLayer implements InputListene
|
||||
*/
|
||||
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();
|
||||
|
||||
public MapEventLayer(Map map) {
|
||||
super(map);
|
||||
mTracker = new VelocityTracker();
|
||||
mScrollTracker = new VelocityTracker();
|
||||
mScaleTracker = new VelocityTracker();
|
||||
mRotateTracker = new VelocityTracker();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -198,9 +206,9 @@ public class MapEventLayer extends AbstractMapEventLayer implements InputListene
|
||||
|
||||
} else if (mStartMove > 0) {
|
||||
/* handle fling gesture */
|
||||
mTracker.update(e.getX(), e.getY(), e.getTime());
|
||||
float vx = mTracker.getVelocityX();
|
||||
float vy = mTracker.getVelocityY();
|
||||
mScrollTracker.update(e.getX(), e.getY(), e.getTime());
|
||||
float vx = mScrollTracker.getVelocityX();
|
||||
float vy = mScrollTracker.getVelocityY();
|
||||
|
||||
/* reduce velocity for short moves */
|
||||
float t = e.getTime() - mStartMove;
|
||||
@ -209,8 +217,22 @@ public class MapEventLayer extends AbstractMapEventLayer implements InputListene
|
||||
vy *= 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;
|
||||
}
|
||||
if (action == MotionEvent.ACTION_CANCEL) {
|
||||
@ -286,11 +308,11 @@ public class MapEventLayer extends AbstractMapEventLayer implements InputListene
|
||||
}
|
||||
|
||||
mStartMove = e.getTime();
|
||||
mTracker.start(x1, y1, mStartMove);
|
||||
mScrollTracker.start(x1, y1, mStartMove);
|
||||
return;
|
||||
}
|
||||
mViewport.moveMap(mx, my);
|
||||
mTracker.update(x1, y1, e.getTime());
|
||||
mScrollTracker.update(x1, y1, e.getTime());
|
||||
mMap.updateMap(true);
|
||||
if (mMap.viewport().getMapPosition(mapPosition))
|
||||
mMap.events.fire(Map.MOVE_EVENT, mapPosition);
|
||||
@ -342,6 +364,13 @@ public class MapEventLayer extends AbstractMapEventLayer implements InputListene
|
||||
mAngle = rad;
|
||||
|
||||
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 {
|
||||
r = Math.abs(r);
|
||||
@ -394,25 +423,32 @@ public class MapEventLayer extends AbstractMapEventLayer implements InputListene
|
||||
if (mDoScale || mDoRotate) {
|
||||
scaleBy = (float) (pinchWidth / mPrevPinchWidth);
|
||||
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))
|
||||
return;
|
||||
|
||||
float pivotX = 0, pivotY = 0;
|
||||
|
||||
if (!mFixOnCenter) {
|
||||
pivotX = (x2 + x1) / 2 - width / 2;
|
||||
pivotY = (y2 + y1) / 2 - height / 2;
|
||||
mPivotX = (x2 + x1) / 2 - width / 2;
|
||||
mPivotY = (y2 + y1) / 2 - height / 2;
|
||||
}
|
||||
|
||||
synchronized (mViewport) {
|
||||
if (!mDoTilt) {
|
||||
if (rotateBy != 0)
|
||||
mViewport.rotateMap(rotateBy, pivotX, pivotY);
|
||||
mViewport.rotateMap(rotateBy, mPivotX, mPivotY);
|
||||
if (scaleBy != 1)
|
||||
mViewport.scaleMap(scaleBy, pivotX, pivotY);
|
||||
mViewport.scaleMap(scaleBy, mPivotX, mPivotY);
|
||||
|
||||
if (!mFixOnCenter)
|
||||
mViewport.moveMap(mx, my);
|
||||
@ -468,7 +504,7 @@ public class MapEventLayer extends AbstractMapEventLayer implements InputListene
|
||||
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 h = Tile.SIZE * 5;
|
||||
@ -493,7 +529,7 @@ public class MapEventLayer extends AbstractMapEventLayer implements InputListene
|
||||
return false;
|
||||
}
|
||||
|
||||
private static class VelocityTracker {
|
||||
private class VelocityTracker {
|
||||
/* sample window, 200ms */
|
||||
private static final int MAX_MS = 200;
|
||||
private static final int SAMPLES = 32;
|
||||
@ -563,5 +599,12 @@ public class MapEventLayer extends AbstractMapEventLayer implements InputListene
|
||||
float getVelocityX() {
|
||||
return getVelocity(mMeanX);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "VelocityX: " + getVelocityX()
|
||||
+ "\tVelocityY: " + getVelocityY()
|
||||
+ "\tNumSamples: " + mNumSamples;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -74,6 +74,9 @@ public class MapEventLayer2 extends AbstractMapEventLayer implements InputListen
|
||||
private float mPrevX2;
|
||||
private float mPrevY2;
|
||||
|
||||
private float mPivotX;
|
||||
private float mPivotY;
|
||||
|
||||
private double mAngle;
|
||||
private double mPrevPinchWidth;
|
||||
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 LONG_PRESS_THRESHOLD = 500;
|
||||
|
||||
private final VelocityTracker mTracker;
|
||||
private final VelocityTracker mScrollTracker;
|
||||
private final VelocityTracker mScaleTracker;
|
||||
private final VelocityTracker mRotateTracker;
|
||||
|
||||
private Task mGestureTask;
|
||||
|
||||
private final MapPosition mapPosition = new MapPosition();
|
||||
|
||||
public MapEventLayer2(Map map) {
|
||||
super(map);
|
||||
mTracker = new VelocityTracker();
|
||||
mScrollTracker = new VelocityTracker();
|
||||
mScaleTracker = new VelocityTracker();
|
||||
mRotateTracker = new VelocityTracker();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -231,9 +239,9 @@ public class MapEventLayer2 extends AbstractMapEventLayer implements InputListen
|
||||
}
|
||||
if (mStartMove > 0) {
|
||||
/* handle fling gesture */
|
||||
mTracker.update(e.getX(), e.getY(), e.getTime());
|
||||
float vx = mTracker.getVelocityX();
|
||||
float vy = mTracker.getVelocityY();
|
||||
mScrollTracker.update(e.getX(), e.getY(), e.getTime());
|
||||
float vx = mScrollTracker.getVelocityX();
|
||||
float vy = mScrollTracker.getVelocityY();
|
||||
|
||||
/* reduce velocity for short moves */
|
||||
float t = e.getTime() - mStartMove;
|
||||
@ -243,7 +251,20 @@ public class MapEventLayer2 extends AbstractMapEventLayer implements InputListen
|
||||
vx *= t * t;
|
||||
}
|
||||
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) {
|
||||
@ -378,11 +399,11 @@ public class MapEventLayer2 extends AbstractMapEventLayer implements InputListen
|
||||
}
|
||||
|
||||
mStartMove = e.getTime();
|
||||
mTracker.start(x1, y1, mStartMove);
|
||||
mScrollTracker.start(x1, y1, mStartMove);
|
||||
return;
|
||||
}
|
||||
mViewport.moveMap(mx, my);
|
||||
mTracker.update(x1, y1, e.getTime());
|
||||
mScrollTracker.update(x1, y1, e.getTime());
|
||||
mMap.updateMap(true);
|
||||
if (mMap.viewport().getMapPosition(mapPosition))
|
||||
mMap.events.fire(Map.MOVE_EVENT, mapPosition);
|
||||
@ -434,6 +455,13 @@ public class MapEventLayer2 extends AbstractMapEventLayer implements InputListen
|
||||
mAngle = rad;
|
||||
|
||||
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 {
|
||||
r = Math.abs(r);
|
||||
@ -486,25 +514,32 @@ public class MapEventLayer2 extends AbstractMapEventLayer implements InputListen
|
||||
if (mDoScale || mDoRotate) {
|
||||
scaleBy = (float) (pinchWidth / mPrevPinchWidth);
|
||||
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))
|
||||
return;
|
||||
|
||||
float pivotX = 0, pivotY = 0;
|
||||
|
||||
if (!mFixOnCenter) {
|
||||
pivotX = (x2 + x1) / 2 - width / 2;
|
||||
pivotY = (y2 + y1) / 2 - height / 2;
|
||||
mPivotX = (x2 + x1) / 2 - width / 2;
|
||||
mPivotY = (y2 + y1) / 2 - height / 2;
|
||||
}
|
||||
|
||||
synchronized (mViewport) {
|
||||
if (!mDoTilt) {
|
||||
if (rotateBy != 0)
|
||||
mViewport.rotateMap(rotateBy, pivotX, pivotY);
|
||||
mViewport.rotateMap(rotateBy, mPivotX, mPivotY);
|
||||
if (scaleBy != 1)
|
||||
mViewport.scaleMap(scaleBy, pivotX, pivotY);
|
||||
mViewport.scaleMap(scaleBy, mPivotX, mPivotY);
|
||||
|
||||
if (!mFixOnCenter)
|
||||
mViewport.moveMap(mx, my);
|
||||
@ -567,7 +602,7 @@ public class MapEventLayer2 extends AbstractMapEventLayer implements InputListen
|
||||
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 h = Tile.SIZE * 5;
|
||||
@ -583,7 +618,7 @@ public class MapEventLayer2 extends AbstractMapEventLayer implements InputListen
|
||||
return true;
|
||||
}
|
||||
|
||||
private static class VelocityTracker {
|
||||
private class VelocityTracker {
|
||||
/* sample window, 200ms */
|
||||
private static final int MAX_MS = 200;
|
||||
private static final int SAMPLES = 32;
|
||||
@ -653,5 +688,12 @@ public class MapEventLayer2 extends AbstractMapEventLayer implements InputListen
|
||||
float getVelocityX() {
|
||||
return getVelocity(mMeanX);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "VelocityX: " + getVelocityX()
|
||||
+ "\tVelocityY: " + getVelocityY()
|
||||
+ "\tNumSamples: " + mNumSamples;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -41,9 +41,13 @@ public class Animator2 extends Animator {
|
||||
/**
|
||||
* 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_SCALE = 1f;
|
||||
|
||||
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 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 velocityY the y velocity depends on screen resolution
|
||||
*/
|
||||
@ -92,6 +119,30 @@ public class Animator2 extends Animator {
|
||||
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) {
|
||||
if (!isActive())
|
||||
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 velocityY the y velocity depends on screen resolution
|
||||
*/
|
||||
@ -171,6 +225,7 @@ public class Animator2 extends Animator {
|
||||
}
|
||||
|
||||
if ((mState & ANIM_KINETIC) != 0) {
|
||||
// Reduce value to simulate kinetic behaviour
|
||||
adv = (float) Math.sqrt(adv);
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user