Monday, November 2, 2009

Drag and Drop

Drag and Drop

Dragging clips on the stage is easy to do. This recipe is built on a few simple ideas.

First to get an object to follow the mouse around all Sprite and MovieClip classes share the startDrag() and stopDrag() methods. These methods do exactly what their names imply. The startDrag() method starts dragging a clip, while stopDrag() ends the drag.

Making a click and drag
This type of interactivity centers two events MOUSE_DOWN and MOUSE_UP. A drag always begins with a MOSUE_DOWN event and ends with a MOUSE_UP.

The key to making a reliable click and drag is to be sure to capture a MOUSE_UP event when the cursor is NOT over the target clip. Do this by also adding a MOUSE_UP event to the stage

Often as you drag a clip the cursor will end up outside the area of a clip. This can happen if you move the cursor beyond the boundaries of drag or if you move the cursor very fast, or drag the cursor outside the area of the stage and then release.

Setting up a simple drag
To create a simple drag, create a clip on the stage and assign it an instance name. For this example assume the name of the clip is drag_mc.

The code block below allows the clip drag_mc to be dragged all over the stage without limit.

drag_mc.addEventListener( MouseEvent.MOUSE_DOWN, on_press );

function on_press( e:MouseEvent ):void {
    drag_mc.startDrag();
    drag_mc.addEventListener( MouseEvent.MOUSE_UP, on_release );
    stage.addEventListener( MouseEvent.MOUSE_UP, on_release );
}

function on_release( e:MouseEvent ):void {
    drag_mc.stopDrag();
    drag_mc.removeEventListener( MouseEvent.MOUSE_UP, on_release );
    stage.removeEventListener( MouseEvent.MOUSE_UP, on_release );
}

Explanation
The code block above works by adding an a MOUSE_DOWN event to the target clip. This event begins the dragging action.

The handler for this event, on_press, starts dragging the clip by calling startDrag(). Here we also add two more event listeners, listening for the MOUSE_UP event. One of these events is added to the target clip, drag_mc in this example. The other is added to the stage. By adding the MOSUE_UP event to the stage we can catch the MOUSE_UP event in the case where the mouse is released outside the area of the target clip. Note both of these listeners are handled with the same functions on_release.

The last part of the example is the on_release function. Here the dragging action is ended by calling stopDrag() on the target clip. Then we remove both of the MOUSE_UP event listeners.


Details
Note, when a clip is being dragged via the
startDrag() method Flash always rounds the x and y position of the clip
to the nearest whole number. This is usually not a problem. In cases
where a clip constrained to a vertical or horizontal line a clip may
shift a pixel to one side as it is being dragged. If this happens be
sure to check the x and y position of the clip and set the number to a
whole number.


Dragging from the center or dragging from an edge
The startDrag() method provides the option of dragging from the center or dragging from the point relative to the mouse when the drag was initiated.

Dragging from the center is a drag where the clip aligns it's registration point with the cursor when the drag begins.

Dragging from a relative point keeps the clip in the same relative position to the mouse as it is dragged.

Why use one method or the other? Center drag is good for games and situations where the position of the clip, as you drag it needs to aligned to the mouse in some way. Dragging relative is best for situations where you don't want the object to move when it is initially clicked. This is good for scroll bars and things like dragging images around on a light table.

This option is called lockCenter. Passing a true as the first parameter when calling startDrag() will force the clip to align at the registration point. Passing a false will cause the clip to move relative. Note false is the default value, so passing nothing defaults to a drag relative. The previous examples were working in this mode. Try out lockCenter with:

drag_mc.startDrag( true );

You should see the clip align it's registration point with the cursor.

Note: If the registration point is not in the center the clip will not align with the center. For example if the registration point is set in the upper left corner of the clip then the clip will appear to drag from the upper left corner. See registration point notes.



Limiting the area of a drag
Besides the lockCenter parameter startDrag() also takes an optional bounds parameter. This bounds parameter sets the boundaries for the drag action. The bounds defines a rectangle and during the drag the clip may only move within the rectangle.

The bounds parameter must be an instance of the Rectangle Class. The Rectangle Object object describes a rectangle using four properties, x, y, width and height. The x and y place the upper left corner of the rectangle. From that corner the width and height extend to define the area of the rectangle.

Try it out for yourself. Using the last example define a rectangle for the drag action. Below I have defined a Rectangle 300 by 200 pixels starting at x 100 and y 100. Notice that I set lockCenter to false for this example.

drag_mc.addEventListener( MouseEvent.MOUSE_DOWN, on_press );

