package org.emdev.ui.widget;
import android.os.SystemClock;
import java.util.concurrent.locks.ReentrantLock;
public class Flinger {
private static final float FLING_DURATION_PARAM = 50f;
private static final int DECELERATED_FACTOR = 3;
private static final int DEFAULT_DURATION = 250;
private static final int MODE_STOPPED = 0;
private static final int MODE_SCROLL = 1;
private static final int MODE_FLING = 2;
private final ReentrantLock lock = new ReentrantLock();
private int mode = MODE_STOPPED;
private int startX, startY;
private int minX, minY, maxX, maxY;
private double sinAngle;
private double cosAngle;
private int duration;
private int distance;
private int finalX, finalY;
private int currX, currY;
private long startTime;
private float oldProgress;
public int getCurrX() {
return currX;
}
public int getCurrY() {
return currY;
}
public void startScroll(final int startX, final int startY, final int dx, final int dy) {
lock.lock();
try {
mode = MODE_SCROLL;
this.startX = startX;
this.startY = startY;
this.finalX = startX + dx;
this.finalY = startY + dy;
this.duration = DEFAULT_DURATION;
this.startTime = SystemClock.uptimeMillis();
if (DEFAULT_DURATION > 0) {
final double velocityX = (double) (finalX - startX) / (DEFAULT_DURATION / 1000);
final double velocityY = (double) (finalY - startY) / (DEFAULT_DURATION / 1000);
final double velocity = Math.hypot(velocityX, velocityY);
this.sinAngle = velocityY / velocity;
this.cosAngle = velocityX / velocity;
this.distance = (int) Math.round(velocity * DEFAULT_DURATION / 1000);
}
oldProgress = 0;
} finally {
lock.unlock();
}
}
public void fling(final int startX, final int startY, final int velocityX, final int velocityY, final int minX,
final int maxX, final int minY, final int maxY) {
lock.lock();
try {
mode = MODE_FLING;
this.startX = startX;
this.startY = startY;
this.currX = startX;
this.currY = startY;
this.minX = minX;
this.minY = minY;
this.maxX = maxX;
this.maxY = maxY;
final double velocity = Math.hypot(velocityX, velocityY);
this.sinAngle = velocityY / velocity;
this.cosAngle = velocityX / velocity;
this.startTime = SystemClock.uptimeMillis();
oldProgress = 0;
this.duration = (int) Math.round(FLING_DURATION_PARAM
* Math.pow(Math.abs(velocity), 1.0 / (DECELERATED_FACTOR - 1)));
// System.out.println("Start time:" + startTime + ", duration:" + duration + ", " + startY);
this.distance = (int) Math.round(velocity * duration / DECELERATED_FACTOR / 1000);
this.finalX = getX(1.0f);
this.finalY = getY(1.0f);
} finally {
lock.unlock();
}
}
public boolean computeScrollOffset() {
lock.lock();
try {
if (isFinished()) {
if (oldProgress == 0 || oldProgress == 1) {
// System.out.println("oldProgress == 0 || oldProgress == 1");
return false;
} else {
currX = finalX;
currY = finalY;
oldProgress = 1;
// System.out.println("Finished: " + currY);
// It was not really finished, but it surely is now.
return false;
}
}
// System.out.println("Flinger.computeScrollOffset(" + SystemClock.uptimeMillis() + ")");
float progress = duration > 0 ? (float) (SystemClock.uptimeMillis() - startTime) / duration : 1;
if (oldProgress == progress && progress != 0) {
// System.out.println("oldProgress == progress && progress != 0");
currX = finalX;
currY = finalY;
mode = MODE_STOPPED;
return false;
}
progress = Math.min(progress, 1);
// System.out.println("computeScrollOffset progress:" + progress);
if (mode == MODE_FLING) {
float f = progress;
f = 1 - (float) Math.pow(1 - progress, DECELERATED_FACTOR);
currX = getX(f);
currY = getY(f);
// System.out.println("computeScrollOffset(FLING):" + f + ", " + currY);
} else {
currX = (int) (startX + (finalX - startX) * progress);
currY = (int) (startY + (finalY - startY) * progress);
// System.out.println("computeScrollOffset(SCROLL):" + progress + ", " + currY);
}
oldProgress = progress;
return true;
} finally {
lock.unlock();
}
}
public boolean isFinished() {
lock.lock();
try {
if (SystemClock.uptimeMillis() - startTime >= duration) {
startTime = 0;
duration = 0;
mode = MODE_STOPPED;
}
return mode == MODE_STOPPED;
} finally {
lock.unlock();
}
}
public void forceFinished() {
lock.lock();
try {
if (isFinished()) {
return;
}
startTime = 0;
duration = 0;
// System.out.println("Flinger.forceFinished(): " + oldProgress);
if (oldProgress > 0) {
if (mode == MODE_FLING) {
currX = getX(oldProgress);
currY = getY(oldProgress);
} else {
currX = (int) (startX + (finalX - startX) * oldProgress);
currY = (int) (startY + (finalY - startY) * oldProgress);
}
oldProgress = 0;
}
mode = MODE_STOPPED;
} finally {
lock.unlock();
}
}
public void abortAnimation() {
lock.lock();
try {
startTime = 0;
duration = 0;
oldProgress = 0;
mode = MODE_STOPPED;
} finally {
lock.unlock();
}
}
private int getX(final float f) {
int r = (int) Math.round(startX + f * distance * cosAngle);
if (cosAngle > 0 && startX <= maxX) {
r = Math.min(r, maxX);
} else if (cosAngle < 0 && startX >= minX) {
r = Math.max(r, minX);
}
return r;
}
private int getY(final float f) {
int r = (int) Math.round(startY + f * distance * sinAngle);
if (sinAngle > 0 && startY <= maxY) {
r = Math.min(r, maxY);
} else if (sinAngle < 0 && startY >= minY) {
r = Math.max(r, minY);
}
return r;
}
}