Модель событий Генератор—Листенеры

У практически любого современного человека понятие «событие» ассоциируется со студией новостей телевидения. Камеры, репортеры, сообщения — кому не знакомы эти понятия нашего века информации. Атак как лучший способ разобраться в каком-то неизвестном предмете — это сопоставить его с общеизвестными вещами, то мы попробуем представить инструменты реализации событий как маленькую студию, информирующую столь же маленький внутренний мир Flash-плейера о произошедших в нем изменениях.

Чтобы имелся смысл следить за событиями, они должны, по крайней мере, происходить. В реальном мире источником новостей являются политики, падающие самолеты и эксцентричные звезды шоу-бизнеса. В мире Flash-плейера события производятся или пользователем, или встроенными объектами. За ними постоянно наблюдают репортеры — особые внутренние методы, фиксирующие изменения того или иного свойства, Как работает типичная функция-репортер? Чаще всего она представляет собой просто циклически вызываемую проверку некоторого условия. Например, для того чтобы установить, что курсор мыши передвигается, нужно сопоставить текущие его координаты с координатами, записанными на предыдущем вызове функции-репортера. Если данная проверка производится с достаточной частотой (30—50 мс), то создастся впечатление, что событие регистрируется мгновенно.

Если происходит событие, журналист, за ним следящий, отправляет информацию на студию, откуда она транслируется на страну или даже на весь мир.


Так же происходит и в ActionScript.

Функция-репортер вызывает специальный метод особого объекта AsBroadcaster, являющегося своеобразной студией новостей в мире Flash-плейера. При этом все «подписчики новостей» мгновенно информируются о произошедшем событии.

Объекты, «слушающие» новости, называются листенерами (от английского listener — «слушатель»). Обычно листенеры — истинные «патриоты» — они интересуются только новостями того класса, к которому относятся сами. Например, если событие проигрывания нового кадра onEnter-Frame относится к клипам, то кнопки его «слышать» не будут. Аналогично реальному миру, во Flash-плейере есть центральные каналы, зрителями которых являются все объекты соответствующего класса. В частности, все клипы одновременно получают сообщения о таких событиях своего класса, как onEnterFrame или onMouseDown. А есть каналы и кабельные, на которые нужно подписываться. Например, события объекта Key по умолчанию не имеют листенеров. Чтобы подписать объект на «кабельные» события, нужно использовать особый метод addListener() объекта, являющегося их источником. Аналогично метод remove Listener() удаляет объект из числа листенеров объекта.

Как реализуется концепция листенеров? Очень просто — в каждом объекте, производящем события, хранятся списки с объектами, которые должны быть уведомлены.


Когда событие происходит, сообщение о нем при помощи особого метода объекта-студии AsBroadcaster отправляется всем листенерам, зарегистрированным в соответствующем списке. При помощи метода addListcner() объект заносится в массив листенеров. Методом removeListener() он оттуда удаляется.

Настоящее телевидение чрезвычайно сильно воздействует на поведение масс. Объекты Action-Script более дисциплинированы и в случае наступления события реагируют на него только в том случае, если вы дадите им алгоритм действий. Этот алгоритм нужно сохранить как метод листенера. Имя этого метода должно совпадать с именем события, при наступлении которого он должен активизироваться. Например, если при нажатии кнопки мыши клип ball должен исчезнуть, то нужно набрать:

ball.onMouseDown=function():Void { // onMoussDown — событие
this._visible=false;
}

Теперь ясно, что сообщение, которое отправляется объектом-студией AsBroadcaster всем листенерам объекта, является нечем иным, как вызовом метода с именем произошедшего события. Если функция-обработчик для данного события была создана, то она будет активизирована. Иначе вызов просто проигнорируется.

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


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

Обработчики событий
Обработчик события — это метод листенера события, который активизируется, если событие происходит. Обработчик события должен иметь совершенно определенное имя. Например, код, который необходимо выполнить при движении мыши, нужно поместить в блок метода с именем on-MouseMove. Скрипт, реагирующий на нажатие кнопки, должен быть сохранен в ее методе с именем onRelease и т. д. Часто имя обработчика события отождествляют с самим событием. Строго говоря, это не совсем верно. Не существует события onEnterFrame, но вызов метода с таким именем отправляется всем клипам при загрузке нового кадра. Однако, четкости и простоты ради, мы будем называть в дальнейшем события именами их обработчиков.

Обычно объект или класс имеет всего несколько событий. Однако в случае клипов и кнопок число их достаточно значительно — 9 и 18 (!).

Наиболее распространенный синтаксис обработчиков событий имеет следующую форму:

listener.eventHandler=function(){
statements;
}

Здесь:
• listener — листенер события;
• eventHandler — стандартное имя обработчика события.

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