function on_press( e:MouseEvent ):void {
    var bounds:Rectangle = new Rectangle( 100, 100, 300, 200 );
    drag_mc.startDrag( false, bounds );
    drag_mc.addEventListener( MouseEvent.MOUSE_UP, on_release );
    stage.addEventListener( MouseEvent.MOUSE_UP, on_release );
}

function on_release( e:MouseEvent ):void {
    drag_mc.stopDrag();
    drag_mc.removeEventListener( MouseEvent.MOUSE_UP, on_release );
    stage.removeEventListener( MouseEvent.MOUSE_UP, on_release );
}

Note: To limit the drag to a horizontal line set the height of the bounds Rectangle to 0. To limit the drag to a vertical line set the width of the bounds to 0. For example, the following limits the drag to a horizontal line in the center of the stage, that stops 100 pixels from either side of the stage.

var bounds:Rectangle = new Rectangle( 100, 200, 350, 0 );

I came up with the numbers above by taking the left limit of 100 and setting that as the x. Then I vertical position and set that as y. Using the default stage size of 550 by 400, y of 200 is half the height 400. I wanted the right limit to stop 100 pixels from the right edge. Since the stage 550 width and I'm stopping 100 pixels from the left and right, I subtract 200 from the width for 350. Last, I don't want to move vertically so I set the height to 0.

You may have noticed testing these examples, that the the clip, if it starts outside the area of the drag, it is forced into the are when you start moving it. Often you will want to create a drag that works from the current position of the clip. The line below modifies the example so that the clip drags all the way across the stage at it's current y position.

var bounds:Rectangle = new Rectangle( 0, drag_mc.y, 550, 0 );
drag_mc.startDrag( false, bounds );


Note that I used drag_mc.y to set the y position for the bounds rectangle. This way the clip uses it's current position to set the drag.


Getting the Bounds of an Object
It is very convenient to use an object on the stage, such as another MovieClip, to define the rectangle used as bounds. This can be accomplished with the getRect() or getBounds() methods. These methods return a Rectangle that defines the are of the clip.

The getRect() and getBounds() methods are same with the difference that the rectangle returned by getRect() does not include any strokes, while getBounds() does include any strokes on the object in the area of the rectangle returned.

Both getRect() and getBounds() take a single parameter that sets the coordinate space. This should usually be set to the parent clip. In the example below I use the keyword this, since the script is written on the main timeline and both clips are contained on the main timeline.

var rect:Rectangle = bounds_mc.getRect( this );
drag_mc.startDrag( false, rect );

Note: getRect() and getBounds() return rectangles. If the object is not a rectangle the either function returns a rectangle that surrounds the Object, getBounds() would include any strokes, getRect() would ignore them.


Dragable Class
To make the drag scrip more portable you could create a class that could be added to any MovieClip to make that clip dragable. The code above could be added to a class that extends MovieClip (or Sprite).

This first example creates a Dragable class using inheritance.

The script below should be saved in an Actionscript file named Dragable.as. The Dragable class can then be assign as the Base Class of any MovieClip in your library to make it Dragable.

package {
    import flash.display.*;
    import flash.events.*;
    
    public class Dragable extends MovieClip {
        public function Dragable() {
            super();
            addEventListener( MouseEvent.MOUSE_DOWN, on_press );
        }
        
        private function on_press( e:MouseEvent ):void {
            addEventListener( MouseEvent.MOUSE_UP, on_release );
            stage.addEventListener( MouseEvent.MOUSE_UP, on_release );
            startDrag();
        }
        
        private function on_release( e:MouseEvent ):void {
            removeEventListener( MouseEvent.MOUSE_UP, on_release );
            stage.removeEventListener( MouseEvent.MOUSE_UP, on_release );
            stopDrag();
        }
    }
}

You can also make instances of this class using the new keyword. In this case you'd need to add artwork to the Dragable instance and add it to the display to make it visible. For example the following creates a new instance of Dragable and draws a black ellipse inside it and adds it to the display list.

var dragable_mc:Dragable = new Dragable();
dragable_mc.graphics.beginFill( 0x000000 );
dragable_mc.graphics.drawEllipse( 0, 0, 32, 48 );
dragable_mc.graphics.endFill();
addChild( dragable_mc );

Dargable using Composition
MakeDragable has the same effect as the script above but accomplishes the effect via Composition.

package {
    import flash.display.*;
    import flash.events.*;
    
    public class MakeDragable {
        private var _target:MovieClip;
        
        public function MakeDragable( target:MovieClip ) {
            _target = target;
            _target.addEventListener( MouseEvent.MOUSE_DOWN, on_press );
        }
        
        private function on_press( e:MouseEvent ):void {
            _target.addEventListener( MouseEvent.MOUSE_UP, on_release );
            _target.stage.addEventListener( MouseEvent.MOUSE_UP, on_release );
            _target.startDrag();
        }
        
        private function on_release( e:MouseEvent ):void {
            _target.removeEventListener( MouseEvent.MOUSE_UP, on_release );
            _target.stage.removeEventListener( MouseEvent.MOUSE_UP, on_release );
            _target.stopDrag();
        }
    }
}

