Статьи

AS3 101: события — Basix

В этой главе AS3 101 мы углубимся в механику системы событий Flash. Если вы следили за этим до сих пор, вы видели события в использовании, начиная с первого эпизода серии . Редактор и я почувствовали, что пришло время написать что-то, что должно быть официально включено в учебную программу, поэтому, если вы когда-нибудь видели эти строки кода о добавлении прослушивателей или диспетчеризации событий, и не совсем прижились, то это учебник для вас.

Уже существует учебник Activetuts + по основам событий , поэтому основное внимание в этом учебнике будет уделено отправке событий из ваших собственных классов, включая создание пользовательских типов событий и объектов.

Чтобы добиться успеха в этом руководстве, вы должны чувствовать себя комфортно в написании и использовании собственных классов в ActionScript 3, а также в безопасности при использовании существующих событий, предоставляемых Flash, таких как MouseEvent.CLICK или Event.ENTER_FRAME . Мы сосредоточимся в первую очередь на отправке пользовательских событий из пользовательских классов.


Мы потратим много времени на теорию для этого урока, но в конце мы создадим простой элемент управления ползунком, который отправляет свои собственные события:


Этот пример на самом деле является довольно простым примером того, почему вы хотите отправлять свои собственные события. Когда вы пишете свои собственные классы, вы в идеале держите их в собственных черных ящиках и инкапсулируете. Но вам все равно нужно, чтобы различные объекты взаимодействовали, чтобы создать полезную программу.

Модель событий, предоставляемая ActionScript 3, является довольно хорошим и удобным способом для облегчения взаимодействия между вашими классами, сохраняя при этом разделение обязанностей в ваших классах. Так что, если мы напишем наш собственный пользовательский класс, такой как ActiveSlider выше ActiveSlider классов ActiveSlider , нам нужно будет позволить другим объектам знать, когда ползунок изменяет свое значение. Если ползунок может отправлять свое собственное событие изменения, то другие объекты, которым необходимо знать эту информацию, могут легко подписаться и получить уведомление.

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


У меня хорошие новости: рассылка ваших собственных событий на самом деле очень проста. Это один из основных принципов ActionScript 3, встроенный в проигрыватель Flash Player, и поэтому вам нужно сделать только одну вещь, чтобы получить возможность отправлять события. Это одна вещь:

Расширить EventDispatcher

Вот и все: при написании вашего класса используйте строку:

1
public class MyClass extends EventDispatcher {

Конечно, вам нужно импортировать EventDispatcher , который находится в пакете flash.events . Скорее всего, вам понадобятся другие классы в пакете, поэтому может быть удобнее просто импортировать пакет с подстановочным знаком.

1
import flash.events.*;

Теперь вы настроены на отправку события. Все, что вам нужно сделать сейчас, это вызвать метод, предоставленный EventDispatcher именем dispatchEvent . Легко понять, нет?

Когда вы вызываете dispatchEvent , вы должны предоставить хотя бы один аргумент, объект Event . Все встроенные объекты Event находятся в пакете flash.events , так что здесь вам пригодится импорт с использованием подстановочных знаков. Каждый тип объекта Event будет иметь свои собственные требования, но чаще всего вам просто нужно передать ему только один аргумент. Этот аргумент является типом события, который представляет собой String которая называет событие, например, "click" или "complete" . Они чаще пишутся как MouseEvent.CLICK или Event.COMPLETE , но конечный результат тот же; это идентификатор, который отделяет один тип события от другого и позволяет одному объекту Event управлять несколькими типами событий.

Итак, собрав все вместе, если вы хотите отправить "complete" событие, вы можете сделать это так:

1
dispatchEvent(new Event(Event.COMPLETE));

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


Любой другой объект в вашем приложении теперь может прослушивать ваши пользовательские события. Еще одна хорошая новость: это ничем не отличается от регистрации событий для встроенных классов. На предыдущем шаге мы настроили наш гипотетический класс для отправки события COMPLETE . Чтобы прослушать это событие, мы могли бы написать эту строку где-нибудь еще в нашей программе:

1
2
3
4
5
var myObject:MyClass = new MyClass();
myObject.addEventListener(Event.COMPLETE, myCompleteHandler);
function myCompleteHandler(e:Event):void {
    trace(«My object completes me.»);
}

Вот и все. Например, это должно быть знакомо любому, кто подключил COMPLETE слушатель к Loader , поэтому я не буду останавливаться на этом подробнее.


Где вы фактически размещаете строку кода dispatchEvent требует некоторого рассмотрения. Как правило, это должна быть последняя строка кода метода, в котором он написан. Это делается для того, чтобы любой другой код, который также выполняется в этом методе, мог устанавливать свойства или иным образом обновлять внутреннее состояние объекта. Посредством диспетчеризации после завершения этого внутреннего обновления объект находится в «чистом» состоянии во время отправки.

Рассмотрим, например, наш рабочий пример. Допустим, событие COMPLETE связано с обработкой некоторых данных; куча данных настолько велика, что для полной обработки потребуется несколько секунд, поэтому целью объекта является асинхронная обработка, чтобы не блокировать пользовательский интерфейс. И мы отправляем событие COMPLETE чтобы сказать, что данные были обработаны.

Теперь предположим, что основной метод выглядит примерно так:

1
2
3
4
5
6
private function processDataChunk():void {
    _data += someDataProcess();
    if (done()) {
        closeData();
    }
}

Хорошо, не очень реалистично, но это иллюстрирует суть. Мы продолжаем наращивать внутренние данные, пока какая-то другая внутренняя логика не определит, что мы закончили, и в этот момент мы выписываем некоторые последние биты данных, чтобы закрыть операцию.

Теперь давайте добавим вызов dispatchEvent :

1
2
3
4
5
6
7
private function processDataChunk():void {
    _data += someDataProcess();
    if (done()) {
        dispatchEvent(new Event(Event.COMPLETE));
        closeData();
    }
}

В чем проблема с этим подходом? Любой код, который выполняется в слушателях для события COMPLETE будет выполняться до closeData метода closeData . Следовательно, состояние диспетчера изменяется более одного раза в пределах диапазона метода processDataChunk и не является «стабильным» до closeData вызова closeData . Тем не менее, мы говорим всем нашим слушателям, что мы готовы до этого звонка. Это может привести к некоторым трудным для отслеживания ошибкам, когда один объект утверждает, что он COMPLETE но на самом деле это не так. Очевидное решение заключается в переключении некоторых строк:

1
2
3
4
5
6
7
private function processDataChunk():void {
    _data += someDataProcess();
    if (done()) {
        closeData();
        dispatchEvent(new Event(Event.COMPLETE));
    }
}

Вы все настроены на отправку ваших собственных событий. Теперь, что вы должны отправить? Есть несколько вариантов для рассмотрения:

  1. Просто повторно используйте объект события и тип события, уже предоставленные Flash Player
  2. Повторное использование существующего объекта события, но с предоставлением пользовательского типа события
  3. Переотправить существующее событие
  4. Создать пользовательский объект события
  5. Толчок против тяги

Этот первый вариант мы уже видели в наших предыдущих примерах. Нам необходимо отправить событие, связанное с завершением какого-либо процесса, и, как это происходит, Flash предоставляет тип события ( COMPLETE ), связанный с объектом события ( Event ), который соответствует нашим критериям. Нам не нужно предоставлять дополнительные данные о событии. Отправка события Event.COMPLETE — это все, что нам нужно.

Мы рассмотрим эти другие варианты в следующих шагах.


Как уже упоминалось в шаге «Отправка», типы событий являются просто String идентификаторами. Технически они могут быть любой String вам нравится. Обычно достаточно сделать это одним словом (например, «завершить» или «щелкнуть») или очень короткой фразой (например, «ioError» или «keyFocusChange»); он должен быть уникальным только в пределах области доступных событий данного диспетчера событий.

Кроме того, объекты Event (включая подклассы, такие как MouseEvent или ProgressEvent ) действительно не заботятся о том, какой тип события они дают при создании экземпляра. EventDispatcher с радостью отправит события любого идентификатора типа и любого класса (при условии, что это класс Event или подкласс).

В результате вы можете создать свой собственный тип события String , отправить его и настроить слушателей вместе с ним, и все будет хорошо. Это полезно, когда вы хотите отправить событие, но не можете найти хорошее представление о природе события во встроенных классах.

Например, у вас может быть класс, который действует в качестве координатора сразу для нескольких вещей: загрузить XML, загрузить некоторые изображения на основе данных XML и создать макет на основе размеров загруженных изображений, готовый для начальной анимации в какой момент вы хотите отправить событие. Хотя событие COMPLETE может быть подходящим, вам может показаться, что событие «ready» более уместно инкапсулирует значение.

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

01
02
03
04
05
06
07
08
09
10
11
public class MyClass extends EventDispatcher {
 
    // … A bunch of other stuff not shown.
 
    private function determineReadiness():void {
        if (everythingIsReady) {
            dispatchEvent(new Event(«ready»));
        }
    }
 
}

И код из другого места в той же программе:

1
2
3
4
5
6
var myObject:MyClass = new MyClass();
myObject.addEventListener(«ready», onObjectReady);
 
function onObjectReady(e:Event):void {
    // Do stuff now that it’s ready.
}

И это будет работать.

Тем не менее, стоит упомянуть, пока мы здесь, что вводить в String совпадающие String не рекомендуется. Вероятность ошибки высока, и система событий не скажет вам, что вы набрали "raedy" вместо "ready" . Тот факт, что система событий является гибкой и простой в использовании — просто передайте ей любую старую String для типа события — также является слабым местом. Ваш диспетчер с радостью примет слушателя ко всему , даже к "raedy" событию. Это на самом деле не согласовывает, какие типы событий зарегистрированы с какими типами событий фактически отправляются.

Чтобы предотвратить это, стандартный подход состоит в том, чтобы просто поместить String вы хотите использовать, в статическую константу, и никогда больше не использовать эту строковую литеральную String . Используйте только константу. Конечно, возможность опечаток просто велика, как и раньше, но если вы используете константу READY а не "ready" литерал String , неправильный тип вызовет предупреждение компилятора. Вы сможете быстро и легко исправить свою ошибку. Mistype с литералом String s не выдает ошибку компилятора и не выдает ошибку времени выполнения. Единственное, что происходит, — это то, что SWF не работает должным образом, потому что прослушиватель событий не срабатывает.

Учитывая это, чаще всего эти константы хранятся в связанном классе Event . Мы доберемся до пользовательских классов Event всего за несколько шагов. Но в ситуации, описанной на этом шаге (т. Е. Мы повторно используем класс Event но не тип события), я считаю более удобным просто сохранить эту константу в классе диспетчера. Таким образом, мы могли бы сделать это:

01
02
03
04
05
06
07
08
09
10
11
12
13
public class MyClass extends EventDispatcher {
 