function onEnterFrame():Void {
trace("Привет!!!"); // Выводит: Привет Привет ...
}

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

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

Обработчик события, как и любой метод, может быть с легкостью удален при помощи оператора delete. Производить подобную операцию нужно всякий раз, когда обработчик решит возложенную на него задачу. Это позволит освободить оперативную память, а также (в случае таких событий, как onEnterFrame или onMouseMove) несколько уменьшить нагрузку на процессор. Пример:

_root.onEnterFrame = function(): Void {
trace("Привет!!!"]; // Выводит: Привет!!! (сообщение появляется
// только один раз)
delete _root.onEnterFrame;
};

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

Если группа экземпляров должна реагировать на событие одинаковым образом, то нужно обеспечить наследование соответствующего обработчика. В простейшем случае, если при наступлении события одни и те же действия должны выполнять все клипы или все кнопки в фильме, можно просто сохранить обработчик в прототипе конструктора класса MovieClip или Button. Например:

// Создаем клип ball в виде кружочка
for (var i = 0; i<100; i++) { // Изготавливаем 100 случайно распределенных
// по полю дубликатов ball
ball.duplicateMovieClip("ball" + i, i, {_x: Hath.random()*550, _y:
Math, random!)*400>);
}
// При щелчке по любому из экземпляров он будет исчезать
MovieClip.prototype.onPress = function():Void {
this.removeMovieClip();
};

Если обработчик должен наследовать не все клипы или кнопки, а лишь некоторую их часть, ее нужно выделить в отдельный подкласс.

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

Листенеры
Листенер — это объект, получающий сообщение о событии от объекта, его генерирующего. Объект может быть листенером события и по умолчанию, и явно определен на эту «должность». Так, изначально слушают события своего класса кнопки и клипы. Клипы, кроме того, являются листенерами объекта Mouse. Поэтому, например, основная временная шкала «слышит» событие нажатия клавиши мыши:

_root.onMouseDown = function():Void {
trace("Щелчок произошел!!!"); // При щелчке
// в Output
};

Кнопки листенерами событий объекта Mouse не являются. Поэтому метод с именем on Mouse
Down кнопки при нажатии левой клавиши мыши активизирован не будет:

// Перетащите из встроенной библиотеки кнопку и назовите ее but
but.onMouseMove = function():Void {
but._xscale=but._yscale+=30; // При щелчке кнопка не увеличивается
};

Чтобы кнопка начала получать сообщения о событиях мыши, она должна быть добавлена в список листенеров объекта Mouse. Сделать это можно при помощи метода addListener(), присущего всем объектам, генерирующим события. Его синтаксис:

eventObj.addListener (listener),
где:

• eventObj — объект, подписку на сообщения о событиях которого нужно организовать;
• listener — идентификатор объекта, который должен быть включен в список листенеров объекта eventObj.

При добавлении всего одной строчки кода наша кнопка сможет «слышать» щелчки мыши:

Mouse.addListener(but); // Добавляем but в массив листенеров
but.onMouseDown = function():Void {
but._xscals=but._yscale+=30; // При щелчке мышью кнопка
// увеличивается на треть
};

Если добавить в массив листенеров объекта Mouse клип, то при отпускании левой кнопки мыши его обработчик события onMouseDown будет активизироваться дважды (подумайте, почему).

Метод addListener() возвращает true, если листенер был добавлен в список успешно, и false, если этого не произошло:

trace(Mouse.addListener(but)); // Выводит: true

Список листенеров объекта хранится в особом недокументированном свойстве Listeners. Именно в него заносит переданный ему аргумент метод addListener(). Проверим, действительно ли кнопка but стала листенером объекта Mouse:

trace(Mouse["_listeners"]); // Выводит: level0.but

Обратиться к свойству Listeners нужно при помощи оператора «И», а не оператора точки. Это связано с тем, что класс Mouse является для компилятора статичным, а недокументированные элементы он воспринимает как несуществующие.

Обратите внимание, что в массиве _listeners хранятся указатели лишь на листенеры, заданные пользователем с использованием метода addListener(). Объекты, которые являются листенерами событий объекта по умолчанию, информируются о них по особому скрытому внутреннему механизму. Именно поэтому в списке Listeners объекта Mouse не имеется, например, _root.

Кстати, для того чтобы добавить в массив Listeners новый указатель, совсем необязательно использовать метод addListener(). Для этого будет вполне достаточно скромного метода push() класса Array:

Mouse["_listeners"].push(but); // Аналогично: Mouse.addListener(but);

Особенностью метода addListener является то, что он не осуществляет контроля типа переданного ему аргумента. Поэтому в массив Listeners можно занести даже число:

Mouse.addListener(1);
trace(Mouse["_listeners"]); // Выводит: 1

«Отписать» объект от получения сообщений о событиях можно, использовав метод removeListener() объекта, их генерирующего:

Mouse.removeListener(but);
trace(Mouse["_listeners"]); // В Output ничего не отображается -
// листенер был удален

Метод removeListeners() возвращает true, если листенер был удален. Если же в массиве Listeners не обнаруживается элемента с именем, совпадающим с переданным методу аргументом, то возвращается false. Благодаря этому можно определить, действительно ли произошло удаление листенера:

Mouse.addListener(but);
trace(Mouse.removeListener(but)); // Выводит: true
trace(Mouse.removeListener(but)); // Выводит: false (but уже удалена
// из массива)

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

for (var i=0; i // Вызываем нужный метод листенера
Mouse.("_listeners")[i].onMouseMove();
}

Приблизительно такой же код, как и приведенный, использует отвечающий за рассылку сообщений о событиях метод объекта AsBroadcaster. Данный объект является ядром модели событий Генератор—Листенеры ActionScript, и подробно о нем мы поговорим ниже. Сейчас же главное усвоить важнейшие принципы, лежащие в основе реализации событий. Без владения ими невозможно решить более сложные задачи, чем добавление в массив листенеров одного объекта.

Как подписать на события одну кнопку, мы уже знаем. А можно ли сделать так, чтобы листенерами объекта Mouse были все кнопки в фильме? Задача эта, прямо скажем, не из тривиальных. Для ее решения недостаточно сохранить в массиве листенеров прототип конструктора класса Button.

Увы, но вызов метода прототипа совсем не означает, что данный метод будет активизирован у всех объектов, его наследующих. Чтобы все кнопки получали сообщения о событиях мыши, ссылки на них должны быть занесены в массив Mouse, listeners непосредственно. Но как это сделать, если кнопки могут быть вложены в клипы? Очевидно, что при этом нужно просмотреть все временные диаграммы на предмет наличия на них кнопок. Если кнопка будет найдена, ее имя должно быть занесено в массив листенеров объекта Mouse.

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

// Создаем фильм, в котором имеются вложенные в клипы кнопки
/* функция globalListener() обследует все свойства объекта на предмет наличия ссылок на кнопки. Если кнопка обнаруживается, она заносится в число листенеров объекта Mouse. Если свойство хранит клип, то он также, при помощи рекурсивного вызова данной функции, обследуется на наличие кнопок */

function globalListener(level:MovieClip):Void {
for (var i in level) {
if (level[i] instanceof Button) {
Mouse.addListener(level[i]);
}
if (level[i] instanceof MovieClip) {
globalListener(level[i]);
}
}
}
globalListener(_root);
trace(Mouse["_listeners"].join(" + ")); // Выводит: _level0.mov.but +
// _level0.mov.mov_intro.but + _level0.but (все три кнопки фильма)
Button.prototype.onMouseDown = function():Void {
this._xscale = this._yscale += 10; // При щелчке мыши все кнопки
// увеличиваются
};

Еще более непростая задача — сделать кнопку листенером событий клипов. Дело в том, что клипы информируются о событиях при помощи скрытых внутренних методов, и поэтому не существует конкретного объекта, к которому можно было бы добавить листенер. Вообще события клипов и кнопок стоят особняком от остальных событий ActionScript, так как они подчиняются описанной выше модели далеко не полностью. Чтобы все-таки «научить» кнопки «слышать» события клипов, необходимо использовать методы недокументированного объекта AsBroadcaster(). Как конкретно это делается, мы покажем в разделе, посвященном ядру модели событий Генератор—Листенеры ActionScript.

Вы можете спросить: отчего бы разработчикам Flash, дабы не усложнять жизнь пользователям, не подписать все клипы и кнопки на события хотя бы таких объектов, как Mouse, Key, Stage по умолчанию? Увы, сделать этого нельзя по причине того, что мощность компьютеров не столь уж огромна. Вы уже знаете, что факт события сопровождается рассылкой сообщений всем его листенерам. Это не слишком сильно сказывается на скорости проигрывания фильма, если листенеров немного. Но если число клипов достигает нескольких сотен, то работа плейера практически останавливается, И значительный вклад в общую нагрузку на систему вносит необходимость информировать все клипы и кнопки более чем о двух десятках видов событий. Естественно, что если рассылать сообщение обо всех событиях всем объектам, то Rash-плейер со средним фильмом не потянет даже новейший Pentium 4. Поэтому, чтобы swf-файлы могли воспроизводиться и на машинах невысокой мощности, по умолчанию листенерами событий является ограниченное количество объектов.

Оцените статью: (0 голосов)
0 5 0

Статьи из раздела Action Script на эту тему:
Модель событий Flash 5
Обновление экрана при событиях
Событийные методы
Ядром модели событий Генератор – Листенеры Flash MX 2004

Вернуться в раздел: Action Script / 8. События