Статьи

Бросать объекты путем создания класса PanAndThrow

В этом уроке мы будем макетировать и заканчивать классы панорамирования и броска, которые позволят нам добавить этот эффект к любому элементу, который мы хотим. Для этого мы создадим средство просмотра изображений, но не обычное средство просмотра. Здесь мы будем иметь масштабирование, метание, панорамирование. Почти звучит как приложение для ниндзя, а?


Класс Pan и Throw позволит вам добавить функциональность Pan и Throw к любому объекту ActionScript. Хотя это руководство предназначено специально для Flex, сам класс можно использовать везде, где есть ActionScript. Я видел этот эффект на нескольких веб-сайтах, а затем в Photoshop CS4 и решил, что хочу, чтобы это тоже было в моих проектах.

Есть много приложений для этого эффекта; В этом уроке мы будем использовать средство просмотра изображений, которое позволяет увеличивать и уменьшать изображение и изменять трение, которое использует эффект броска. Тем не менее, этот урок на самом деле не о просмотрщике изображений, а о создании классов панорамирования и броска. Итак, начнем с этого. Откройте ваш любимый редактор Flex и запустите проект; для получения информации об этом в Flex Builder см. Adobe LiveDocs . Как только ваш проект создан, откройте файл MXML. Нам нужно добавить к этому некоторый код, прежде чем мы создадим наш класс.


Поскольку это не основная часть урока, я не собираюсь проводить здесь много времени. Если у вас есть какие-либо вопросы по этому разделу, которые не охвачены, вы можете задать их в комментариях ниже. Во-первых, вот объекты MXML для помещения в приложение:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
<?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>

Вы заметите четыре функции, вызываемые в тегах: init (), changeDecay (), smoothImage () и zoom (). Нам нужно написать эти функции. Это код между тегами <mx: script>:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
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();
    }
 
}

После того, как у вас есть MXML, вам нужно создать папку с именем «classes» в той же папке, что и ваш файл MXML. (Если используется Flash, папка должна находиться в том же каталоге, что и ваш FLA-файл.) Это наш пакет классов и туда, куда будет помещен файл PanAndThrow.as. Во Flex Builder создайте новый класс, поместите его в пакет классов и назовите его PanAndThrow; это создаст ваш класс — стиль по умолчанию.


Вот наш основной класс PanAndThrow. Сохраните его как PanAndThrow.as в вашей новой папке «classes».

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
//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()
    {
 
    }
 
}

Какие переменные и функции нам нужны в нашем классе PanAndThrow? Чтобы получить это, вы можете спросить себя: «Что должен делать мой класс, что ему нужно знать, и что ему нужно для этого?» Итак, давайте создадим некоторый псевдокод.

Когда я впервые разработал этот класс, я поместил все в конструктор, но это привело к проблеме, когда я создал методы start и stop из-за области действия. Я не мог создать экземпляр этого класса в глобальном масштабе со всей необходимой информацией. Поэтому я сделал функцию init (), чтобы экземпляр можно было запускать и останавливать вне класса.


«Псевдокод» означает просто фальшивый код, который мы можем использовать, чтобы помочь себе подумать о том, какой реальный код нам понадобится.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package classes
 
public class PanAndThrow
{
    /*These will be the variables that we make.
     * anObjectToThrow;
     * anObjectToThrowItIn;
     * ObjectLocation;
     * PreviousObjectLocation;
     * Decay;
     * 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();
     * stop();
     * start();
     * pan();
     * throw();
     */
 
}

Теперь, когда у нас есть некоторый псевдокод, мы можем начать создавать класс. Давайте начнем с функции init (). Это также приведет нас к одному из принципов объектно-ориентированного программирования, который называется инкапсуляция , который касается доступа к частям кода.

Этот код должен идти в классе PanAndThrow, который мы только начали. (Не знаете где? Ознакомьтесь с кратким советом по классу документов .)

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// thanks to OOP, a lower level class and an upper level class (one that extends
// the lower level class) can be used.
// Sprite class.
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();
}

Просто пара вещей, на которые я хочу обратить внимание. В функции для init я установил несколько аргументов равными значению. Это означает, что я даю им значение по умолчанию, что делает их необязательными. При установке значений по умолчанию для аргументов функции, они должны быть последними параметрами — вы не можете иметь обязательную переменную после необязательной. Я добавил переменные по умолчанию, чтобы сделать вызов короче, если мы используем настройки по умолчанию. Я могу позвонить PanAndThrow (движитель, Eventer); и будет сделано, вместо PanAndThrow (mover, enventer, decayer, yVal, …) и так далее.

Вы когда-нибудь задумывались, что означает «приватный» или «публичный» перед функциями и переменными? Это экспозиция объекта. «Открытый» объект может быть доступен любому другому классу; «закрытый» объект может быть виден только другим членам этого класса; «Защищенный» объект скрыт от всего, кроме классов, находящихся в одном пакете.

