Advertisement

Throw Objects by Creating a PanAndThrow Class

by
Student iconAre you a student? Get a yearly Tuts+ subscription for $45 →

In this tutorial we will be mocking up and finishing a pan and throw class that will allow us to add this effect to any element we want. To accomplish this, we will create a image viewer - but not your average viewer. Here we'll have zooming, throwing, panning.. Almost sounds like a ninja app, huh?


Step 1: Introduction

The Pan and Throw Class will allow you to add the pan and throw functionality to any ActionScript object you want. Although this tutorial is specifically for Flex, the class itself can be used anywhere ActionScript is. I had seen this effect on several websites, then in Photoshop CS4 and decided that I wanted this on my projects too.

There are many applications for this effect; the one that we are going to be using for this tutorial is an image viewer that lets you zoom in and out of am image and change the friction that the throw effect uses. However, this tutorial isn't really about the image viewer, it is about making a pan and throw class. So let's get started with it. Open up your favorite Flex editor and get a project going; for information on doing this in Flex Builder see the Adobe LiveDocs. Once your project is created, open the MXML file. We need to add some code to this before we create our class.


Step 2: Our MXML

Since this isn't the major portion of the tutorial I am not going to spend much time here. If you have any questions about this section that aren't covered, you can ask in the comments below. Firstly, here are the MXML objects to put in the application:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" frameRate="24" layout="absolute" creationComplete="init()" backgroundColor="#888888" >

    <mx:Canvas id="outside" height="100%" width="100%" verticalScrollPolicy="off" horizontalScrollPolicy="off" >
        <mx:Image id="inside" source="@Embed('img/selfportrait.jpg')" creationComplete="smoothImage(event);" />
    </mx:Canvas>


    <mx:VBox id="control" backgroundColor="#000000" backgroundAlpha=".6" height="120"  width="200" cornerRadius="5" borderStyle="solid" borderThickness="0" top="-5" left="-5" paddingTop="10" paddingLeft="10" >

        <mx:HSlider id="hSlider" minimum="10" maximum="200" value="100" allowTrackClick="true" liveDragging="true" change="zoom()" />
        <mx:Label color="#aaaaaa" text="Zoom" />

        <mx:HSlider id="sldDecay" minimum=".1" maximum="1" value=".7" allowTrackClick="true" liveDragging="true" change="changeDecay()"/>
        <mx:Label color="#aaaaaa" text="Decay" />
    </mx:VBox>



</mx:Application>

You'll notice the four functions called in the tags: init(), changeDecay(), smoothImage() and zoom(). We need to write up those functions. This is the code between the <mx:script> tags:


import mx.states.SetStyle;
import mx.effects.Move;
import mx.containers.HBox;
import mx.containers.Box;

private var imageWidth:Number = 0;
private var imageHeight:Number = 0;
private var mover:Move = new Move();

// this will be called when the application loads
private function init():void {
	// This event will add the ability to hide and show our controls with a click.
    control.addEventListener(MouseEvent.CLICK, controlClick);
    mover.target = control;
}
// this function will zoom in and out of our image according to the value of our zoom slider.
private function zoom():void {
    inside.width = (imageWidth*hSlider.value)/100;
    inside.height = (imageHeight*hSlider.value)/100;
}
// this gets called when our image changes size.
private function smoothImage(ev:Event):void{
    //set image smoothing so image looks better when transformed.
    var bmp:Bitmap = ev.target.content as Bitmap;
    bmp.smoothing = true;

    imageWidth=inside.width;
    imageHeight=inside.height;
}
// we won't be using this one yet
private function changeDecay():void
{
	// this will change the decay (friction) value of our class, when we get there.
}

private function controlClick(e:MouseEvent):void
{
    mover.play();
	//this function hides/shows the controls on click
    if(control.y != -5){
        mover.stop();
        mover.yTo = -5;
        mover.play();
    }
    else if(e.target == control){
        mover.stop();
        mover.yTo = (control.height - 10) * -1;
        mover.play();
    }

}

Once you have your MXML you need to create a folder called "classes" in the same folder as your MXML file. (If using Flash, the folder needs to be in the same dir as your FLA file.) This is our classes package and is where the PanAndThrow.as file will go. In Flex Builder, create a new class, put it in the classes package, and call it PanAndThrow; this will create your class - default style.