    public static const READY:String = «ready»;
 
    // etc.
 
    private function determineReadiness():void {
        if (everythingIsReady) {
            dispatchEvent(new Event(READY));
        }
    }
 
}

И:

1
2
3
4
5
6
var myObject:MyClass = new MyClass();
myObject.addEventListener(READY, onObjectReady);
 
function onObjectReady(e:Event):void {
    // Do stuff now that it’s ready.
}

Это дает нам возможность хранить типы событий в константах, не создавая неудобств при создании целого класса Event который нам не нужен. Я должен подчеркнуть, что это стилистический выбор, и вы можете свободно использовать эту технику или нет. Я счел это оправданным объяснением, чтобы вы могли принять собственное обоснованное решение. В любом случае, вы должны хранить ваши пользовательские события типа String s в статических константах. Где эти статические константы определены, зависит от вас.


Есть несколько раз, когда один класс (назовем его ClassX ) владеет свойством, которое типизируется как другой класс (мы назовем его одним ClassY ), а сам он принадлежит третьему классу (как насчет ClassZ ?). ClassX прослушивает событие от ClassY , но мы не только хотим, чтобы ClassX отвечал на событие, мы также хотим учесть, что ClassX должен отправлять подобное (или даже то же самое) событие, чтобы ClassZ мог также предпринять дальнейшие действия.

В качестве более конкретного примера у нас есть класс (это будет « ClassX »), который является своего рода диспетчером данных. Он загружает XML-документ, анализирует его и сохраняет данные из XML в своих собственных свойствах. Таким образом, он имеет объект URLLoader (это будет « ClassY ») и прослушивает событие Event.COMPLETE когда документ XML загружается.

Затем у нас есть основной класс документа, которому принадлежит менеджер данных (класс документа « ClassZ »). Он координирует загрузку данных с другими элементами пользовательского интерфейса, поэтому он хочет знать, когда данные загружены и готовы, чтобы он мог приступить к созданию и расположению элементов пользовательского интерфейса на основе данных.

01
02
03
04
05
06
07
08
09
10
11
12
public class DataManager extends EventDispatcher {
    private var _xmlLoader:URLLoader;
    public function DataManager() {
        _xmlLoader = new URLLoader(new URLRequest(«some.xml»));
        _xmlLoader.addEventListener(Event.COMPLETE, onLoadComplete);
    }
    // … A bunch of other stuff
    private function onLoadComplete(e:Event):void {
        // … XML parsing
        // With the XML parsed, we’d like to dispatch another event to signal being done.
    }
}

Мы могли бы сделать это:

1
dispatchEvent(new Event(Event.COMPLETE));

Но мы могли бы также сделать это:

1
dispatchEvent(e);

Здесь мы просто повторно отправляем существующее событие. Мы не только повторно используем тип события и класс Event , но мы фактически повторно используем весь объект Event как он был передан нашему собственному слушателю.

Это не ракетостроение, но это небольшая удобная техника, которая на удивление не так очевидна.

«Но подождите, — подумайте вы, — если бы мы повторно отправили событие, которое возникло из объекта URLLoader , разве target этого события по-прежнему будет _xmlLoader когда он вернется в класс документа?» И у вас было бы очень хорошее и вдумчивое замечание, и я бы гордился тем, что вы так старательно думали, но вы ошибаетесь.

Довольно волшебная вещь случается при повторном отправлении событий. Свойство target устанавливается для текущего диспетчера. Вы можете найти рабочий пример кода на этом шаге в пакете загрузки, озаглавленном redispatch .

На самом деле, это не так волшебно. При вызове dispatchEvent , если переданный объект Event уже имеет target набор, тогда вызывается метод clone создания идентичной, но дискретной копии исходного Event , за исключением значения, содержащегося в target .


Все, что упомянуто до сих пор — это то, что вы хотите знать Но наступит момент, когда лучше всего отправлять собственное событие. Не просто пользовательский тип события, а целый пользовательский класс Event .

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

  1. Event подкласса
  2. Звоните super(...)
  3. Хранить типы событий в открытых статических константах
  4. Объявите частные свойства для хранения пользовательских данных
  5. Создайте общедоступные методы получения, чтобы обеспечить доступ только для чтения к пользовательской информации
  6. (Необязательно) Переопределите метод clone
  7. (Необязательно) Переопределите метод toString

Чтобы более подробно объяснить этот процесс, мы начнем наш проект слайдера и создадим SliderEvent нам понадобится для этого. Итак, нам нужно начать наш проект, прежде чем мы сможем написать некоторый код, поэтому на следующем шаге мы быстро переключимся, и мы начнем писать собственный класс Event .


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

Начните с создания папки для всего проекта. Моя будет называться слайдером .

Внутри этого создайте папку com , а внутри нее — папку activetuts .

Теперь создайте две папки внутри activetuts: события и пользовательский интерфейс . Ваша окончательная структура папок должна выглядеть примерно так:

  • ползунок
    • ком
      • activetuts
        • События
        • ползунок

Теперь вернемся к нашему классу Event .


Сначала создайте новый текстовый файл в папке slider / com / activetuts / events и назовите его SliderEvent.as . Сначала мы добавим шаблон для любого класса:

01
02
03
04
05
06
07
08
09
10
11
package com.activetuts.events {
 
    public class SliderEvent {
 
        public function SliderEvent() {
 
        }
 
    }
 
}

Здесь не должно быть ничего удивительного, и если у вас есть шаблоны ActionScript для вашего текстового редактора, вам даже не нужно вводить столько текста.

Теперь мы изменим это так, чтобы оно расширяло Event .

01
02
03
04
05
06
07
08
09
10
11
12
13
package com.activetuts.events {
 
    import flash.events.Event;
 
    public class SliderEvent extends Event {
 
        public function SliderEvent() {
 
        }
 
    }
 
}

Как вы можете видеть, мы просто импортируем класс Event , добавляем extends Event в определение класса.


Наш суперкласс может многое для нас обработать, и это здорово, но нам нужно убедиться, что мы правильно инициализируем суперкласс при инициализации нашего подкласса. Нам нужно настроить конструктор с аргументами, совпадающими с аргументами, найденными в конструкторе Event , и передать их вместе с вызовом super .

01
02
03
04
05
06
07
08
09
10
11
12
13
package com.activetuts.events {
 
    import flash.events.Event;
 
    public class SliderEvent extends Event {
 
        public function SliderEvent(type:String, bubbles:Boolean=false, cancelable:Boolean=true) {
            super(type, bubbles, cancelable);
        }
 
    }
 
}

Пока что это основные методы создания подклассов. Фактически, в зависимости от мастерства вашего редактора с шаблонами, вы можете указать суперкласс при создании файла и сделать все это за вас. Например, Flash Builder может это сделать.


Предположительно, с этим классом событий будет связан один или несколько типов событий. Так же, как событие COMPLETE связано с классом Event , а CLICK даже с классом MouseEvent , наш пользовательский класс событий, скорее всего, будет иметь пользовательские типы событий.

Это так же просто, как написать следующую строку для каждого типа события, которое вы хотите добавить:

1
public static const EVENT_TYPE:String = «eventType»;

Давайте сделаем это сейчас для класса SliderEvent .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
package com.activetuts.events {
 
    import flash.events.Event;
 
    public class SliderEvent extends Event {
 
        public static const CHANGE:String = «sliderChange»;
 
        public function SliderEvent(type:String, bubbles:Boolean=false, cancelable:Boolean=true) {
            super(type, bubbles, cancelable);
        }
 
    }
 
}

Мы могли бы теоретически использовать наш класс сейчас. Мы можем использовать SliderEvent в dispatchEvent , а также прослушивать и создавать события с SliderEvent.CHANGE события SliderEvent.CHANGE .

Но мы не будем останавливаться на достигнутом. Есть еще что рассмотреть. Но прежде чем приступить к написанию кода, нам нужно еще раз заглянуть в теорию.


Когда событие отправляется, иногда достаточно просто знать, что событие произошло. Например, в большинстве случаев вас интересуют Event.ENTER_FRAME , Event.COMPLETE или TimeEvent.TIMER , вы, вероятно, просто хотите знать, что событие произошло. Однако бывают и другие случаи, когда вы, вероятно, хотите узнать больше. При прослушивании MouseEvent.CLICK вас может заинтересовать, была ли нажата клавиша Shift, или координаты мыши во время щелчка. Если вы слушаете ProgressEvent.PROGRESS , вы, скорее всего, захотите узнать реальный прогресс загрузки; то есть сколько байт загружено и сколько нужно загрузить.

Различие между этими двумя методологиями иногда называют «толкать» и «тянуть». Эти термины относятся к тому, как слушатель события получает данные, связанные с событием. Если данные «проталкиваются», то в объекте события хранятся данные, и для получения данных слушателю просто необходимо использовать свойства объекта события. Если данные «извлекаются», хотя, как правило, в объекте события содержится очень мало информации, содержащейся внутри — только необходимые данные: тип, цель и т. Д. Эта цель, тем не менее, является необходимой, поскольку она обеспечивает доступ к диспетчеру событий. к слушателю события, что позволяет слушателю получать необходимые данные от диспетчера .

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

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

Выставка:

Pros Cons
От себя
  • Данные легко доступны в объекте события
  • Возможно, вы отправляете ненужные данные. Раздувание объекта события кучей редко используемых данных может привести к проблемам с памятью и / или производительностью.
Тянуть
  • Очень легко написать. Возможно, вам не нужен собственный класс Event для выполнения события pull.
  • Очень прост в использовании. Если это просто класс Event , единственным обязательным аргументом является тип события.
  • Если данные, обычно извлекаемые из диспетчера, требуют больших затрат на их вычисление и возврат, возможно, вы снижаете производительность, требуя, чтобы диспетчер постоянно раздавал эту информацию.
  • Некоторые данные могут быть трудны для извлечения, например, KeyboardEvent имеет свойство keyCode чтобы детализировать клавишу, которая была нажата, чтобы вызвать событие.

Это может быть хорошим обсуждением для комментариев; Я уверен, что у многих из вас, читающих, есть страстные чувства о том, какая методология лучше. Лично я пытаюсь найти метод, который лучше всего подходит для ситуации.

Сказав это, стоит отметить, что к этому моменту наш класс SliderEvent является довольно « SliderEvent ». Для иллюстрации, и поскольку это не страшная идея (хотя я и придумал несколько из них), мы продолжим делать это событие, которое подталкивает данные вместе с ним; а именно значение ползунка, когда он был изменен.


Чтобы реализовать push-событие, нам нужно место для хранения передаваемых данных. Мы добавим частную собственность для этой цели.

Вы все еще должны открыть SliderEvent (если нет … чего вы ждете?). Добавьте выделенную строку:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
package com.activetuts.events {
 
    import flash.events.Event;
 
    public class SliderEvent extends Event {
 
        public static const CHANGE:String = «sliderChange»;
 
        private var _sliderValue:Number;
 
        public function SliderEvent(type:String, bubbles:Boolean=false, cancelable:Boolean=true) {
            super(type, bubbles, cancelable);
        }
 
    }
 
}

Далее мы изменим конструктор, чтобы мы могли принять параметр-значение, и установим приватное свойство следующим образом:

1
2
3
4
public function SliderEvent(type:String, sliderValue:Number, bubbles:Boolean=false, cancelable:Boolean=true) {
    _sliderValue = sliderValue;
    super(type, bubbles, cancelable);
}

Таким образом, мы можем легко создать SliderEvent и настроить его push-данные в одну строку.

Зачем использовать частные свойства? В этом случае мы хотим защитить данные. По моему мнению, данные, связанные с событием, являются неизменными, если они связаны с событием. Возможность изменять данные объекта события похожа на редактирование учебника истории. Честно говоря, это мое мнение, и стандарт, используемый Adobe со своими встроенными классами, заключается в использовании доступных для записи свойств (технически они используют частные свойства и открытые методы получения и установки, но конечный результат тот же).


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

Добавьте этот метод в класс:

1
2
3
public function get sliderValue():Number {
    return _sliderValue;
}

Это добавляет метод sliderValue чтобы мы могли получить доступ к sliderValue свойство. Я решил не добавлять соответствующий установщик. Вы можете добавить один, если вы чувствуете, что это стоит.


Я упоминал метод clone некоторое время назад. Вы, вероятно, не будете часто вызывать clone , но неплохо было бы переопределить метод clone чтобы ваше пользовательское событие хорошо сочеталось с системой событий.

Добавьте этот метод в ваш класс:

1
2
3
override public function clone():Event {
    return new SliderEvent(type, sliderValue, bubbles, cancelable);
}

Во-первых, обратите внимание на подпись этого метода. Мы используем override потому что этот метод объявлен в Event , и мы наследуем его. Он также возвращает объект типа Event . При написании переопределения clone убедитесь, что вы указали правильный тип возвращаемого значения. В нем легко забыть и указать тип вашего класса, но это приведет к несовместимой ошибке переопределения, потому что возвращаемые типы должны совпадать.

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

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


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

Если вам еще не сказали, метод toString существует во всех объектах (он объявлен и определен в Object , über-классе, от которого наследуются все другие классы, нравится вам это или нет). Это может быть вызвано явно, но удобной вещью является то, что он вызывается автоматически в ряде случаев. Например, когда вы передаете объект в функцию trace , любой объект, который еще не является String будет вызывать toString чтобы убедиться, что он правильно отформатирован для панели «Вывод». Он даже вызывается при работе с Object s вместе со String s, как при конкатенации. Например, если вы напишите это:

1
«The answer to life, the universe, and everything is » + 42;

ActionScript достаточно умен, чтобы преобразовать 42 в String представление Number перед объединением String . Попытка добавить String и Number — это плохая новость, но преобразование Number в String и последующее объединение его с другой String — это нормально.

Поэтому, когда вы пишете свои собственные классы, вы можете предоставить метод toString , который не принимает аргументов, возвращает String и возвращает любую String вам нравится.

В случае объектов Event , Adobe услужливо предоставляет метод formatToString чтобы помочь всем Event выглядеть одинаково при трассировке. Мы будем использовать его в методе, который собираемся добавить в наш класс:

1
2
3
override public function toString():String {
    return formatToString(«SliderValue», «type», «sliderValue»);
}

Сначала обратите внимание на сигнатуру метода. Опять же, это override поэтому у нас есть это ключевое слово. Он public , не принимает параметров и возвращает String (что должно быть очевидно).

Далее отметьте одну строку в теле метода. Мы вызываем formatToString , который определен в Event , поэтому его легко использовать. Первый аргумент, который мы передаем ему — это имя класса String . После этого аргументы являются открытыми. Вы можете сдать один, 15 или ни одного. Мы проходим в два. Независимо от того, сколько вы передаете, все они должны быть Strings и соответствовать именам свойств вашего класса. "type" определяется Event , но "sliderValue" определяется нашим собственным классом. В любом случае, получается, что имя свойства печатается с последующим знаком равенства, за которым следует фактическое значение этого свойства. Короче говоря, это будет выглядеть так:

1
2
[language=»text»]
[SliderValue type=»sliderChange» sliderValue=0.34146341463414637]

Это совершенно не работает, но очень полезно. Это может обеспечить быстрый взгляд на событие, когда вещи не работают так, как вы думаете, они должны.


На данный момент мы прошли через ключевую концепцию этого урока: написание пользовательского класса Event . Но нам действительно нужно проверить это. Мы потратим оставшуюся часть времени на создание простого приложения-слайдера, которое было предварительно просмотрено в начале урока.

У нас уже есть структура папок проекта; нам просто нужно еще несколько файлов. Начнем с FLA-файла.

Создайте новый файл Flash (конечно, ActionScript 3.0) и сохраните его как ActiveSlider.fla в корне папки вашего проекта. Я собираюсь предположить, что вам не нужны пошаговые подробности о том, как собрать этот простой FLA вместе, и вместо этого я изложу ключевые элементы. Вы также можете использовать FLA-файл, находящийся в начальной папке пакета загрузки, для справки, или даже просто скопировать этот FLA-файл в папку вашего проекта и назвать этот шаг выполненным.

На сцене три основных объекта.

