Page 5. Snap to location

Some applications require that the user only drop a dragged object to a few predefined points or areas of the screen. This example takes the basic drag-and-drop interface we created on page 1 and adds a grid of valid points. This gives a "snap to grid" effect that is familiar to those who use graphics programs.

Since this application is different from the ones we have been looking at, we will first present the finished applet so you can experience the interaction.

User Instructions:

  • Use the mouse to drag-and-drop the three colored squares into the containing rectangle on the right in any order.
  • We did not put a button anywhere on the stage, but you can press the "UP ARROW" key on your keyboard to put the squares back in their original positions.
  • Note that once you place a square in the container, you can no longer interact with it until you reset the whole stage with the "UP ARROW" keyboard event.

The initial set up for this application is a little different than in the other examples in this tutorial. We create and place on the stage four different Sprite objects that we will think of as three movable square blocks and a rectangular container called "holder."

 

//Part I -- Set up the graphics

 

var holder:Sprite = new Sprite();

var block1:Sprite = new Sprite();

var block2:Sprite = new Sprite();

var block3:Sprite = new Sprite();

 

this.addChild(holder);

this.addChild(block1);

this.addChild(block2);

this.addChild(block3);

 

holder.graphics.beginFill(0xDDDDDD);

holder.graphics.lineStyle(2,0);

holder.graphics.moveTo(0,0);

holder.graphics.lineTo(0,300);

holder.graphics.lineTo(100,300);

holder.graphics.lineTo(100,0);

holder.graphics.endFill();

holder.x = 350;

holder.y = 50;

 

block1.graphics.beginFill(0xDDDDFF);

block1.graphics.lineStyle(0,0);

block1.graphics.moveTo(0,0);

block1.graphics.drawRect(0,0,100,100);

block1.graphics.endFill();

block1.x = 50;

block1.y = 50;

 

block2.graphics.beginFill(0xFFDDDD);

block2.graphics.lineStyle(0,0);

block2.graphics.moveTo(0,0);

block2.graphics.drawRect(0,0,100,100);

block2.graphics.endFill();

block2.x = 50;

block2.y = 200;

 

block3.graphics.beginFill(0xDDFFDD);

block3.graphics.lineStyle(0,0);

block3.graphics.moveTo(0,0);

block3.graphics.drawRect(0,0,100,100);

block3.graphics.endFill();

block3.x = 175;

block3.y = 100;

 

The drag-and-drop functionality is handled in a way very similar to Page 3 when we addressed interacting efficiently with any one of several objects. A key difference is the counting of the placed squares (using the variable "placed") so the y-coordinate of the final position of the dropped square can be properly computed based on how many squares have been placed before.

Another difference here is the "snapping" to location. In this case, we want the square to either go back where it came from (which we store with a variable called "startingPt" at the moment the object hears the MOUSE_DOWN event) or go into the "holder" rectangle depending on whether the block sprite and the holder sprite are touching. The hitTestObject method of our sprites can make this determination easily. We made the arbitrary decision to use holder.hitTestObject(movingBlock) instead of movingBlock.hitTestObject(holder) -- both return the same result. Note that once we snap an object to line up with the "holder" sprite, we remove its listener for the MOUSE_DOWN event, effectively keeping it from ever being moved again. In Part III we will show how to add a "reset" function during which these listeners can be added back.

 

// Global variables to keep up with things

 

var movingBlock:Sprite;

var startingPt:Point = new Point();

var placed:int = 0;

 

 

// Part II -- Add drag and drop functionality

 

block1.addEventListener(MouseEvent.MOUSE_DOWN, startBlockMove);

block2.addEventListener(MouseEvent.MOUSE_DOWN, startBlockMove);

block3.addEventListener(MouseEvent.MOUSE_DOWN, startBlockMove);

 

function startBlockMove(evt:MouseEvent):void {

movingBlock = Sprite(evt.currentTarget);

startingPt.x = movingBlock.x;

startingPt.y = movingBlock.y;

stage.addEventListener(MouseEvent.MOUSE_MOVE, moveBlock);

}

 