Step 3: The Makings of a Class

Here's our basic PanAndThrow class. Save it as PanAndThrow.as in your new "classes" folder.

//namespace declaration
package classes

// class declaration
public class PanAndThrow
{
    /* this is called the constructor, this method/function will get called when you create
     * an instance of your object, or instantiate your object.
     * for this class we don't do anything because we are going to do everything
     * in the Init function
     */
    public function PanAndThrow()
    {

    }

}

Which variables and functions do we need in our PanAndThrow class? To get that, you can ask yourself "what does my class need to do, what does it need to know, and what does it need to be able to do it?" So let's create some pseudo-code.

Quick Note

When I first developed this class I did put everything in the constructor, but that lead to a problem when I created the start and stop methods because of scope. I couldn't instantiate this class on a global scope with all the information required. I therefore made an init() function, so the instance could be started and stopped from outside the class.


Step 4: Our Pseudo-Code

"Pseudo-code" just means fake code, that we can use to help ourselves think about what real code we'll need.

package classes

public class PanAndThrow
{
	/*These will be the variables that we make. So what do we need to know?
     * anObjectToThrow;
     * anObjectToThrowItIn;
     * ObjectLocation;
     * PreviousObjectLocation;
     * Decay; // for the physics
     * these are the obvious ones, but this list will get a lot bigger
     * as we see exactly what we need in our functions
     */

	public function PanAndThrow()
    {

    }

    /* So what is our class going to do?
     * init(); //it needs to start
     * stop(); // we want to be able to stop it somehow.
     * start(); // if we stop we need to be able to start it again.
     * pan();
     * throw();
     */

}

Now that we have some pseudo-code we can start building the class. Let's start with the init() function. This will also bring us into one of the principles of Object Oriented Programming called encapsulation, which deals with the access of pieces of the code.

This code should go in the PanAndThrow class we've just started. (Not sure where? Check out the Document Class Quick Tip.)

// thanks to OOP, a lower level class and an upper level class (one that extends
// the lower level class) can be used. Like here, almost any object you will use extends the
// Sprite class. So I just have to ask for a Sprite object and you can give a Box or a Button.
private var targetObject:Sprite = new Sprite();
private var eventObject:Sprite = new Sprite();

private var originalDecay:Number = .9;
private var buttonDown:Boolean = false;
private var moveY:Boolean = true;
private var moveX:Boolean = true;
private var TargetClick:Boolean = true;

// We'll use this to check how long your mouse has been down on an object without moving.
private var t:Timer;
private var timerInterval:int = 100;

public function init(ObjectToMove:Sprite, ObjectToEventise:Sprite, DecayAmout:Number = .9, isMoveY:Boolean = true, isMoveX:Boolean = true, OnlyMoveOnTargetClick:Boolean = true):void
{
    targetObject = ObjectToMove;
    eventObject = ObjectToEventise;
    originalDecay = DecayAmount;

    moveX = isMoveX;
    moveY = isMoveY;
    TargetClick = OnlyMoveOnTargetClick;

    t = new Timer(timerInterval);
    start();
}

Just a couple of things I want to point out. In the function for init I have set a few of the arguments to be equal to a value. That means I'm giving them a default value, thus making them optional. When setting default values for arguments of a function, they have to be the last parameters - you can't have a required variable after an optional one. The reason I added default variables is to make the call shorter if we use the default settings. I can call PanAndThrow(mover, eventer); and be done, instead of PanAndThrow(mover, enventer, decayer, yVal, ... ) and so on.

Have you ever wondered what the "private" or "public" in front of functions and variables means? That is the exposure of the object. A "public" object can be accessed by any other class; a "private" object can only be seen by the other members of this class; a "protected" object is hidden from everything except classes which are in the same package.

We want to be able to change the decay from our MXML so we need a public hook to get to our private variable; this is where getter and setter functions come in:

private var originalDecay:Number = .9;

public function get decay():Number
{
	return originalDecay;
}

That's a "getter" function. It means that, to outside classes, it looks like the PanAndThrow class has a public variable called "decay". When they try to access it, we will return to them the value of our (private) originalDecay variable.

Setter functions are almost the same, but allows the outside classes to change the value of our "fake" public variable:

public function set decay(value:Number):void
{
	originalDecay = value;
}