Мы хотим иметь возможность изменить затухание из нашего MXML, поэтому нам нужен открытый хук, чтобы добраться до нашей частной переменной; вот где входят функции получения и установки:

1
2
3
4
5
6
private var originalDecay:Number = .9;
 
public function get decay():Number
{
    return originalDecay;
}

Это функция «получателя». Это означает, что для внешних классов это выглядит так, как будто у класса PanAndThrow есть открытая переменная с именем «decay». Когда они попытаются получить к нему доступ, мы вернем им значение нашей (приватной) переменной originalDecay.

Функции сеттера почти одинаковы, но позволяют внешним классам изменять значение нашей «фиктивной» публичной переменной:

1
2
3
4
public function set decay(value:Number):void
{
    originalDecay = value;
}

Они полезны, потому что вы можете поместить логику в установщик, чтобы ограничить то, что входит в вашу личную переменную. Например, если вы поместите Number в тег MXML для поля, вы получите заданную высоту; если вы введете% (делая число строкой), вы получите процентную высоту. Это встроено в код для установки высоты блока. Теперь, когда у нас есть наши методы получения и установки, вы можете получить доступ к переменной затухания следующим образом:

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

У нас есть наш класс, некоторые локальные переменные и функция init (). Давайте сделаем что-нибудь сейчас. В конце функции init () мы вызвали «start ();» Итак, давайте сделаем функцию запуска. В основном это просто группа слушателей:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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();
}

Функция stop () почти такая же, но мы удаляем слушателей.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
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();
}

Теперь мы можем слушать, что происходит, давайте рассмотрим каждую из этих функций слушателя.


Мы будем смотреть на обработчик события handleOverTarget.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
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;
    }
 
}

Эта функция будет вызываться при возникновении события MOUSE_DOWN в объекте события или целевом объекте. Очень важно отметить, что если я помещу слушателя в родительский объект, обработчик будет даже вызван, когда событие произойдет в дочернем объекте. В этом случае мой целевой объект является дочерним по отношению к объекту события. Когда я щелкаю по целевому объекту, этот метод вызывается дважды: сначала для мыши, нажатой на дочернем элементе, а затем для мыши, расположенной вниз для родительского элемента. Это действительно важно для этого, потому что мы собираемся решить, сможет ли мышь вниз перемещать наш целевой объект, поэтому нам действительно нужно знать, была ли эта мышь на ребенке или нет.

Первое утверждение довольно просто: установите для нашей переменной класса buttonDown значение true.

Следующие два также довольно просты, за исключением того, что я ввел пару новых переменных, которые нужно будет поместить в наш список переменных класса: MousePrevX, MousePrevY, arMousePrevX, arMousePrevY, MouseCurrX и MouseCurrY. Они будут часто использоваться в функциях перетаскивания и панорамирования, так что я подожду, чтобы поговорить о них до тех пор.

Оператор if проверяет, является ли выбранный объект целевым объектом. Помните, что TargetClick был установлен на аргумент, который мы передали init (), OnlyMoveOnTargetClick; если это ложь, мы хотим рассматривать каждый дочерний объект как целевой объект при нажатии. Вот почему у нас есть проверка «||! TargetClick».

Это легкая часть. Следующая часть немного сложнее.

E.currentTarget возвращает объект, который вызвал событие. E.target вернет объект, который был фактической целью. Так что я мог бы сказать это, верно?

1
2
3
4
5
6
7
8
if(e.target == targetObject || !TargetClick)
{
    overTarget = true;
}
else
{
    overTarget = false;
}

Это достаточно просто, но это неправильно. Что если у моего целевого объекта есть дети? Тогда мой e.currentTarget может быть targetObject, но e.target является дочерним объектом targetObject и не будет совпадать. Мы хотим, чтобы это двигалось, даже если мы наведемся на дочерний объект.

Итак, здесь на помощь приходит String.search. Если наш currentTarget не является нашим targetObject, мы используем «else if», чтобы посмотреть, сможем ли мы найти наш целевой объект в цели. e.target.toString () будет производить что-то вроде этого: «application2.eventobject3.targetobject2.targetchild4» для нашего целевого объекта, где targetObject.toString () будет производить что-то вроде этого «application2.eventobject3.targetobject2» все, что мне нужно сделать, чтобы узнать, является ли наша цель дочерней по отношению к нашему targetObject следующим образом:

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

Если есть совпадение, он вернет первый индекс совпадения, или, если совпадения нет, он вернет -1, поэтому мы можем просто посмотреть, если оно больше -1 и альта, мы нашли, если объект нажатие является дочерним элементом нашего targetObject.

(Мы можем проверить дочерние или родительские (ие) объекты с помощью функции getChildAt () и родительского свойства, но это хорошая альтернатива.)