  1. Слайдер трек . Это длинная узкая полоса, указывающая, куда можно перемещать ползунок. Слайдер перемещается «в» трек.
    • Должен быть клип
    • Для простоты математики, необходимо оформить иллюстрацию так, чтобы точка регистрации находилась в верхнем левом углу
    • Назовите это track_mc
    • Место в верхнем центре; она должна занимать большую часть ширины сцены и иметь пространство под ней.
  2. Скользящая рукоятка . Это элемент размером с кнопку, который указывает текущую позицию ползунка. Это кусок, который движется по дорожке и реагирует на мышь.
    • Нужно быть муви клипом.
    • Опять же, по математике, должна быть точка регистрации вверху слева
    • Назовите это grip_mc
    • Поместите его на дорожку так, чтобы она была вертикально отцентрирована с дорожкой и где-то в пределах левого и правого концов дорожки
    • Он должен располагаться сверху дорожки, чтобы ручка закрывала дорожку (поместите ее на слой выше)
  3. Поле вывода . Это текстовое поле, которое в наших собственных демонстрационных целях отображает текущее значение ползунка.
    • Должен быть динамическим текстовым полем.
    • Назовите это output_tf
    • Шрифты несущественны; установите его на то, что вам нравится, и вставьте по мере необходимости
    • Поместите его в нижнюю часть сцены, чтобы он не конфликтовал с пространством, необходимым для слайдера.
Этап нашего FLA

Помимо подключения класса документа, который мы напишем в два этапа, FLA готов к работе.


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

Начните с создания нового файла класса ActiveSlider.as в папке com / activetuts / slider вашего проекта. Этот класс не слишком интенсивен (по крайней мере, не для наших целей. Класс слайдера мог бы стать намного более сложным), и я просто представлю код полностью и обсудим по ходу дела:

1
2
3
4
5
package com.activetuts.slider {
 
    import flash.display.*;
    import flash.events.*;
    import flash.geom.*;

Ничего захватывающего, просто настройка пакета и импорт.

1
2
3
4
5
public class ActiveSlider extends EventDispatcher {
 
    private var _track:Sprite;
    private var _grip:Sprite;
    private var _grabOffset:Point;

Нам понадобятся эти три свойства. Первые два отслеживают Sprite (или MovieClip ), которые составляют ползунок. Третий используется при перетаскивании ручки слайдера; это помогает сохранить смещение положения рукоятки от мыши на величину относительно места щелчка рукоятки.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
public function ActiveSlider(track:Sprite, grip:Sprite) {
    _track = track;
    _grip = grip;
    if (_grip.parent != _track.parent) {
        throw new Error(«The track and the grip Sprites are not in the same container.»)
    }
 
    _grip.addEventListener(MouseEvent.MOUSE_DOWN, onDown);
 
    if (_grip.stage) {
        addStageListener();
    } else {
        _grip.addEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
    }
}

Это конструктор. Он принимает два аргумента Sprite , которые передаются в первые два из этих свойств для хранения. Затем выполняется простая проверка, чтобы убедиться, что два Sprite находятся в одном координатном пространстве, проверяя, что их parent свойства ссылаются на один и тот же объект. Если они этого не делают, то наша математика для размещения ручки может быть ненадежной, поэтому мы выдаем ошибку как способ оповещения разработчика. Остальная часть конструктора посвящена добавлению двух слушателей событий.Во-первых, это MOUSE_DOWNсобытие и прямо вперед. Но вторая попытка добавить MOUSE_UPсобытие в stage, которое может или не может существовать в зависимости от того, grip Spriteесть ли в списке отображения или нет. Следующие два метода могут сделать это немного яснее:

1
2
3
4
5
6
7
8
private function onAddedToStage(e:Event):void {
    _grip.removeEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
    addStageListener();
}
 
private function addStageListener():void {
    _grip.stage.addEventListener(MouseEvent.MOUSE_UP, onUp);
}

onAddedToStageМетод прослушиватель события для ADDED_TO_STAGEсобытия, который был создан в конструкторе, но только если ручка Spriteне уже есть ссылка на stage. addStageListenerМетод просто добавляет MOUSE_UPслушатель события к stage. То есть, если этоstage ссылка в конструкторе, мы называем addStageListenerнепосредственно. Если неstage ссылки, мы создали ADDED_TO_STAGEсобытие, и когда сцепление добавляется в список отображения, и , таким образом , имеет stageссылки, то onAddedToStageметод срабатывает , который затем , в свою очередь вызовов addStageListener. Он также удаляет ADDED_TO_STAGEпрослушиватель событий, потому что нам нужно сделать это только один раз.

1
2
3
4
5
6
7
8
private function onDown(e:MouseEvent):void {
    _grabOffset = new Point(e.localX, e.localY);
    _grip.addEventListener(Event.ENTER_FRAME, onFrame);
}
 
private function onUp(e:MouseEvent):void {
    _grip.removeEventListener(Event.ENTER_FRAME, onFrame);
}

Затем у нас есть два слушателя событий мыши. В onDown, ключевая строка заключается в добавлении ENTER_FRAMEпрослушивателя событий. В onUp, мы удаляем слушатель. Кроме того onDown, мы отмечаем, где на самом деле произошел щелчок мыши, и сохраняем его в _grabOffset. Это будет играть в наш onFrameметод дальше.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
private function onFrame(e:Event):void {
    _grip.x = _grip.parent.mouseX - _grabOffset.x;
 
    var gripBounds = _grip.getBounds(_grip.parent);
    var trackBounds = _track.getBounds(_grip.parent);
 
    if (gripBounds.x < trackBounds.x) {
        _grip.x = _track.x;
    } else if (gripBounds.right > trackBounds.right) {
        _grip.x = trackBounds.right - gripBounds.width
    }
 
    trace(this.value);
}

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

Во-первых, мы устанавливаем xсвойство ручки в соответствии с положением мыши, только нам нужно сместить его в соответствии с исходным положением мыши, чтобы оно плавно двигалось и не подпрыгивало, чтобы его левый край находился у мыши.

Следующие две строки вычисляют ограничивающие прямоугольники как ручки, так и дорожки Sprites. Мы будем использовать эти прямоугольники в следующей математике, поэтому будем держать вещи в порядке, предварительно вычисляя прямоугольники и сохраняя их в переменных.

Тогда есть ifблок. Это просто ограничивает наш слайдер внутри трека. Это простая проверка, чтобы увидеть, является ли xсцепление, рассчитанное в первой строке метода, ниже (слева) xот дорожки. Если это так, это было бы слишком далеко, поэтому нам нужно переместить захват к этому минимальному значению. Точно так же мы проверяем, является ли правый край ручки больше (правее) правого края трека, и если это так, нам нужно вернуть его к этому максимальному значению.

Наконец, у нас есть надежная позиция захвата, и пока мы просто отслеживаем текущее значение ползунка, которое вычисляется в последнем бите кода для класса:

1
2
3
4
5
6
7
        private function get value():Number {
            return (_grip.x - _track.x) / (_track.width - _grip.width);
        }
 
    }
 
}

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

1
return _grip.x / _track.width;

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

1
return _grip.x / (_track.width - _grip.width);

Это делает ширину, на которую мы делим диапазон движения. Тем не менее, все еще может быть ошибка в том, что дорожка может быть смещена влево или вправо от своего контейнера, что означает, что значения, которые мы получаем, не совсем правильные. Нам нужно нейтрализовать это, вычитая xположение дорожки из рукоятки x, и в итоге мы получим следующее:

1
return (_grip.x - _track.x) / (_track.width - _grip.width);

Для справки, вот полный класс, с моими упущениями:

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
package com.activetuts.slider {
 
    import flash.display.*;
    import flash.events.*;
    import flash.geom.*;
 
    public class ActiveSlider extends EventDispatcher {
 
        private var _track:Sprite;
        private var _grip:Sprite;
        private var _grabOffset:Point;
 
        public function ActiveSlider(track:Sprite, grip:Sprite) {
            _track = track;
            _grip = grip;
            if (_grip.parent != _track.parent) {
                throw new Error("The track and the grip Sprites are not in the same container.")
            }
 
            _grip.addEventListener(MouseEvent.MOUSE_DOWN, onDown);
 
            if (_grip.stage) {
                addStageListener();
            } else {
                _grip.addEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
            }
        }
 
        private function onAddedToStage(e:Event):void {
            _grip.removeEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
            addStageListener();
        }
 
        private function addStageListener():void {
            _grip.stage.addEventListener(MouseEvent.MOUSE_UP, onUp);
        }
 
        private function onDown(e:MouseEvent):void {
            _grabOffset = new Point(e.localX, e.localY);
            _grip.addEventListener(Event.ENTER_FRAME, onFrame);
        }
 
        private function onUp(e:MouseEvent):void {
            _grip.removeEventListener(Event.ENTER_FRAME, onFrame);
        }
 
        private function onFrame(e:Event):void {
            _grip.x = _grip.parent.mouseX - _grabOffset.x;
 
            var gripBounds = _grip.getBounds(_grip.parent);
            var trackBounds = _track.getBounds(_grip.parent);
 
            if (gripBounds.x < trackBounds.x) {
                _grip.x = _track.x;
            } else if (gripBounds.right > trackBounds.right) {
                _grip.x = trackBounds.right - _grip.width
            }
 
            trace(this.value);
        }
 
        private function get value():Number {
            return (_grip.x - _track.x) / ((_track.width - _grip.width) - _track.x);
        }
 
    }
 
}

Мы еще не добавили в наш SliderEventкласс; мы сделаем отдельный шаг, чтобы сделать это. Но сначала нам нужен наш класс документа, чтобы мы могли использовать ActiveSlider.


Нам нужен еще один файл, чтобы он работал: наш класс документов . Создайте новый файл класса с именем SliderDemoв корневой папке проекта. Добавьте следующий код:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
package {
 
    import flash.display.*;
    import flash.events.*;
    import com.activetuts.slider.ActiveSlider;
 
    public class SliderDemo extends Sprite {
 
        private var _slider:ActiveSlider;
 
        public function SliderDemo() {
            _slider = new ActiveSlider(track_mc, grip_mc);
        }
 
    }
 
}

Это намного проще, чем в нашем ActiveSliderклассе. Это действительно просто устанавливает ActiveSliderв свойство с именем _slider.

Вернитесь к FLA-файлу и введите SliderDemo в поле класса документа, и вы сможете попробовать это. Ползунок должен иметь возможность перемещаться вперед и назад и ограничиваться шириной дорожки.

Теперь для одного последнего задания. Нам нужно отправить и прослушать SliderEvent.CHANGEсобытие. Мы сделаем это дальше.


Вернитесь к классу ActiveSlider и внесите однострочное изменение в onFrameметод:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
private function onFrame(e:Event):void {
    _grip.x = _grip.parent.mouseX - _grabOffset.x;
 
    var gripBounds = _grip.getBounds(_grip.parent);
    var trackBounds = _track.getBounds(_grip.parent);
 
    if (gripBounds.x < trackBounds.x) {
        _grip.x = _track.x;
    } else if (gripBounds.right > trackBounds.right) {
        _grip.x = trackBounds.right - _grip.width
    }
 
    dispatchEvent(new SliderEvent(SliderEvent.CHANGE, this.value));
}

Мы удалили трассировку и заменили ее реальной, прямой трансляцией событий. Чтобы это работало, нам нужно импортировать SliderEventкласс:

1
2
3
4
5
6
package com.activetuts.slider {
 
    import flash.display.*;
    import flash.events.*;
    import flash.geom.*;
    import com.activetuts.events.SliderEvent;

Наконец, вернитесь к классу SliderDemo и измените его так, чтобы он слушал и реагировал на SliderEvent:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package {
 
    import flash.display.*;
    import flash.events.*;
    import com.activetuts.slider.ActiveSlider;
    import com.activetuts.events.SliderEvent;
 
    public class SliderDemo extends Sprite {
 
        private var _slider:ActiveSlider;
 
        public function SliderDemo() {
            _slider = new ActiveSlider(track_mc, grip_mc);
            _slider.addEventListener(SliderEvent.CHANGE, onSliderChange);
        }
 
        private function onSliderChange(e:SliderEvent):void {
            trace(e);
            output_tf.text = e.sliderValue.toString();
        }
 
    }
 
}

Мы снова импортируем SliderEventкласс и после создания ActiveSliderдобавляем слушатель, вызываемый onSliderChangeк ползунку. Этот метод является самым большим дополнением, но все еще является обычным слушателем событий. Это очень похоже на любой другой прослушиватель событий, только мы обязательно вводим аргумент события как SliderEvent, потому что это то, что мы получаем.

Первая строка кода немного лишняя, но я хотел посмотреть, что происходит, когда вы отслеживаете наш SliderEventобъект. При запуске вы увидите типичное для Flash форматирование объекта события.

Объект события, представленный в виде строки

Вторая строка делает то, что мы были после того, как начать. Он просто захватывает sliderValueсвойство, превращает его в, а Stringзатем вставляет Stringего TextFieldв сцену, чтобы мы могли увидеть значение слайдера в фильме.


Когда вы начинаете развертывать свои собственные пользовательские события, вы начинаете работать с ActionScript 3 так, как это было задумано. События помогают вам отделить классы друг от друга, и хорошо структурированный поток событий в вашем приложении может действительно сделать разницу между чем-то, с чем легко работать, и чем-то с ошибками и темпераментом. С этой (теоретически) последней версией AS3 101 вы должны быть на пути к тому, чтобы стать ниндзя.

Спасибо за чтение, и я увижу вас здесь на Activetuts +, прежде чем вы это знаете!