These are useful because you can put logic into a setter to constrain what comes into your private var. For instance, if you put a Number in the MXML tag for a box you will get a set height; if you put a % (making the number a string) you will get a percentage height. That is built into the code for the box's height setter. Now that we have our getter and setter you can access the decay variable like this from outside the class:

var pt:PanAndThrow = new PanAndThrow();
pt.init(target, parent);
pt.decay = .7;

Step 5: Start, Listen, Stop

We have our class, some local variables and an init() function. Let's do something now. At the end of the init() function we called "start();" so let's make the start function. Mostly it's just a bunch of listeners:

public function start():void{

    // With the mouse down, we are looking to start our pan action, but we need to be able
    // to check our OnlyMoveOnTargetClick which we assigned to our global field TargetClick
    targetObject.addEventListener(MouseEvent.MOUSE_DOWN, handleOverTarget);
    eventObject.addEventListener(MouseEvent.MOUSE_DOWN, handleOverTarget);

    // When we call our pan, it uses a mouse move listener, which means it gets called every time the
    // mouse moves, so we need to see how to limit when the target object moves.
    eventObject.addEventListener(MouseEvent.MOUSE_MOVE, moveIt);

    // this is to throw the object after a pan, this is a little tricky because the throwIt() function calls another listener.
    targetObject.addEventListener(MouseEvent.MOUSE_UP, throwIt);
    eventObject.addEventListener(MouseEvent.MOUSE_UP, throwIt);

    //the throwItOut method makes our object act as though we let go of the mouse button, but it gets fired when
    // the mouse leaves the parent object
    targetObject.addEventListener(MouseEvent.MOUSE_OUT, throwItOut);
    eventObject.addEventListener(MouseEvent.MOUSE_OUT, throwItOut);

    // this is the timer listener, this will check to see if you have been holding the mouse down for a little bit, I will
    // explain the need for this when we get to the timerOut() function
    t.addEventListener(TimerEvent.TIMER, timerOut);
    t.start();
}

The stop() function is almost the same, but we are removing the listeners.

public function stop():void{
			
	targetObject.removeEventListener(MouseEvent.MOUSE_DOWN, handleOverTarget);
	eventObject.removeEventListener(MouseEvent.MOUSE_DOWN, handleOverTarget);

	eventObject.removeEventListener(MouseEvent.MOUSE_MOVE, moveIt);

	targetObject.removeEventListener(MouseEvent.MOUSE_UP, throwIt);
	eventObject.removeEventListener(MouseEvent.MOUSE_UP, throwIt);

	targetObject.removeEventListener(MouseEvent.MOUSE_OUT, throwItOut);
	eventObject.removeEventListener(MouseEvent.MOUSE_OUT, throwItOut);

	t.removeEventListener(TimerEvent.TIMER, timerOut);
	t.stop();
}

Now we can listen to what is going on, let's go through each of these listener functions.


Step 6: MouseEvent.MOUSE_DOWN

We are going to be looking at the handleOverTarget event handler.

private function handleOverTarget(e:MouseEvent):void
{
    buttonDown = true;
    arMousePrevX = MousePrevX = MouseCurrX = eventObject.mouseX;
    arMousePrevY = MousePrevY = MouseCurrY = eventObject.mouseY;

    if(e.currentTarget == targetObject || !TargetClick)
    {
    	overTarget = true;
    }
    else if(e.target.toString().search(targetObject.toString()) < 0)
    {
    	overTarget = false;
    }

}

This function will be called when there is a MOUSE_DOWN event on either the event object or the target object. It is very important to note that if I put a listener on a parent object, the handler will even be called when the event occurs on a child. In this case my target object is a child of the event object. When I click on the target object this method will be called twice: first for the mouse down on the child then second for the mouse down on the parent. That is really important for this because we are going to be deciding whether the mouse down will be able to move our target object so we really need to be able to know whether that mouse down was on the child or not.

The first statement is pretty straightforward: set our class variable buttonDown to true.

The next two are pretty easy also, except I have introduced a couple new variables that will need to be put in our class variable list: MousePrevX, MousePrevY, arMousePrevX, arMousePrevY, MouseCurrX and MouseCurrY. These will be used a lot in the drag and pan functions, so I will wait to talk about them till then.

