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 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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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);
}