В этой главе AS3 101 мы углубимся в механику системы событий Flash. Если вы следили за этим до сих пор, вы видели события в использовании, начиная с первого эпизода серии . Редактор и я почувствовали, что пришло время написать что-то, что должно быть официально включено в учебную программу, поэтому, если вы когда-нибудь видели эти строки кода о добавлении прослушивателей или диспетчеризации событий, и не совсем прижились, то это учебник для вас.
Уже существует учебник Activetuts + по основам событий , поэтому основное внимание в этом учебнике будет уделено отправке событий из ваших собственных классов, включая создание пользовательских типов событий и объектов.
Чтобы добиться успеха в этом руководстве, вы должны чувствовать себя комфортно в написании и использовании собственных классов в ActionScript 3, а также в безопасности при использовании существующих событий, предоставляемых Flash, таких как MouseEvent.CLICK
или Event.ENTER_FRAME
. Мы сосредоточимся в первую очередь на отправке пользовательских событий из пользовательских классов.
предварительный просмотр
Мы потратим много времени на теорию для этого урока, но в конце мы создадим простой элемент управления ползунком, который отправляет свои собственные события:
Шаг 1: Зачем использовать диспетчеризацию событий?
Этот пример на самом деле является довольно простым примером того, почему вы хотите отправлять свои собственные события. Когда вы пишете свои собственные классы, вы в идеале держите их в собственных черных ящиках и инкапсулируете. Но вам все равно нужно, чтобы различные объекты взаимодействовали, чтобы создать полезную программу.
Модель событий, предоставляемая ActionScript 3, является довольно хорошим и удобным способом для облегчения взаимодействия между вашими классами, сохраняя при этом разделение обязанностей в ваших классах. Так что, если мы напишем наш собственный пользовательский класс, такой как ActiveSlider
выше ActiveSlider
классов ActiveSlider
, нам нужно будет позволить другим объектам знать, когда ползунок изменяет свое значение. Если ползунок может отправлять свое собственное событие изменения, то другие объекты, которым необходимо знать эту информацию, могут легко подписаться и получить уведомление.
Лично я нахожу необходимость отправлять свои собственные события в моих классах настолько часто, что мой шаблон класса устанавливает каждый новый класс с образцом, который должен быть в состоянии сделать это. Когда вы научитесь сохранять объекты дискретными, вы будете обращаться к диспетчеризации событий как к наиболее распространенной технике для этого.
Шаг 2: Как отправить ваши собственные события
У меня хорошие новости: рассылка ваших собственных событий на самом деле очень проста. Это один из основных принципов ActionScript 3, встроенный в проигрыватель Flash Player, и поэтому вам нужно сделать только одну вещь, чтобы получить возможность отправлять события. Это одна вещь:
Расширить EventDispatcher
Вот и все: при написании вашего класса используйте строку:
1
|
public class MyClass extends EventDispatcher {
|
Конечно, вам нужно импортировать EventDispatcher
, который находится в пакете flash.events
. Скорее всего, вам понадобятся другие классы в пакете, поэтому может быть удобнее просто импортировать пакет с подстановочным знаком.
1
|
import flash.events.*;
|
Шаг 3: Отправка
Теперь вы настроены на отправку события. Все, что вам нужно сделать сейчас, это вызвать метод, предоставленный EventDispatcher
именем dispatchEvent
. Легко понять, нет?
Когда вы вызываете dispatchEvent
, вы должны предоставить хотя бы один аргумент, объект Event
. Все встроенные объекты Event
находятся в пакете flash.events
, так что здесь вам пригодится импорт с использованием подстановочных знаков. Каждый тип объекта Event
будет иметь свои собственные требования, но чаще всего вам просто нужно передать ему только один аргумент. Этот аргумент является типом события, который представляет собой String
которая называет событие, например, "click"
или "complete"
. Они чаще пишутся как MouseEvent.CLICK
или Event.COMPLETE
, но конечный результат тот же; это идентификатор, который отделяет один тип события от другого и позволяет одному объекту Event
управлять несколькими типами событий.
Итак, собрав все вместе, если вы хотите отправить "complete"
событие, вы можете сделать это так:
1
|
dispatchEvent(new Event(Event.COMPLETE));
|
Просто отбросьте эту строку (или такую) в зависимости от того, какой метод подходит для вашего класса. Затем ваш класс будет использовать свою унаследованную систему диспетчеризации событий, и все слушатели будут уведомлены за вас. Говоря о слушателях, давайте также кратко рассмотрим их.
Шаг 4: Слушай
Любой другой объект в вашем приложении теперь может прослушивать ваши пользовательские события. Еще одна хорошая новость: это ничем не отличается от регистрации событий для встроенных классов. На предыдущем шаге мы настроили наш гипотетический класс для отправки события 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
, поэтому я не буду останавливаться на этом подробнее.
Шаг 5: Куда отправлять
Где вы фактически размещаете строку кода 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));
}
}
|
Шаг 6: Пользовательские события
Вы все настроены на отправку ваших собственных событий. Теперь, что вы должны отправить? Есть несколько вариантов для рассмотрения:
- Просто повторно используйте объект события и тип события, уже предоставленные Flash Player
- Повторное использование существующего объекта события, но с предоставлением пользовательского типа события
- Переотправить существующее событие
- Создать пользовательский объект события
- Толчок против тяги
Этот первый вариант мы уже видели в наших предыдущих примерах. Нам необходимо отправить событие, связанное с завершением какого-либо процесса, и, как это происходит, Flash предоставляет тип события ( COMPLETE
), связанный с объектом события ( Event
), который соответствует нашим критериям. Нам не нужно предоставлять дополнительные данные о событии. Отправка события Event.COMPLETE
— это все, что нам нужно.
Мы рассмотрим эти другие варианты в следующих шагах.
Шаг 7: Пользовательские типы событий
Как уже упоминалось в шаге «Отправка», типы событий являются просто 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 в статических константах. Где эти статические константы определены, зависит от вас.
Шаг 8: повторная отправка событий
Есть несколько раз, когда один класс (назовем его 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
.
Шаг 9: Пользовательские объекты событий
Все, что упомянуто до сих пор — это то, что вы хотите знать Но наступит момент, когда лучше всего отправлять собственное событие. Не просто пользовательский тип события, а целый пользовательский класс Event
.
Процесс для этого прост, вам просто нужно выполнить несколько шагов. Мы обсудим это в свое время. Обратите внимание, что довольно много из этого представляет собой шаблонный код, и вы можете легко создать шаблон для подкласса Event
и изменить всего несколько ключевых частей и быть включенным и запущенным. Основные шаги в сокращенной форме, как будто вы знали, что я говорил, о чем:
-
Event
подкласса - Звоните
super(...)
- Хранить типы событий в открытых статических константах
- Объявите частные свойства для хранения пользовательских данных
- Создайте общедоступные методы получения, чтобы обеспечить доступ только для чтения к пользовательской информации
- (Необязательно) Переопределите метод
clone
- (Необязательно) Переопределите метод
toString
Чтобы более подробно объяснить этот процесс, мы начнем наш проект слайдера и создадим SliderEvent
нам понадобится для этого. Итак, нам нужно начать наш проект, прежде чем мы сможем написать некоторый код, поэтому на следующем шаге мы быстро переключимся, и мы начнем писать собственный класс Event
.
Шаг 10: Создать структуру проекта
Для этого все будет довольно просто, но мы, тем не менее, создадим пакеты для наших классов.
Начните с создания папки для всего проекта. Моя будет называться слайдером .
Внутри этого создайте папку com , а внутри нее — папку activetuts .
Теперь создайте две папки внутри activetuts: события и пользовательский интерфейс . Ваша окончательная структура папок должна выглядеть примерно так:
- ползунок
- ком
- activetuts
- События
- ползунок
- activetuts
- ком
Теперь вернемся к нашему классу Event
.
Шаг 11: 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
в определение класса.
Шаг 12: позвони super
Наш суперкласс может многое для нас обработать, и это здорово, но нам нужно убедиться, что мы правильно инициализируем суперкласс при инициализации нашего подкласса. Нам нужно настроить конструктор с аргументами, совпадающими с аргументами, найденными в конструкторе 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 может это сделать.
Шаг 13: Храните типы событий в общедоступных статических константах
Предположительно, с этим классом событий будет связан один или несколько типов событий. Так же, как событие 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
.
Но мы не будем останавливаться на достигнутом. Есть еще что рассмотреть. Но прежде чем приступить к написанию кода, нам нужно еще раз заглянуть в теорию.
Шаг 14: Толчок против Тяги
Когда событие отправляется, иногда достаточно просто знать, что событие произошло. Например, в большинстве случаев вас интересуют Event.ENTER_FRAME
, Event.COMPLETE
или TimeEvent.TIMER
, вы, вероятно, просто хотите знать, что событие произошло. Однако бывают и другие случаи, когда вы, вероятно, хотите узнать больше. При прослушивании MouseEvent.CLICK
вас может заинтересовать, была ли нажата клавиша Shift, или координаты мыши во время щелчка. Если вы слушаете ProgressEvent.PROGRESS
, вы, скорее всего, захотите узнать реальный прогресс загрузки; то есть сколько байт загружено и сколько нужно загрузить.
Различие между этими двумя методологиями иногда называют «толкать» и «тянуть». Эти термины относятся к тому, как слушатель события получает данные, связанные с событием. Если данные «проталкиваются», то в объекте события хранятся данные, и для получения данных слушателю просто необходимо использовать свойства объекта события. Если данные «извлекаются», хотя, как правило, в объекте события содержится очень мало информации, содержащейся внутри — только необходимые данные: тип, цель и т. Д. Эта цель, тем не менее, является необходимой, поскольку она обеспечивает доступ к диспетчеру событий. к слушателю события, что позволяет слушателю получать необходимые данные от диспетчера .
Другими словами, вы можете либо отправить группу данных в прослушиватель внутри объекта события, либо вы можете потребовать, чтобы прослушиватель извлекал данные из диспетчера по мере необходимости.
На мой взгляд, плюсы и минусы каждой техники несколько сбалансированы, и путь, который вы выбираете для объекта события, зависит от конкретной ситуации, а не от личных предпочтений.
Выставка:
Pros | Cons | |
---|---|---|
От себя |
|
|
Тянуть |
|
|
Это может быть хорошим обсуждением для комментариев; Я уверен, что у многих из вас, читающих, есть страстные чувства о том, какая методология лучше. Лично я пытаюсь найти метод, который лучше всего подходит для ситуации.
Сказав это, стоит отметить, что к этому моменту наш класс SliderEvent
является довольно « SliderEvent
». Для иллюстрации, и поскольку это не страшная идея (хотя я и придумал несколько из них), мы продолжим делать это событие, которое подталкивает данные вместе с ним; а именно значение ползунка, когда он был изменен.
Шаг 15. Объявление частных свойств для хранения пользовательских данных
Чтобы реализовать 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 со своими встроенными классами, заключается в использовании доступных для записи свойств (технически они используют частные свойства и открытые методы получения и установки, но конечный результат тот же).
Шаг 16. Создание общедоступных методов получения пользовательских данных
Поэтому следующим шагом было бы убедиться, что мы можем получить доступ к передаваемым данным. Частная собственность сама по себе не подходит для этой цели. Поэтому нам нужно написать открытый метод _sliderValue
свойства _sliderValue
. Мы решим не писать установщик, чтобы свойство стало доступно только для чтения (как обсуждалось на последнем шаге).
Добавьте этот метод в класс:
1
2
3
|
public function get sliderValue():Number {
return _sliderValue;
}
|
Это добавляет метод sliderValue
чтобы мы могли получить доступ к sliderValue
свойство. Я решил не добавлять соответствующий установщик. Вы можете добавить один, если вы чувствуете, что это стоит.
Шаг 17: переопределить метод clone
Я упоминал метод clone
некоторое время назад. Вы, вероятно, не будете часто вызывать clone
, но неплохо было бы переопределить метод clone
чтобы ваше пользовательское событие хорошо сочеталось с системой событий.
Добавьте этот метод в ваш класс:
1
2
3
|
override public function clone():Event {
return new SliderEvent(type, sliderValue, bubbles, cancelable);
}
|
Во-первых, обратите внимание на подпись этого метода. Мы используем override
потому что этот метод объявлен в Event
, и мы наследуем его. Он также возвращает объект типа Event
. При написании переопределения clone
убедитесь, что вы указали правильный тип возвращаемого значения. В нем легко забыть и указать тип вашего класса, но это приведет к несовместимой ошибке переопределения, потому что возвращаемые типы должны совпадать.
Все, что мы действительно делаем в основе события, — это создание нового SliderEvent
и передача тех же значений, которые мы сохранили в текущем объекте события. Это создает идентичную, но сдержанную копию: клон.
Это необязательный шаг, но он быстрый выигрыш и гарантирует, что ваше пользовательское событие будет хорошо сочетаться с остальной системой событий.
Шаг 18: переопределить метод toString
И последнее, и опять же, это необязательно. Но он также очень полезен в качестве инструмента отладки, поэтому обычно окупается за несколько раз.
Если вам еще не сказали, метод 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]
|
Это совершенно не работает, но очень полезно. Это может обеспечить быстрый взгляд на событие, когда вещи не работают так, как вы думаете, они должны.
Шаг 19: Построение слайдера
На данный момент мы прошли через ключевую концепцию этого урока: написание пользовательского класса Event
. Но нам действительно нужно проверить это. Мы потратим оставшуюся часть времени на создание простого приложения-слайдера, которое было предварительно просмотрено в начале урока.
У нас уже есть структура папок проекта; нам просто нужно еще несколько файлов. Начнем с FLA-файла.
Создайте новый файл Flash (конечно, ActionScript 3.0) и сохраните его как ActiveSlider.fla в корне папки вашего проекта. Я собираюсь предположить, что вам не нужны пошаговые подробности о том, как собрать этот простой FLA вместе, и вместо этого я изложу ключевые элементы. Вы также можете использовать FLA-файл, находящийся в начальной папке пакета загрузки, для справки, или даже просто скопировать этот FLA-файл в папку вашего проекта и назвать этот шаг выполненным.
На сцене три основных объекта.
- Слайдер трек . Это длинная узкая полоса, указывающая, куда можно перемещать ползунок. Слайдер перемещается «в» трек.
- Должен быть клип
- Для простоты математики, необходимо оформить иллюстрацию так, чтобы точка регистрации находилась в верхнем левом углу
- Назовите это
track_mc
- Место в верхнем центре; она должна занимать большую часть ширины сцены и иметь пространство под ней.
- Скользящая рукоятка . Это элемент размером с кнопку, который указывает текущую позицию ползунка. Это кусок, который движется по дорожке и реагирует на мышь.
- Нужно быть муви клипом.
- Опять же, по математике, должна быть точка регистрации вверху слева
- Назовите это
grip_mc
- Поместите его на дорожку так, чтобы она была вертикально отцентрирована с дорожкой и где-то в пределах левого и правого концов дорожки
- Он должен располагаться сверху дорожки, чтобы ручка закрывала дорожку (поместите ее на слой выше)
- Поле вывода . Это текстовое поле, которое в наших собственных демонстрационных целях отображает текущее значение ползунка.
- Должен быть динамическим текстовым полем.
- Назовите это
output_tf
- Шрифты несущественны; установите его на то, что вам нравится, и вставьте по мере необходимости
- Поместите его в нижнюю часть сцены, чтобы он не конфликтовал с пространством, необходимым для слайдера.
Помимо подключения класса документа, который мы напишем в два этапа, FLA готов к работе.
Шаг 20: класс ActiveSlider
Основным классом пользовательского интерфейса, с которым мы будем работать, является класс 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
свойство ручки в соответствии с положением мыши, только нам нужно сместить его в соответствии с исходным положением мыши, чтобы оно плавно двигалось и не подпрыгивало, чтобы его левый край находился у мыши.
Следующие две строки вычисляют ограничивающие прямоугольники как ручки, так и дорожки Sprite
s. Мы будем использовать эти прямоугольники в следующей математике, поэтому будем держать вещи в порядке, предварительно вычисляя прямоугольники и сохраняя их в переменных.
Тогда есть 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
.
Шаг 21: Класс документа
Нам нужен еще один файл, чтобы он работал: наш класс документов . Создайте новый файл класса с именем 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
событие. Мы сделаем это дальше.
Шаг 22: ОтправкаSliderEvent
Вернитесь к классу 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; |
Шаг 23: Прислушиваться к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
в сцену, чтобы мы могли увидеть значение слайдера в фильме.
Шаг 24: Завершение
Когда вы начинаете развертывать свои собственные пользовательские события, вы начинаете работать с ActionScript 3 так, как это было задумано. События помогают вам отделить классы друг от друга, и хорошо структурированный поток событий в вашем приложении может действительно сделать разницу между чем-то, с чем легко работать, и чем-то с ошибками и темпераментом. С этой (теоретически) последней версией AS3 101 вы должны быть на пути к тому, чтобы стать ниндзя.
Спасибо за чтение, и я увижу вас здесь на Activetuts +, прежде чем вы это знаете!