The if statement checks to see whether the object clicked is the target object. Remember, TargetClick was set to the argument we passed to init(), OnlyMoveOnTargetClick; if this is false we want to treat every child object as the target object when clicked. That's why we have the "|| !TargetClick" check.

That's the easy part. The next part is a little trickier.

The e.currentTarget returns the object that triggered the event. The e.target will return the object that was the actual target. So I could say this, right?


if(e.target == targetObject || !TargetClick)
{
	overTarget = true;
}
else
{
	overTarget = false;
}

That is simple enough, but it's wrong. What if my target object has children? Then my e.currentTarget may be the targetObject but the e.target is targetObject's child and will not match. We want this to move even if we are mousing down on a child object.

So here comes String.search to the rescue. If our currentTarget is not our targetObject, we use an "else if" to see if we can find our target object in the target. e.target.toString() will produce something like this: "application2.eventobject3.targetobject2.targetchild4" for our target object where targetObject.toString() will produce something like this "application2.eventobject3.targetobject2" all I need to do to find out if our target is a child of our targetObject is by this:

e.target.toString().search(targetObject.toString())

If there is a match it will return the first index of the match, or if there is not a match it will return a -1, so we can just see if it is greater than -1 and viola, we have found if the object being clicked on is a child of our targetObject.

(We could check the children or parent(s) of the object via the getChildAt() function and parent property, but this is a neat alternative.)


Step 7: TimerOut and Pan

The timer function is pretty easy too, especially since we have done this before. Well, almost done this before. When we have dragged around our little targetObject a bit and decide we don't want to let it go, we just love it too much, and abruptly stop the mouse, what would happen if you let go of the mouse button at that point? well, what do you think would happen? I am not going to answer that for you, I am just going to help you with the code to keep it from happening. In the final code, comment these three lines out. This should look really familiar, we just used this in the button down handler, except for one variable, MouseDragged. We are going to use that when we call our other function:

private function timerOut(e:TimerEvent):void
{
    MouseDragged = false;
    arMousePrevX = MousePrevX = MouseCurrX = eventObject.mouseX;
    arMousePrevY = MousePrevY = MouseCurrY = eventObject.mouseY;
}

So, if you are asking why we need this timer event, you probably didn't try and take it out to see what was happening. So do that.

This next function is one of our main functions; it is the pan function. There is a lot involved so let's dive into our pseudo-code:

private function moveIt(e:MouseEvent):void
{
	/*ok, so what do we need this function to do?
     *it needs to pan our target object.
     *so lets see if we are over our target object
     */

     //if(we are over our target object)
     //{

    	// what tools are we going to need to pan?
        // well, maybe we should check to see if the button is down

        //if(button is down)
        //{

        // we might need to set the button down variable. buttonDown = true;
        // and if we are in this function at this point our button is down and
		// the mouse has moved -- that's a drag: so MouseDragged = true;

        // if we are moving the object according to the mouse move then we should
		// probably know where our mouse is : MouseCurrX,Y = current MouseX,Y;

        // this is an introduction to our artificial mouse prev, which will be explained
		// in the next function. The ar stands for 'artificial' or 'after release',
		// whichever you prefer. That needs to be set to our actual previous mouse pos.
        // arMousePrevX = MousePrevX;
        // arMousePrevY = MousePrevY;

        // then we need to actually move the targetObject,
        // but remember our variables, moveX and moveY, so:
        // if moveX move x;
        // if moveY move y;

        // we need to reset our Decay (friction) back to the original state:
        //Decay = originalDecay;
    // that should finish the if
    //}
    //what else?
    //{
    	// we set our buttonDown to true before, so lets set it to false here.
        //buttonDown = false;
        // if this isn't a target click, we should set our overTarget to false so:
        //if(!TargetClick)
        	//overTarget = false;
    // that's it.
    //}

    // there are a few things that we want to happen regardless of the conditions.
    // first, we need to set our mousePrevX,Y variable -- BEFORE the mouse is
	// moved again!
    //MousePrevX = eventObject.mouseX;
    //MousePrevY = eventObject.mouseY;

    // Here are two more variables to keep track of: xOpposideEdge and yOppositeEdge
    // we are testing to see what the size of our target object is in relation
	// to our event object; if one is bigger we need to change the behavior of the bounce.
    // if(targetObject.width > eventObject.width){xOppositeEdge = true;}
    // else{xOppositeEdge = false;}

   // if(targetObject.height > eventObject.height){yOppositeEdge = true;}
   // else{yOppositeEdge = false;}

    // and finally we need to stop and restart our timer.
    //t.stop();
    //t.start();
//}

I admit this is a little more psuedo-y than the last; that is for two reasons: one, you don't know what is coming, and two, I am just really excited to get to the code:

private function moveIt(e:MouseEvent):void
{
	// in our pseudo-code this was two conditions but we can combine then to one,
    // we test to see if our event was a button down, and if we are over our target,
    // if we are then let's move the target object.
    if(e.buttonDown && overTarget)
    {

        buttonDown = true;
        MouseDragged = true;
        MouseCurrX = eventObject.mouseX;
        MouseCurrY = eventObject.mouseY;
        // here is the artificial / after release one. again, well get to that.
        arMousePrevX = MousePrevX;
        arMousePrevY = MousePrevY;

        /* this is the important one, in our pseudo it was "move the target object",
         * so we need to translate that. To help us we'll create a local variable
         * Topper for the top, and Sider for the side.
         * so let's look at Topper (the same will apply to Sider).
         * eventObject.mouseY looks at where our mouse is inside of the eventObject.
         * We take our MousePrev away from that, and that will give us how much the object
         * should travel, so the Y might travel 2 pixels, or -2 pixels depending on
         * direction, so we take that change and add it to the target's current
         * position, but that isn't happening yet, this is just a var.
         */
        var Topper:int = (eventObject.mouseY - MousePrevY) + targetObject.y;
        var Sider:int = (eventObject.mouseX - MousePrevX) + targetObject.x;

        // here is where it happens, if moveY (remember from the pseudo-code) then we
        // can set the position of the target.
        if(moveY){targetObject.y = Topper;}
        if(moveX){targetObject.x = Sider;}
		// so really we are just using Topper and Sider to temporarily store where the
		// target object should move to

        Decay = originalDecay;
    }
    else
    {
        buttonDown = false;
        if(!TargetClick)
        overTarget = false;
    }

    MousePrevX = eventObject.mouseX;
    MousePrevY = eventObject.mouseY;


    if(targetObject.width > eventObject.width){xOppositeEdge = true;}
    else{xOppositeEdge = false;}

    if(targetObject.height > eventObject.height){yOppositeEdge = true;}
    else{yOppositeEdge = false;}


    t.stop();
    t.start();
}

And now we are panning.


Step 8: Throw, It, Out, Repeater!

This is the second big function and with this we will have our class built! Ready to pan and throw any object you see fit! There are two functions that we need to address first: throwIt(), which we set as a handler to the MOUSE_UP event, and throwItOut(), which we set as a handler to the MOUSE_OUT event.

private function throwIt(e:MouseEvent):void
{
    buttonDown = false;
    if(MouseDragged){
   		eventObject.addEventListener(Event.ENTER_FRAME, theRepeater);
    }
}

private function throwItOut(e:MouseEvent):void
{
    buttonDown = false;
    if(e.relatedObject == null || e.relatedObject == eventObject.parent){
    	eventObject.addEventListener(Event.ENTER_FRAME, theRepeater);
    }
}

These two functions are almost the same (after all, they are doing the same thing just at different times). In them we set the buttonDown to false, because this is a mouse up event, and check to see if the mouse was dragged, using either MouseDragged (which we set in the last function) or by checking "e.relatedObject"; the object that the mouse just moved out of.

If it was dragged we add another listener. The ENTER_FRAME event is a really cool one. This is the basis of our animation; every time we enter a new frame the throw() function will be run. That is what allows us to simulate a mouse drag after release (remember the arMousePrevX,Y variable? That is what it is for). And that is all the throw is really doing, simulating a mouse drag, without a mouse of course. So we have pretty much already got the function we need, except we need to replace the calls to the current mouse position to our artificial mouse position.

Almost got a little ahead of myself there. So with these two event functions, throwIt and throwItOut, they are doing the same thing but that if in the second function is worth mentioning. I struggled a while trying to get this functionality, until I looked at the event a little closer. The problem was trying to get the target object to act as though I let go of the button when the cursor left the event object. Go ahead, try and do this without the e.relatedObject. I almost had it a few times, but couldn't get it right. What the e.relatedObject does is finds what object you are on, after the event is called. That is why it is soooo cool. When our cursor leaves the movie altogether, it returns a null, otherwise it will return the object you are on, so we can check to see if e.relatedObject is null or is a parent to the eventObject. That produces the correct action we are looking for.

In the above functions we set up calls to theRepeater(). This will be the throw function, and remember it will be called every time we enter a new frame. Let's step through this line by line:

private function theRepeater(e:Event):void
{
	 // the timer must be stopped, try removing this and see what happens.
    t.stop();

    // here is a local variable that will hold the current (fake) cursor position.
    // well it is only "fake" after the first time around.
    var oldxer:Number = MouseCurrX;
    var oldyer:Number = MouseCurrY;

    // now, just like we did before, we need to find the difference between our current
    // and previous position. so how is this different from before? why?
    var xDiff:Number = MouseCurrX - arMousePrevX;
    var yDiff:Number = MouseCurrY - arMousePrevY;

    // if the button is down, we aren't going to move any more, the button will stop the action in this case.
    if(!buttonDown)
    {
    	// take the difference and times it by the decay. this will give us the new
        // difference, which will be slightly smaller than the last one, which is how
        // we get the friction effect with this.
		// e.g. if Decay is 0.5 then the distance moved will halve every frame.
        xDiff = xDiff * Decay;
        yDiff = yDiff * Decay;

        // next is one of the confusing parts for me, this doesn't move the object at
        // all, it just tests to see if our targetObject has reached the edge. if it has,
        // we need to bounce it back. (this could be changed to some other action if you
        // want, you could even remove it, what happens if you do? try it!

        // in the pan function we set this variable, OppositeEdge, this is where we will
        // use that 'if the targetObject is bigger than the Event Object' that we set in
        // the init() function. I am only going to walk through the x here because the y is
		// almost the same (what is different? why? think about it!)
        if(xOppositeEdge)
        {
             /* so first, "the width of the eventObject, - the width of the targetObject - 50",
             * here, the width of the targetObject is greater than that of the eventObject
             * this will allow the opposite edge of the target object to be 50 px in from
             * the opposite edge. If you go to the example movie and shrink the image to
			 * 10% and throw it around, then increase the size to 200% and try and notice
             * what edge is doing what, then you will see the difference between the bounces.
             * That is the best way to understand this part.
             */
        	if(targetObject.x < (eventObject.width - targetObject.width - 50))
			{
				xDiff = -1 * xDiff;
				targetObject.x = eventObject.width - targetObject.width - 50;
			}
            // this does the same thing for the other edge.
        	if(targetObject.x > 50)
			{
				xDiff = -1 * xDiff;
				targetObject.x = 50;
			}
        }
        // this is if the target object is smaller than the eventObject.
        else
        {
        	/* so again we are testing the edges of the targetObject against the
             * event object. This time we are dealing with the same edge (well,
             * 5px outside the edge). So this will bounce like it is hitting a wall.
             */
            if(targetObject.x <  -5)
			{
				xDiff = -1 * xDiff;
				targetObject.x = -5;
			}
            if(targetObject.x > (eventObject.width - (targetObject.width - 5)))
			{
				xDiff = -1 * xDiff;
				targetObject.x = eventObject.width - (targetObject.width - 5);
			}
        }

        if(yOppositeEdge)
        {
            if(targetObject.y < (eventObject.height - targetObject.height - 50))
			{
				yDiff = -1 * yDiff;
				targetObject.y = eventObject.height - targetObject.height - 50;
			}
            if(targetObject.y > 50)
			{
				yDiff = -1 * yDiff;
				targetObject.y = 50;
			}

        }
        else
        {
            if(targetObject.y < -5)
			{
				yDiff = -1 * yDiff;
				targetObject.y = -5;
			}
            if(targetObject.y > (eventObject.height - (targetObject.height - 5)))
			{
				yDiff = -1 * yDiff;
				targetObject.y = eventObject.height - (targetObject.height - 5);
			}
        }

        // well, if you have questions about that part, just post a comment about it and I will answer them.

        // here are the sider and Topper vars (just like the ones from the pan function).
        var sider:int = xDiff + targetObject.x;
        var Topper:int = yDiff + targetObject.y;

        // we need to set this ready for the next go around.
        MouseCurrX = MouseCurrX + xDiff;
        MouseCurrY = MouseCurrY + yDiff;

        // and then the if moveX,Y (again like the pan function)
        if(moveY){targetObject.y = Topper;}
        if(moveX){targetObject.x = sider; }

        // and now set our artificial mouse prev
        arMousePrevX = oldxer;
        arMousePrevY = oldyer;

        // and if we are not in frictionless mode (OriginalDecay = 1)
        // we are going to subtract a small amount from our decay, to
        // gives it a little more natural easing.
        if(originalDecay < 1)
		{
        	Decay = Decay - .004;
		}

    // so the moving is done.
    }
    // if the button is down we need to remove the listener.
	else
    {
	    eventObject.removeEventListener(Event.ENTER_FRAME, theRepeater);
    }

    // now we need to check if the effect is over, which is if our x and y diffs are less than 1px.
    if((Math.abs(xDiff) < 1 && Math.abs(yDiff) < 1))
    {
    	eventObject.removeEventListener(Event.ENTER_FRAME, theRepeater);
    }
}

And with that, our class is finished.


Step 9: The Completed Class Code

You can grab the completed code from the Source zip, linked at the top of the tutorial. It's in the PanAndThrow.as class.


Step 10: Do Something With It

To do something with this we need to go back to the MXML and add a few lines of code. Add our declaration in the global variable section, fill in the decay method and call our pan and throw init() function. With all that added, here is the complete MXML file:

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" frameRate="24" layout="absolute" creationComplete="init()" backgroundColor="#888888" >

<mx:Script>
	<![CDATA[
		import mx.states.SetStyle;
		import mx.effects.Move;
        // be sure to import our new class!
		import classes.PanAndThrow;
		import mx.containers.HBox;
		import mx.containers.Box;
		
		
		// here we initialise our pan and throw class
		public var pt:PanAndThrow = new PanAndThrow();
		private var imageWidth:Number = 0;
        private var imageHeight:Number = 0;
        private var mover:Move = new Move();
		
		private function init():void {
			
			control.addEventListener(MouseEvent.CLICK, controlClick);
			mover.target = control;

            //and here is the init call.
			pt.init(inside, outside, sldDecay.value, true, true, false);

		}

		private function zoom():void {
            inside.width = (imageWidth*hSlider.value)/100;
            inside.height = (imageHeight*hSlider.value)/100;
        }
        private function smoothImage(ev:Event):void{
	        var bmp:Bitmap = ev.target.content as Bitmap;
	        bmp.smoothing = true;
	
	        imageWidth=inside.width;
	        imageHeight=inside.height;
    	}

    	private function changeDecay():void
    	{
        	// now we can access our public setter from here.
    		pt.decay = sldDecay.value;
    	}

    	private function controlClick(e:MouseEvent):void
    	{
    		mover.play();

    		if(control.y != -5){
	    		mover.stop();
	    		mover.yTo = -5;
	    		mover.play();
    		}
    		else if(e.target == control){
	    		mover.stop();
	    		mover.yTo = (control.height - 10) * -1;
	    		mover.play();
	    	}
	
    	}
		
	]]>
</mx:Script>


<mx:Canvas id="outside" height="100%" width="100%" verticalScrollPolicy="off" horizontalScrollPolicy="off" >
		<mx:Image id="inside" source="@Embed('img/selfportrait.jpg')" creationComplete="smoothImage(event);" />
</mx:Canvas>


<mx:VBox id="control" backgroundColor="#000000" backgroundAlpha=".6" height="120"  width="200" cornerRadius="5" borderStyle="solid" borderThickness="0" top="-5" left="-5" paddingTop="10" paddingLeft="10" >
	
	<mx:HSlider id="hSlider" minimum="10" maximum="200" value="100" allowTrackClick="true" liveDragging="true" change="zoom()" />
	<mx:Label color="#aaaaaa" text="Zoom" />
	
	<mx:HSlider id="sldDecay" minimum=".1" maximum="1" value=".7" allowTrackClick="true" liveDragging="true" change="changeDecay()"/>
	<mx:Label color="#aaaaaa" text="Decay" />
</mx:VBox>
</mx:Application>

Conclusion

Now you have a working pan and throw class! I hope you are as excited as I am. There is a lot here and I hope I was able to cover everything and not make this tutorial too long.

I hope you liked this tutorial, thanks for reading! Please post in the comments if you have any questions.

Advertisement