S60List.java
/*
* Copyright © 2012 Nokia Corporation. All rights reserved.
* Nokia and Nokia Connecting People are registered trademarks of Nokia Corporation.
* Oracle and Java are trademarks or registered trademarks of Oracle and/or its
* affiliates. Other product and company names mentioned herein may be trademarks
* or trade names of their respective owners.
* See LICENSE.TXT for license information.
*/
package com.nokia.example.attractions.views.list;
import com.nokia.example.attractions.views.ViewMaster;
/**
* List view custom UI control with gesture controls.
*/
final class S60List
extends List
implements ViewMaster.PointerEventListener {
private Thread delayThread;
private boolean enabled = false;
private final ViewMaster view;
private int oldY;
private long oldTime;
private int dragThreshold = 10;
private boolean dragging = false;
private Animator animator = null;
private int friction = 100;
private int maxScrollSpeed = 100;
private int minScrollSpeed = 10;
private int velocityY = 0;
S60List() {
view = ViewMaster.getInstance();
}
public final void disable() {
if (!enabled) {
return;
}
enabled = false;
view.setPointerEventListener(null);
super.disable();
}
public final void enable() {
if (enabled) {
return;
}
enabled = true;
super.enable();
view.setPointerEventListener(this);
}
public final void resize(int x, int y, int width, int height) {
super.resize(x, y, width, height);
dragThreshold = height / 20;
maxScrollSpeed = height;
minScrollSpeed = height / 3;
friction = height / 2;
}
private void animate(int deltaY, int velocityY, boolean lastFrame) {
addTranslateY(deltaY);
this.velocityY = velocityY;
// Scrolling is no longer active if this is the last frame
if (lastFrame) {
stopScrollAnimation();
}
// Request repaint from ViewMaster
view.forceDraw();
}
/**
* Stops scrolling.
*/
protected final void stopScrollAnimation() {
velocityY = 0;
if (animator != null) {
animator.close();
animator = null;
}
}
public final void pointerDragged(int x, int y) {
final long currentTime = System.currentTimeMillis();
final int dy = y - oldY;
final int dt = (int) (currentTime - oldTime);
if (Math.abs(dy) > dragThreshold) {
dragging = true;
}
if (dragging) {
if (dt > 0) {
velocityY = (velocityY + dy * 1000 / dt) / 2;
}
oldY = y;
oldTime = currentTime;
}
if (delayThread == null && dragging) {
if (animator != null) {
stopScrollAnimation();
}
else {
addTranslateY(dy);
view.forceDraw();
}
}
}
public final void pointerPressed(int x, int y) {
oldY = y;
oldTime = System.currentTimeMillis();
}
public final void pointerReleased(int x, int y) {
if (delayThread == null) {
if (dragging) {
if (animator != null) {
animator.close();
animator = null;
}
final int v = Math.min(Math.max(-maxScrollSpeed, velocityY),
maxScrollSpeed);
if (Math.abs(v) > minScrollSpeed) {
animator = new Animator(v, friction);
animator.start();
}
}
else {
if (animator != null) {
stopScrollAnimation();
}
else {
handleSelectEvent(x, y);
}
}
}
dragging = false;
}
/**
* Common handler function for selection events.
*
* @param event Gesture event.
*/
private void handleSelectEvent(int x, int y) {
setFocusedRowIndex(getRowAt(x, y));
if (isFocusedRowIndex()) {
view.draw();
// Delay the event handling,
// so that user gets a chance to see the focus.
delayThread = new Thread() {
public void run() {
try {
Thread.sleep(100);
}
catch (InterruptedException e) {
return;
}
select();
resetFocusedRowIndex();
delayThread = null;
}
};
delayThread.start();
}
}
/**
* Finds the row with the given coordinates.
*
* @param x X-coordinate.
* @param y Y-coordinate.
* @return Row at the given coordinates or -1 if no row found.
*/
private int getRowAt(int x, int y) {
int listY = y - getTranslateY();
int heightSoFar = 0;
int heightNext = 0;
// Go through all items (pretend that list item heights can vary).
if (data != null) {
for (int i = 0; i < data.size(); i++) {
heightSoFar = heightNext;
heightNext += itemHeight();
if (listY >= heightSoFar && listY <= heightNext) {
return i;
}
}
}
return -1;
}
/**
* Animation thread.
*/
private class Animator
extends Thread {
private static final int MAX_FPS = 30;
private volatile boolean run = true;
private int startSpeed;
private int friction;
/**
* Constructor.
*
* @param startSpeed
* @param friction
*/
public Animator(final int startSpeed, final int friction) {
if (friction < 0) {
throw new IllegalArgumentException(
"Friction cannot be negative.");
}
this.startSpeed = startSpeed;
this.friction = startSpeed > 0 ? friction : -friction;
}
/**
* @see Thread#run()
*/
public void run() {
final long startTime = System.currentTimeMillis();
final int tMax = Math.abs(startSpeed * 1000 / friction);
if (tMax == 0) {
animate(0, 0, true);
return;
}
final int sMax = startSpeed / 2 * tMax;
final int dtMin = 1000 / MAX_FPS;
int t = 0;
int tUpdated = t;
int sUpdated = 0;
int ds = 0;
boolean lastFrame = false;
long frameEndTime = startTime;
try {
while (run) {
Thread.sleep(Math.max(dtMin - (int) (System.
currentTimeMillis() - frameEndTime), 0));
t = (int) (System.currentTimeMillis() - startTime);
if (t < tMax) {
ds += startSpeed * t - friction * t * t / 2000
- sUpdated;
}
else {
ds += sMax - sUpdated;
lastFrame = true;
run = false;
}
if (run && (lastFrame || Math.abs(ds / 1000) > 0)) {
final int v = startSpeed - friction * t / 2000;
animate(ds / 1000, v, lastFrame);
tUpdated = t;
sUpdated += ds;
ds = 0;
}
frameEndTime = System.currentTimeMillis();
}
}
catch (Exception e) {
animate(0, 0, true);
}
}
;
/**
* Closes this thread.
*/
public void close() {
run = false;
}
}
}