function moveBlock(e:MouseEvent):void {

movingBlock.x = stage.mouseX;

movingBlock.y = stage.mouseY;

}

 

stage.addEventListener(MouseEvent.MOUSE_UP, stopMotion);

 

function stopMotion(evt:MouseEvent):void {

stage.removeEventListener(MouseEvent.MOUSE_MOVE, moveBlock);

snapInPlace();

movingBlock = new Sprite();

startingPt = new Point();

}

 

function snapInPlace():void {

if (holder.hitTestObject(movingBlock)) {

movingBlock.x = holder.x;

movingBlock.y = holder.y + 100*placed;

placed++;

movingBlock.removeEventListener(MouseEvent.MOUSE_DOWN, startBlockMove);

}

else {

movingBlock.x = startingPt.x;

movingBlock.y = startingPt.y;

}

}

 

 

Sometimes this is not the interaction we want, especially if it is possible for our movingBlock to hit more than one possible containing object simultaneously. In these cases, you might consider the similar method hitTestPoint, which takes as its arguments an x and y coordinate of a point relative to the parent object. For example, in the code above, if you change the line containing hitTestObject to read

if (holder.hitTestPoint(stage.mouseX, stage.mouseY)) {

the result will be that the mouse must actually be over the holder rectangle for the dropped square to snap into place. This interaction is often more intuitive for a user in some applications.

The final part of the code is a simple reset function that allows the user to try the applet again without having to reload the page. This could also be accomplished by putting a "reset button on the stage" and using this same code. Note that not only do we put the points back where they started but we also have to re-register all of the MOUSE_DOWN listeners because any combination of them could have been removed when the "reset" action is taken. We also reset "placed" to 0 so that future placement of the squares will have the correct y-coordinates.

 

// Part III -- Add a reset capability with a keyboard event

 

stage.addEventListener(KeyboardEvent.KEY_DOWN,reset);

 

function reset(evt:KeyboardEvent) {

if (evt.keyCode == Keyboard.UP) {

block1.x = 50;

block1.y = 50;

block2.x = 50;

block2.y = 200;

block3.x = 175;

block3.y = 100;

 

block1.addEventListener(MouseEvent.MOUSE_DOWN, startBlockMove);

block2.addEventListener(MouseEvent.MOUSE_DOWN, startBlockMove);

block3.addEventListener(MouseEvent.MOUSE_DOWN, startBlockMove);

 

placed = 0;

}

}

 

A more challenging issue is to allow the user to drag things on and off the "holder" rectangle, because in this case you have to keep track of whether any given rectangle is on or off the stack. This can be accomplished with some logic and a little extra programming having nothing to do with dragging and dropping -- perhaps you will give this a try.

Download

Notes

  • There is a troubling issue that can arise when you have multiple objects (particular large ones) that can be dragged in your application. Namely, the user expects the object being moved to pass over all other objects on the screen. (If you did not notice this in the application above, try it again, this time moving the squares across one another.) This can be alleviated by swapping the depth of the clicked object so that it has the highest depth (and therefore is on top of everything else). In Actionscript 3, the depth is referred to as the Display List index, and the maximum index of any child on the stage is "stage.numChildren - 1." Hence, the following revision (highlighted) to the startBlockMove function will produce the desired effect:

function startBlockMove(evt:MouseEvent):void {

movingBlock = Sprite(evt.currentTarget);

setChildIndex(movingBlock, numChildren - 1);

startingPt.x = movingBlock.x;

startingPt.y = movingBlock.y;

stage.addEventListener(MouseEvent.MOUSE_MOVE, moveBlock);

}

Back to Basic Tutorials              Back to Flash and Math Home

We welcome your comments, suggestions, and contributions. Click the Contact Us link below and email one of us.

Adobe®, Flash®, ActionScript®, Flex® are registered trademarks of Adobe Systems Incorporated.