Use the class in this way. Create a new instance and pass instance name of the MovieClip that will be dragable. For example the follow would make drag_mc dragable, assume that drag_mc is on the stage and has been assigned the instance name drag_mc.

new MakeDragable( drag_mc );

Note: In the MakeDragable class we need to reference the stage from the _target property. Since the class doesn't extend MovieClip or other DisplayObject, it doesn't have a reference to the stage.



Making a Scrollbar
A great application for dragging is a scrollbar. A scrollbar can be used for many different uses, scrolling text, or volume control or in place you need to allow users to present variable range for input.

The basic idea is to create an object drags along the horizontal or vertical axis within a limited range.

Key feature of the scroll bar is controlling the range of of the motion. The range of motion can be controlled in two ways. Set via a parameter, pass a value like 150 to set the range of motion. Your code would use this number to limit the range to 150 pixels. Or, reference an Object on the stage and use the size of that object to set the range. Here you would use the width or height of another object to set the range of motion.

The first method is easiest, but can takes some guess work to gauge the right distance. The second method is more flexibly but requires a little more coding and an extra object on the stage.

Drag by setting the distance
The example below assumes that you have a MovieClip with the instance name drag_mc on the stage. This example assumes the clip is at it's topmost position and will be able to drag up to 200 pixels below that point.

We'll create a Rectangle to set the bounds for the drag action and save it in a variable to use whenever we drag the clip. We'll also use the initial x and y value of the clip to generate the Rectangle. The rectangle is located at the x and y of drag_mc, has a width of 0 and a height of 200.

var drag_rect:Rectangle = new Rectangle(drag_mc.x, drag_mc.y, 0, 200);
drag_mc.addEventListener( MouseEvent.MOUSE_DOWN, on_press );


function on_press( e:MouseEvent ):void {
    drag_mc.startDrag( false, drag_rect );
    drag_mc.addEventListener( MouseEvent.MOUSE_UP, on_release );
    stage.addEventListener( MouseEvent.MOUSE_UP, on_release );
}

function on_release( e:MouseEvent ):void {
    drag_mc.stopDrag();
    drag_mc.removeEventListener( MouseEvent.MOUSE_UP, on_release );
    stage.removeEventListener( MouseEvent.MOUSE_UP, on_release );
}

Note: You could set the numbers for the bounds Rectangle by typing all of the numbers. But, using the x and y of drag_mc make is convenient, since moving drag_mc will reposition the bounds without you having to change the script.

Drag by using another clip to define the distance
Let's try that again with a an object used to set the range. This example requires two MovieClips on the stage. Be sure to place the registration point for each clip in the upper left corner! This is important to make the clips drag correctly.

The first should be a tall narrow rectangle. This will set the range. Name this clip track_mc.

The second a smaller box. This will be the dragging clip. Name this clip drag_mc.

var range:Number = track_mc.x + track_mc.height;
var drag_rect:Rectangle = new Rectangle(track_mc.x, track_mc.y, 0, range );
drag_mc.addEventListener( MouseEvent.MOUSE_DOWN, on_press );


function on_press( e:MouseEvent ):void {
    drag_mc.startDrag( false, drag_rect );
    drag_mc.addEventListener( MouseEvent.MOUSE_UP, on_release );
    stage.addEventListener( MouseEvent.MOUSE_UP, on_release );
}

function on_release( e:MouseEvent ):void {
    drag_mc.stopDrag();
    drag_mc.removeEventListener( MouseEvent.MOUSE_UP, on_release );
    stage.removeEventListener( MouseEvent.MOUSE_UP, on_release );
}

If the clips do not align on the stage, they should jump into alignment when you begin dragging.

If the clips are offset and to the left, right, top or bottom check the registration points on both clips. Be sure they are both in the upper left corner.

This example will allow the dragging clip to drag the entire height of the track. This will allow the dragging clip to move past the bottom of the track so that it stops when it's top edge reaches the bottom of the track. To keep the drag clip within the area of the track we need to make the range shorter than the track by the height of the dragger.

Try this, modify the first line of the last example as follows:

var range:Number = track_mc.x + track_mc.height - drag_mc.height;

Now the range is the difference between the height of the track and the height of the dragger.


Making a Scrollbar Class








No comments:

Post a Comment