Animator2: physical fling (#497)
This commit is contained in:
parent
241f112b58
commit
03eb28f09e
@ -21,7 +21,9 @@ import com.badlogic.gdx.input.GestureDetector.GestureListener;
|
||||
import com.badlogic.gdx.math.Vector2;
|
||||
|
||||
import org.oscim.core.Tile;
|
||||
import org.oscim.map.Animator2;
|
||||
import org.oscim.map.Map;
|
||||
import org.oscim.utils.Parameters;
|
||||
|
||||
public class GestureHandler implements GestureListener {
|
||||
private boolean mayFling = true;
|
||||
@ -84,7 +86,10 @@ public class GestureHandler implements GestureListener {
|
||||
//log.debug("fling " + button + " " + velocityX + "/" + velocityY);
|
||||
if (mayFling && button == Buttons.LEFT) {
|
||||
int m = Tile.SIZE * 4;
|
||||
mMap.animator().animateFling((int) velocityX, (int) velocityY, -m, m, -m, m);
|
||||
if (Parameters.ANIMATOR2)
|
||||
((Animator2) mMap.animator()).animateFlingScroll(velocityX, velocityY, -m, m, -m, m);
|
||||
else
|
||||
mMap.animator().animateFling(velocityX, velocityY, -m, m, -m, m);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
@ -31,7 +31,7 @@ import org.oscim.layers.tile.buildings.BuildingLayer;
|
||||
import org.oscim.map.Map;
|
||||
import org.oscim.map.ViewController;
|
||||
import org.oscim.theme.VtmThemes;
|
||||
import org.oscim.utils.Easing;
|
||||
import org.oscim.utils.animation.Easing;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
@ -19,15 +19,19 @@
|
||||
*/
|
||||
package org.oscim.layers;
|
||||
|
||||
import org.oscim.backend.CanvasAdapter;
|
||||
import org.oscim.backend.Platform;
|
||||
import org.oscim.core.MapPosition;
|
||||
import org.oscim.core.Tile;
|
||||
import org.oscim.event.Event;
|
||||
import org.oscim.event.Gesture;
|
||||
import org.oscim.event.GestureListener;
|
||||
import org.oscim.event.MotionEvent;
|
||||
import org.oscim.map.Animator2;
|
||||
import org.oscim.map.Map;
|
||||
import org.oscim.map.Map.InputListener;
|
||||
import org.oscim.map.ViewController;
|
||||
import org.oscim.utils.Parameters;
|
||||
|
||||
import static org.oscim.backend.CanvasAdapter.dpi;
|
||||
import static org.oscim.utils.FastMath.withinSquaredDist;
|
||||
@ -469,8 +473,14 @@ public class MapEventLayer extends AbstractMapEventLayer implements InputListene
|
||||
int w = Tile.SIZE * 5;
|
||||
int h = Tile.SIZE * 5;
|
||||
|
||||
mMap.animator().animateFling(velocityX * 2, velocityY * 2,
|
||||
-w, w, -h, h);
|
||||
if (Parameters.ANIMATOR2) {
|
||||
if (!CanvasAdapter.platform.isDesktop() && CanvasAdapter.platform != Platform.WEBGL) {
|
||||
velocityX *= 2;
|
||||
velocityY *= 2;
|
||||
}
|
||||
((Animator2) mMap.animator()).animateFlingScroll(velocityX, velocityY, -w, w, -h, h);
|
||||
} else
|
||||
mMap.animator().animateFling(velocityX * 2, velocityY * 2, -w, w, -h, h);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -21,14 +21,17 @@
|
||||
package org.oscim.layers;
|
||||
|
||||
import org.oscim.backend.CanvasAdapter;
|
||||
import org.oscim.backend.Platform;
|
||||
import org.oscim.core.MapPosition;
|
||||
import org.oscim.core.Tile;
|
||||
import org.oscim.event.Event;
|
||||
import org.oscim.event.Gesture;
|
||||
import org.oscim.event.MotionEvent;
|
||||
import org.oscim.map.Animator2;
|
||||
import org.oscim.map.Map;
|
||||
import org.oscim.map.Map.InputListener;
|
||||
import org.oscim.map.ViewController;
|
||||
import org.oscim.utils.Parameters;
|
||||
import org.oscim.utils.async.Task;
|
||||
|
||||
import static org.oscim.backend.CanvasAdapter.dpi;
|
||||
@ -569,8 +572,14 @@ public class MapEventLayer2 extends AbstractMapEventLayer implements InputListen
|
||||
int w = Tile.SIZE * 5;
|
||||
int h = Tile.SIZE * 5;
|
||||
|
||||
mMap.animator().animateFling(velocityX * 2, velocityY * 2,
|
||||
-w, w, -h, h);
|
||||
if (Parameters.ANIMATOR2) {
|
||||
if (!CanvasAdapter.platform.isDesktop() && CanvasAdapter.platform != Platform.WEBGL) {
|
||||
velocityX *= 2;
|
||||
velocityY *= 2;
|
||||
}
|
||||
((Animator2) mMap.animator()).animateFlingScroll(velocityX, velocityY, -w, w, -h, h);
|
||||
} else
|
||||
mMap.animator().animateFling(velocityX * 2, velocityY * 2, -w, w, -h, h);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -28,8 +28,8 @@ import org.oscim.core.MapPosition;
|
||||
import org.oscim.core.Point;
|
||||
import org.oscim.core.Tile;
|
||||
import org.oscim.renderer.MapRenderer;
|
||||
import org.oscim.utils.Easing;
|
||||
import org.oscim.utils.ThreadUtils;
|
||||
import org.oscim.utils.animation.Easing;
|
||||
import org.oscim.utils.async.Task;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@ -48,21 +48,21 @@ public class Animator {
|
||||
public final static int ANIM_TILT = 1 << 3;
|
||||
public final static int ANIM_FLING = 1 << 4;
|
||||
|
||||
private final Map mMap;
|
||||
final Map mMap;
|
||||
|
||||
private final MapPosition mCurPos = new MapPosition();
|
||||
private final MapPosition mStartPos = new MapPosition();
|
||||
private final MapPosition mDeltaPos = new MapPosition();
|
||||
final MapPosition mCurPos = new MapPosition();
|
||||
final MapPosition mStartPos = new MapPosition();
|
||||
final MapPosition mDeltaPos = new MapPosition();
|
||||
|
||||
private final Point mScroll = new Point();
|
||||
private final Point mPivot = new Point();
|
||||
final Point mPivot = new Point();
|
||||
private final Point mVelocity = new Point();
|
||||
|
||||
private float mDuration = 500;
|
||||
private long mAnimEnd = -1;
|
||||
private Easing.Type mEasingType = Easing.Type.LINEAR;
|
||||
float mDuration = 500;
|
||||
long mAnimEnd = -1;
|
||||
Easing.Type mEasingType = Easing.Type.LINEAR;
|
||||
|
||||
private int mState = ANIM_NONE;
|
||||
int mState = ANIM_NONE;
|
||||
|
||||
public Animator(Map map) {
|
||||
mMap = map;
|
||||
@ -251,7 +251,7 @@ public class Animator {
|
||||
animStart(duration, ANIM_FLING, Easing.Type.SINE_OUT);
|
||||
}
|
||||
|
||||
private void animStart(float duration, int state, Easing.Type easingType) {
|
||||
void animStart(float duration, int state, Easing.Type easingType) {
|
||||
if (!isActive())
|
||||
mMap.events.fire(Map.ANIM_START, mMap.mMapPosition);
|
||||
mCurPos.copy(mStartPos);
|
||||
@ -332,7 +332,7 @@ public class Animator {
|
||||
}
|
||||
}
|
||||
|
||||
private Task updateTask = new Task() {
|
||||
Task updateTask = new Task() {
|
||||
@Override
|
||||
public int go(boolean canceled) {
|
||||
if (!canceled)
|
||||
@ -341,7 +341,7 @@ public class Animator {
|
||||
}
|
||||
};
|
||||
|
||||
private double doScale(ViewController v, float adv) {
|
||||
double doScale(ViewController v, float adv) {
|
||||
double newScale = mStartPos.scale + mDeltaPos.scale * Math.sqrt(adv);
|
||||
|
||||
v.scaleMap((float) (newScale / mCurPos.scale),
|
||||
|
254
vtm/src/org/oscim/map/Animator2.java
Normal file
254
vtm/src/org/oscim/map/Animator2.java
Normal file
@ -0,0 +1,254 @@
|
||||
/*
|
||||
* Copyright 2013 Hannes Janetzek
|
||||
* Copyright 2016 Stephan Leuschner
|
||||
* Copyright 2016 devemux86
|
||||
* Copyright 2016 Izumi Kawashima
|
||||
* Copyright 2017 Wolfgang Schramm
|
||||
* Copyright 2018 Gustl22
|
||||
*
|
||||
* This file is part of the OpenScienceMap project (http://www.opensciencemap.org).
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under the
|
||||
* terms of the GNU Lesser General Public License as published by the Free Software
|
||||
* Foundation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.oscim.map;
|
||||
|
||||
import org.oscim.backend.CanvasAdapter;
|
||||
import org.oscim.core.Point;
|
||||
import org.oscim.core.Tile;
|
||||
import org.oscim.renderer.MapRenderer;
|
||||
import org.oscim.utils.ThreadUtils;
|
||||
import org.oscim.utils.animation.DragForce;
|
||||
import org.oscim.utils.animation.Easing;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import static org.oscim.utils.FastMath.clamp;
|
||||
|
||||
public class Animator2 extends Animator {
|
||||
private static final Logger log = LoggerFactory.getLogger(Animator2.class);
|
||||
|
||||
private final static int ANIM_KINETIC = 1 << 5;
|
||||
|
||||
/**
|
||||
* The minimum changes that are pleasant for users.
|
||||
*/
|
||||
private static final float DEFAULT_MIN_VISIBLE_CHANGE_PIXELS = 0.5f;
|
||||
|
||||
private static final float FLING_FRICTION_MOVE = 0.9f;
|
||||
|
||||
private final DragForce mFlingRotateForce = new DragForce();
|
||||
private final DragForce mFlingScaleForce = new DragForce();
|
||||
private final DragForce mFlingScrollForce = new DragForce();
|
||||
|
||||
private final Point mMovePoint = new Point();
|
||||
private final Point mScrollRatio = new Point();
|
||||
|
||||
private long mFrameStart = -1;
|
||||
private float mScrollDet2D = 1f;
|
||||
|
||||
public Animator2(Map map) {
|
||||
super(map);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param velocityX the x velocity depends on screen resolution
|
||||
* @param velocityY the y velocity depends on screen resolution
|
||||
*/
|
||||
public void animateFlingScroll(float velocityX, float velocityY,
|
||||
int xmin, int xmax, int ymin, int ymax) {
|
||||
ThreadUtils.assertMainThread();
|
||||
|
||||
if (velocityX * velocityX + velocityY * velocityY < 2048)
|
||||
return;
|
||||
|
||||
mMap.getMapPosition(mStartPos);
|
||||
|
||||
float flingFactor = 2.0f; // Can be changed but should be standardized for all callers
|
||||
float screenFactor = CanvasAdapter.DEFAULT_DPI / CanvasAdapter.dpi;
|
||||
|
||||
velocityX *= screenFactor * flingFactor;
|
||||
velocityY *= screenFactor * flingFactor;
|
||||
velocityX = clamp(velocityX, xmin, xmax);
|
||||
velocityY = clamp(velocityY, ymin, ymax);
|
||||
|
||||
float sumVelocity = Math.abs(velocityX) + Math.abs(velocityY);
|
||||
mScrollRatio.x = velocityX / sumVelocity;
|
||||
mScrollRatio.y = velocityY / sumVelocity;
|
||||
mScrollDet2D = (float) (mScrollRatio.x * mScrollRatio.x + mScrollRatio.y * mScrollRatio.y);
|
||||
|
||||
mFlingScrollForce.setValueThreshold(DEFAULT_MIN_VISIBLE_CHANGE_PIXELS);
|
||||
mFlingScrollForce.setFrictionScalar(FLING_FRICTION_MOVE);
|
||||
mFlingScrollForce.setValueAndVelocity(0f, (float) Math.sqrt(velocityX * velocityX + velocityY * velocityY));
|
||||
|
||||
animFlingStart(ANIM_MOVE);
|
||||
}
|
||||
|
||||
private void animFlingStart(int state) {
|
||||
if (!isActive())
|
||||
mMap.events.fire(Map.ANIM_START, mMap.mMapPosition);
|
||||
mCurPos.copy(mStartPos);
|
||||
mState |= ANIM_FLING | state;
|
||||
mFrameStart = MapRenderer.frametime; // CurrentTimeMillis would cause negative delta
|
||||
mMap.render();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param velocityX the x velocity depends on screen resolution
|
||||
* @param velocityY the y velocity depends on screen resolution
|
||||
*/
|
||||
public void kineticScroll(float velocityX, float velocityY,
|
||||
int xmin, int xmax, int ymin, int ymax) {
|
||||
ThreadUtils.assertMainThread();
|
||||
|
||||
if (velocityX * velocityX + velocityY * velocityY < 2048)
|
||||
return;
|
||||
|
||||
mMap.getMapPosition(mStartPos);
|
||||
|
||||
float duration = 500;
|
||||
|
||||
float screenFactor = CanvasAdapter.DEFAULT_DPI / CanvasAdapter.dpi;
|
||||
velocityX = velocityX * screenFactor;
|
||||
velocityY = velocityY * screenFactor;
|
||||
velocityX = clamp(velocityX, xmin, xmax);
|
||||
velocityY = clamp(velocityY, ymin, ymax);
|
||||
if (Float.isNaN(velocityX) || Float.isNaN(velocityY)) {
|
||||
log.debug("fling NaN!");
|
||||
return;
|
||||
}
|
||||
|
||||
double tileScale = mStartPos.scale * Tile.SIZE;
|
||||
ViewController.applyRotation(-velocityX, -velocityY, mStartPos.bearing, mMovePoint);
|
||||
mDeltaPos.setX(mMovePoint.x / tileScale);
|
||||
mDeltaPos.setY(mMovePoint.y / tileScale);
|
||||
|
||||
animStart(duration, ANIM_KINETIC | ANIM_MOVE, Easing.Type.SINE_OUT);
|
||||
}
|
||||
|
||||
/**
|
||||
* called by MapRenderer at begin of each frame.
|
||||
*/
|
||||
@Override
|
||||
void updateAnimation() {
|
||||
if (mState == ANIM_NONE)
|
||||
return;
|
||||
|
||||
ViewController v = mMap.viewport();
|
||||
|
||||
/* cancel animation when position was changed since last
|
||||
* update, i.e. when it was modified outside the animator. */
|
||||
if (v.getMapPosition(mCurPos)) {
|
||||
log.debug("cancel anim - changed");
|
||||
cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
final long currentFrametime = MapRenderer.frametime;
|
||||
|
||||
if ((mState & ANIM_FLING) == 0) {
|
||||
// Do predicted animations
|
||||
long millisLeft = mAnimEnd - currentFrametime;
|
||||
|
||||
float adv = clamp(1.0f - millisLeft / mDuration, 1E-6f, 1);
|
||||
// Avoid redundant calculations in case of linear easing
|
||||
if (mEasingType != Easing.Type.LINEAR) {
|
||||
adv = Easing.ease(0, (long) (adv * Long.MAX_VALUE), Long.MAX_VALUE, mEasingType);
|
||||
adv = clamp(adv, 0, 1);
|
||||
}
|
||||
|
||||
double scaleAdv = 1;
|
||||
if ((mState & ANIM_SCALE) != 0) {
|
||||
scaleAdv = doScale(v, adv);
|
||||
}
|
||||
|
||||
if ((mState & ANIM_KINETIC) != 0) {
|
||||
adv = (float) Math.sqrt(adv);
|
||||
}
|
||||
|
||||
if ((mState & ANIM_MOVE) != 0) {
|
||||
v.moveTo(mStartPos.x + mDeltaPos.x * (adv / scaleAdv),
|
||||
mStartPos.y + mDeltaPos.y * (adv / scaleAdv));
|
||||
}
|
||||
|
||||
if ((mState & ANIM_ROTATE) != 0) {
|
||||
v.setRotation(mStartPos.bearing + mDeltaPos.bearing * adv);
|
||||
}
|
||||
|
||||
if ((mState & ANIM_TILT) != 0) {
|
||||
v.setTilt(mStartPos.tilt + mDeltaPos.tilt * adv);
|
||||
}
|
||||
|
||||
if (millisLeft <= 0) {
|
||||
//log.debug("animate END");
|
||||
cancel();
|
||||
}
|
||||
} else {
|
||||
// Do physical fling animation
|
||||
long deltaT = currentFrametime - mFrameStart;
|
||||
mFrameStart = currentFrametime;
|
||||
boolean isAnimationFinished = true;
|
||||
|
||||
if ((mState & ANIM_SCALE) != 0) {
|
||||
float valueDelta = mFlingScaleForce.updateValueAndVelocity(deltaT) / 1000f;
|
||||
float velocity = mFlingScaleForce.getVelocity();
|
||||
if (valueDelta != 0) {
|
||||
valueDelta = valueDelta > 0 ? valueDelta + 1 : -1 / (valueDelta - 1);
|
||||
v.scaleMap(valueDelta, (float) mPivot.x, (float) mPivot.y);
|
||||
}
|
||||
isAnimationFinished = (velocity == 0);
|
||||
}
|
||||
|
||||
if ((mState & ANIM_MOVE) != 0) {
|
||||
float valueDelta = mFlingScrollForce.updateValueAndVelocity(deltaT);
|
||||
float velocity = mFlingScrollForce.getVelocity();
|
||||
|
||||
float valFactor = (float) Math.sqrt((valueDelta * valueDelta) / mScrollDet2D);
|
||||
float dx = (float) mScrollRatio.x * valFactor;
|
||||
float dy = (float) mScrollRatio.y * valFactor;
|
||||
|
||||
if (dx != 0 || dy != 0) {
|
||||
v.moveMap(dx, dy);
|
||||
}
|
||||
|
||||
isAnimationFinished = isAnimationFinished && (velocity == 0);
|
||||
}
|
||||
|
||||
if ((mState & ANIM_ROTATE) != 0) {
|
||||
float valueDelta = mFlingRotateForce.updateValueAndVelocity(deltaT);
|
||||
float velocity = mFlingRotateForce.getVelocity();
|
||||
|
||||
v.rotateMap(valueDelta, (float) mPivot.x, (float) mPivot.y);
|
||||
|
||||
isAnimationFinished = isAnimationFinished && (velocity == 0);
|
||||
}
|
||||
|
||||
/*if ((mState & ANIM_TILT) != 0) {
|
||||
// Do some tilt fling
|
||||
isAnimationFinished = isAnimationFinished && (velocity == 0);
|
||||
}*/
|
||||
|
||||
if (isAnimationFinished) {
|
||||
//log.debug("animate END");
|
||||
cancel();
|
||||
}
|
||||
}
|
||||
|
||||
/* remember current map position */
|
||||
final boolean changed = v.getMapPosition(mCurPos);
|
||||
|
||||
if (changed) {
|
||||
mMap.updateMap(true);
|
||||
} else {
|
||||
mMap.postDelayed(updateTask, 10);
|
||||
}
|
||||
}
|
||||
}
|
@ -132,7 +132,10 @@ public abstract class Map implements TaskQueue {
|
||||
ThreadUtils.init();
|
||||
|
||||
mViewport = new ViewController();
|
||||
mAnimator = new Animator(this);
|
||||
if (Parameters.ANIMATOR2)
|
||||
mAnimator = new Animator2(this);
|
||||
else
|
||||
mAnimator = new Animator(this);
|
||||
mLayers = new Layers(this);
|
||||
|
||||
input = new EventDispatcher<InputListener, MotionEvent>() {
|
||||
|
@ -86,12 +86,12 @@ public class ViewController extends Viewport {
|
||||
* @param mx the amount of pixels to move the map horizontally.
|
||||
* @param my the amount of pixels to move the map vertically.
|
||||
*/
|
||||
public void moveMap(float mx, float my) {
|
||||
public synchronized void moveMap(float mx, float my) {
|
||||
ThreadUtils.assertMainThread();
|
||||
|
||||
Point p = applyRotation(mx, my);
|
||||
applyRotation(mx, my, mPos.bearing, mMovePoint);
|
||||
double tileScale = mPos.scale * Tile.SIZE;
|
||||
moveTo(mPos.x - p.x / tileScale, mPos.y - p.y / tileScale);
|
||||
moveTo(mPos.x - mMovePoint.x / tileScale, mPos.y - mMovePoint.y / tileScale);
|
||||
}
|
||||
|
||||
/* used by MapAnimator */
|
||||
@ -120,18 +120,26 @@ public class ViewController extends Viewport {
|
||||
mPos.y = mMinY;
|
||||
}
|
||||
|
||||
private synchronized Point applyRotation(double mx, double my) {
|
||||
if (mPos.bearing == 0) {
|
||||
mMovePoint.x = mx;
|
||||
mMovePoint.y = my;
|
||||
/**
|
||||
* @param mx the amount of pixels to move the map horizontally.
|
||||
* @param my the amount of pixels to move the map vertically.
|
||||
* @param bearing the bearing to rotate the map.
|
||||
* @param out the position where to move.
|
||||
*/
|
||||
public static void applyRotation(double mx, double my, float bearing, Point out) {
|
||||
if (out == null)
|
||||
out = new Point();
|
||||
|
||||
if (bearing == 0) {
|
||||
out.x = mx;
|
||||
out.y = my;
|
||||
} else {
|
||||
double rad = Math.toRadians(mPos.bearing);
|
||||
double rad = Math.toRadians(bearing);
|
||||
double rcos = Math.cos(rad);
|
||||
double rsin = Math.sin(rad);
|
||||
mMovePoint.x = mx * rcos + my * rsin;
|
||||
mMovePoint.y = mx * -rsin + my * rcos;
|
||||
out.x = mx * rcos + my * rsin;
|
||||
out.y = mx * -rsin + my * rcos;
|
||||
}
|
||||
return mMovePoint;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -16,6 +16,11 @@ package org.oscim.utils;
|
||||
|
||||
public final class Parameters {
|
||||
|
||||
/**
|
||||
* If true the <code>Animator2</code> will be used instead of default <code>Animator</code>.
|
||||
*/
|
||||
public static boolean ANIMATOR2 = false;
|
||||
|
||||
/**
|
||||
* Allow custom tile size instead of the calculated one.
|
||||
*/
|
||||
|
87
vtm/src/org/oscim/utils/animation/DragForce.java
Normal file
87
vtm/src/org/oscim/utils/animation/DragForce.java
Normal file
@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright (C) 2017 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.oscim.utils.animation;
|
||||
|
||||
/**
|
||||
* See https://developer.android.com/reference/android/support/animation/FlingAnimation.html
|
||||
* Class android.support.animation.FlingAnimation
|
||||
*/
|
||||
public final class DragForce {
|
||||
|
||||
private static final float DEFAULT_FRICTION = -4.2f;
|
||||
private static final float DEFAULT_MIN_VISIBLE_CHANGE_PIXELS = 0.5f;
|
||||
|
||||
// This multiplier is used to calculate the velocity threshold given a certain value
|
||||
// threshold. The idea is that if it takes >= 1 frame to move the value threshold amount,
|
||||
// then the velocity is a reasonable threshold.
|
||||
private static final float VELOCITY_THRESHOLD_MULTIPLIER = 1000f / 16f; // 1 frame ≙ 16 ms (62.5 fps)
|
||||
private float mFriction = DEFAULT_FRICTION;
|
||||
private float mVelocityThreshold = DEFAULT_MIN_VISIBLE_CHANGE_PIXELS * VELOCITY_THRESHOLD_MULTIPLIER;
|
||||
|
||||
// Internal state to hold a value/velocity pair.
|
||||
private float mValue;
|
||||
private float mVelocity;
|
||||
|
||||
public void setFrictionScalar(float frictionScalar) {
|
||||
mFriction = frictionScalar * DEFAULT_FRICTION;
|
||||
}
|
||||
|
||||
public float getFrictionScalar() {
|
||||
return mFriction / DEFAULT_FRICTION;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the animation state (i.e. value and velocity).
|
||||
*
|
||||
* @param deltaT time elapsed in millisecond since last frame
|
||||
* @return the value delta since last frame
|
||||
*/
|
||||
public float updateValueAndVelocity(long deltaT) {
|
||||
float velocity = mVelocity;
|
||||
mVelocity = (float) (velocity * Math.exp((deltaT / 1000f) * mFriction));
|
||||
float valueDelta = (mVelocity - velocity);
|
||||
mValue += valueDelta;
|
||||
if (isAtEquilibrium(mValue, mVelocity)) {
|
||||
mVelocity = 0f;
|
||||
}
|
||||
return valueDelta;
|
||||
}
|
||||
|
||||
public void setValueAndVelocity(float value, float velocity) {
|
||||
mValue = value;
|
||||
mVelocity = velocity;
|
||||
}
|
||||
|
||||
public float getValue() {
|
||||
return mValue;
|
||||
}
|
||||
|
||||
public float getVelocity() {
|
||||
return mVelocity;
|
||||
}
|
||||
|
||||
public float getAcceleration(float position, float velocity) {
|
||||
return velocity * mFriction;
|
||||
}
|
||||
|
||||
public boolean isAtEquilibrium(float value, float velocity) {
|
||||
return Math.abs(velocity) < mVelocityThreshold;
|
||||
}
|
||||
|
||||
public void setValueThreshold(float threshold) {
|
||||
mVelocityThreshold = threshold * VELOCITY_THRESHOLD_MULTIPLIER;
|
||||
}
|
||||
}
|
@ -15,7 +15,7 @@
|
||||
* You should have received a copy of the GNU Lesser General Public License along with
|
||||
* this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package org.oscim.utils;
|
||||
package org.oscim.utils.animation;
|
||||
|
||||
import static org.oscim.utils.FastMath.clamp;
|
||||
|
Loading…
x
Reference in New Issue
Block a user