Функция таймера тоже довольно проста, тем более что мы делали это раньше. Ну, почти сделал это раньше. Когда мы немного обошли наш маленький объект targetObject и решили, что не хотим его отпускать, мы просто слишком сильно его любим и внезапно остановили мышь, что произойдет, если вы отпустите кнопку мыши в этой точке? ну, как вы думаете, что произойдет? Я не собираюсь отвечать на это за вас, я просто помогу вам с кодом, чтобы не допустить этого. В последнем коде закомментируйте эти три строки. Это должно выглядеть очень знакомо, мы просто использовали это в обработчике нажатия кнопки, за исключением одной переменной MouseDragged. Мы будем использовать это, когда будем вызывать нашу другую функцию:

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

Итак, если вы спрашиваете, зачем нам нужно это событие таймера, вы, вероятно, не пытались его снять, чтобы посмотреть, что происходит. Так сделай это.

Эта следующая функция является одной из наших основных функций; это функция панорамирования. Здесь много всего, поэтому давайте углубимся в наш псевдокод:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
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.
        // 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.
        // whichever you prefer.
        // 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(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();
//}

Я признаю, что это немного более psuedo-y, чем в прошлом; это по двум причинам: во-первых, вы не знаете, что произойдет, и во-вторых, я просто очень рад получить код:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
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.
        arMousePrevX = MousePrevX;
        arMousePrevY = MousePrevY;
 
        /* this is the important one, in our pseudo it was «move the target object»,
         * so we need to translate that.
         * 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();
}

И сейчас мы панорамируем.


Это вторая большая функция, и с ее помощью мы построим наш класс! Готов панорамировать и бросать любые объекты, которые вы считаете нужным! Сначала нужно обратиться к двум функциям: throwIt (), который мы устанавливаем в качестве обработчика события MOUSE_UP, и throwItOut (), который мы устанавливаем в качестве обработчика события MOUSE_OUT.

1
private function throwIt(e:MouseEvent):void { buttonDown = false;

Эти две функции почти одинаковы (в конце концов, они делают одно и то же только в разное время). В них мы устанавливаем для buttonDown значение false, потому что это событие перемещения мыши, и проверяем, не перетаскивалась ли мышь, используя либо MouseDragged (который мы установили в последней функции), либо проверяя «e.relatedObject»; объект, из которого только что вышла мышь.

Если его перетянули, мы добавили еще одного слушателя. Событие ENTER_FRAME действительно классное. Это основа нашей анимации; каждый раз, когда мы вводим новый кадр, запускается функция throw (). Это то, что позволяет нам моделировать перетаскивание мышью после освобождения (вспомните переменную arMousePrevX, Y? Вот для чего она). И это все, что делает бросок, имитируя перетаскивание мыши, конечно же, без мыши. Таким образом, мы почти уже получили нужную нам функцию, за исключением того, что нам нужно заменить вызовы текущей позиции мыши на нашу искусственную позицию мыши.

Почти опередил себя там. Таким образом, с этими двумя функциями событий, throwIt и throwItOut, они делают то же самое, но это стоит упомянуть во второй функции. Я боролся, пытаясь получить эту функциональность, пока не посмотрел на событие немного ближе. Проблема была в том, чтобы заставить целевой объект действовать так, как будто я отпустил кнопку, когда курсор покинул объект события. Попробуй и попробуй сделать это без объекта e.relatedObject. У меня почти было это несколько раз, но я не мог понять это правильно. То, что делает e.relatedObject, находит, на каком объекте вы находитесь, после вызова события. Вот почему это так круто. Когда наш курсор полностью покидает фильм, он возвращает ноль, иначе он вернет объект, на котором вы находитесь, поэтому мы можем проверить, является ли e.relatedObject нулевым или является родительским для события eventObject. Это производит правильное действие, которое мы ищем.

В вышеприведенных функциях мы настраиваем вызовы theRepeater (). Это будет функция throw, и помните, что она будет вызываться каждый раз, когда мы вводим новый кадр. Давайте шаг за шагом пройдем эту строку:

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
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.
    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.
        // difference, which will be slightly smaller than the last one, which is how
        // we get the friction effect with this.
        // eg 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.
        // we need to bounce it back.
        // want, you could even remove it, what happens if you do?
 
        // 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.
        // 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.
             * 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.
             * 5px outside the edge).
             */
            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);
    }
}

И с этим наш класс закончен.


Вы можете получить готовый код из исходного zip-архива, связанного в верхней части руководства. Это в классе PanAndThrow.as.


Чтобы сделать что-то с этим, нам нужно вернуться к MXML и добавить несколько строк кода. Добавьте нашу декларацию в раздел глобальных переменных, заполните метод decay и вызовите нашу функцию pan и throw init (). Со всем, что добавлено, вот полный файл MXML:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
<?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>

Теперь у вас есть рабочая сковорода и класс броска! Я надеюсь, что вы так же взволнованы, как и я. Здесь много всего, и я надеюсь, что смог охватить все и не сделать этот урок слишком длинным.

Надеюсь, вам понравился этот урок, спасибо за чтение! Пожалуйста, оставьте в комментариях, если у вас есть какие-либо вопросы.