Skip to content

Instantly share code, notes, and snippets.

@dawsontoth
Created July 25, 2012 03:43
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save dawsontoth/3174236 to your computer and use it in GitHub Desktop.
Save dawsontoth/3174236 to your computer and use it in GitHub Desktop.
Draggable Code Explained, Somewhat
var U = require('utils');
var VBN = require('viewBasedNavigator');
exports.applyTo = function(args) {
args = args || {};
U.def(args, {
id: null,
view: null,
anchor: 'left',
expanded: 320,
collapsed: 10,
shadow: 0
});
var win = VBN.getWindow();
var view = args.view;
// Collapse the view.
// lowestPoint tracks: the lowest point of the view (like a top anchored tray being all the way up),
// movementDelta tracks: how far the view can be dragged.
// Both of these are constants, and do not change.
var lowestPoint, movementDelta;
lowestPoint = args.collapsed - args.expanded - args.shadow;
movementDelta = args.expanded - args.collapsed;
// Attach the view to the lowestPoint we calculated above.
view[args.anchor] = lowestPoint;
// Add some click handling to it.
view.addEventListener('touchstart', touchStart);
view.addEventListener('touchmove', touchMove);
view.addEventListener('touchcancel', touchEnd);
view.addEventListener('touchend', touchEnd);
// Keep track of the state of the drag.
// Start point is the x/y where the user started touching. It's a global point.
// Start time is used to figure out how long they touch down for, so we can snap the view open or closed if they simply tap the view.
var startPoint, startTime, deltaPoint;
// isVertical lets us know if we're dragging vertically as opposed to horizontally.
var isVertical = args.anchor in {
top: true,
bottom: true
};
// and attachTopOrLeft lets us determine top or left. Combined with isVertical, we can uniquely identify, with only booleans, where the draggable view is attached and what it can be dragged towards.
var attachTopOrLeft = args.anchor in {
top: true,
left: true
};
// Keep track of the last place the view was snapped (in this case, open or closed). So if they single tap, we can snap it to the opposite of what happened last (open -> closed, closed -> open).
var lastSnap = 0;
// Keep track of the source from touchStart. There was a bug in mobile web where the source wasn't appearing in touch move or some other event, if I remember correctly.
var lastSource;
function touchStart(evt) {
lastSource = evt.source;
// Make sure we're dragging something that wants to be dragged.
if (!lastSource || !lastSource.draggable) return true;
// Fire some events to possibly hide other draggable views.
U.fireEvent('snapping' + args.id, lastSnap);
// Get a global x/y. Note that we need to translate the point, in px, to dp on Android. Lame, huh?
var global = U.pointToDP(view.convertPointToView({
x: evt.x,
y: evt.y
}, win));
// Remember where they started touching. We only want the x or the y, based on where our view can be dragged.
startPoint = isVertical ? global.y : global.x;
// and remember when they started touching.
startTime = new Date().getTime();
}
function touchMove(evt) {
// Again, make sure we only do this for draggable views.
if (!lastSource || !lastSource.draggable) return true;
// Compute the current global point, accounting for Android's evt x/y that ignore the default unit in tiapp.xml.
var global = U.pointToDP(view.convertPointToView({
x: evt.x,
y: evt.y
}, win));
// Grab the relevant point again.
var currentPoint = isVertical ? global.y : global.x;
// Figure out, in this order:
// 1) how far we've been dragged from where we've started
// 2) translate that to be relevant to us being attached in a positive direction (like top, left), or a negative one (like bottom or right). The direction relevancy is necessary when we actually move our view.
// 3) Compensate how far we've dragged with where we started dragging from. So if it was snapped open before, drag from it being snapped open. Otherwise, drag from it being snapped shut previously.
deltaPoint = (attachTopOrLeft ? 1 : -1) * (currentPoint - startPoint) + (lastSnap ? movementDelta : 0);
// Now, if they've dragged farther than the fully expanded position, move the view by a decreasing amount the further from fully expanded they drag.
if (deltaPoint > movementDelta) {
var triNum = (Math.sqrt(8.0 * ((deltaPoint - movementDelta) / 2) + 1.0) - 1.0) / 2.0;
deltaPoint = movementDelta + triNum * 2;
}
// Now that we have our calculated "deltaPoint" (or change from the lowest point), we can apply it to our view.
view[args.anchor] = lowestPoint + deltaPoint;
}
function touchEnd() {
if (!lastSource || !lastSource.draggable) {
lastSource = null;
return true;
}
lastSource = null;
// Figure out the intent of their drag.
var snapTo = lastSnap;
// If they move more than the margin, snap to the other end.
var margin = 50;
if ((lastSnap ? movementDelta - deltaPoint : deltaPoint) > margin) {
snapTo = !lastSnap;
}
// Detect a very short touch to snap all the way open or closed.
if ((new Date().getTime() - startTime < 400)) {
snapTo = !lastSnap;
}
if (!require('data/settings').allowAnimations()) {
view[args.anchor] = lowestPoint + snapTo * movementDelta;
} else {
U.chainAnimate(view, [
createAnimation(view, args.anchor, lowestPoint + snapTo * movementDelta + 5 * (!lastSnap ? 1 : -1), 200), createAnimation(view, args.anchor, lowestPoint + snapTo * movementDelta, 300)]);
}
if (snapTo != lastSnap && args.id) {
U.fireEvent('snapped' + args.id, snapTo);
}
lastSnap = snapTo;
}
if (args.id) {
U.addEventListener('snap' + args.id, function(snapTo) {
if (lastSnap == snapTo) return;
touchStart({
x: 0,
y: 0,
source: view
});
touchMove({
x: 0,
y: 0,
source: view
});
touchEnd({
x: 0,
y: 0,
source: view
});
});
}
};
function createAnimation(view, anchor, val, duration) {
var obj = {};
obj['duration'] = duration;
obj[anchor] = val;
switch (anchor) {
case 'top':
case 'bottom':
obj.left = view.left || 0;
obj.right = view.right || 0;
break;
case 'left':
case 'right':
obj.top = view.top || 0;
obj.bottom = view.bottom || 0;
break;
}
return Ti.UI.createAnimation(obj);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment