Март 2011
Руководство по actionscript. часть 4, стр. 120
Рассмотрим код мeтoдamouseMoveListener ( ) из класса CustomMousePointer. Обратите внимание на вызов метода updateAf terEvent ( ), выделенный полужирным шрифтом:
private function mouseMoveListener (e:MouseEvent):void { // При перемещении мыши обновляем позицию пользовательского указателя // мыши, чтобы она соответствовала позиции системного указателя var pointlnParent:Point = parent. globalToLocal(new Point(e. stageX,
e. stageY));
x = pointlnParent. x; у = pointlnParent. у;
// Запрашиваем постсобытийное обновление экрана, чтобы анимация // указателя была максимально плавной е. updateAfterEvent( );
// Убеждаемся, что пользовательский указатель мыши отображается на экране // (он может быть скрыт, поскольку системный указатель мог покидать // пределы области отображения приложения Flash Player), if (!visible) f
visible = true;
}
}
Стоит отметить, что, когда среда Flash обновляет экран в ответ на вызов метода updateAf terEvent ( ), она отображает не только те изменения, которые были внесены функцией-приемником события, запросившей данное обновление, но и все визуальные изменения, произошедшие с момента последнего обновления экрана.
Руководство по actionscript. часть 4, стр. 121
Постсобытийные обновления для событий таймера
Для того чтобы разрешить обновление экрана сразу после истечения некоторого произвольного интервала времени, язык ActionScript предоставляет метод TimerEvent. updateAf terEvent ( ), который вызывает постсобытийное обновление экрана после возникновения события TimerEvent. TIMER.
Указанный метод точно так же используется внутри функций-приемников события TimerEvent. TIMER, как и методы MouseEvent. updateAf terEvent ( ) и KeyboardEvent. updateAf terEvent ( ) внутри функций-приемников событий мыши и клавиатуры.
Для демонстрации использования метода Time г Event. updateAf terEvent ( ) создадим расширенный пример, который генерирует событие TimerEvent. TIMER в десять раз чаще, чем скорость кадров среды выполнения Flash. Мы начнем с установки скорости, равной одному кадру в секунду:
stage. frameRate = 1:
Далее мы создаем объект Timer, который осуществляет диспетчеризацию события TimerEvent. TIMER каждые 100 мс (10 раз в секунду):
var timer:Timer = new TimerdOO, 0);
Затем мы регистрируем функцию-приемник timerListener ( ) в объекте timer для событий TimerEvent. TIMER, как показано в следующем коде:
timer. addEventListener(TimerEvent. TIMER, timerListener);
После этого мы запускаем таймер:
timer. start( );
Теперь внутри функции timerListener ( ) рисуем прямоугольник и помещаем его в случайное место на экране. Для того чтобы гарантировать, что прямоугольник появится на экране сразу после завершения процесса диспетчеризации события TimerEvent. TIMER (а не на следующем запланированном этапе обновления экрана), мы используем метод TimerEvent. updateAf terEvent ( ) для запроса постсобытийного обновления экрана. Рассмотрим получившийся код для метода timerListener ( ):
private function timerListener (e:TimerEvent):void { // Создаем прямоугольник var rect:Sprite = new Sprite( ); rect. graphi cs.1i neStyle(1); rect. graphics. beginFill(OxOOOOFF); rect. graphics. drawRect(0, 0, 150, 75);
rect. x = Math. floor(Math. random( )*stage. stageWidth); rect. у = Math. f1oor(Math. random( )*stage. stageHeight);
// Добавляем прямоугольник на экран addChild(rect):
// Запрашиваем постсобытийное обновление экрана e. updateAfterEvent( );
}
В результате такого вызова метода TimerEvent. updateAf terEvent ( ) отображение визуальных изменений, вносимых внутри метода timerListener ( ), происходит приблизительно каждые 100 мс, а не раз в секунду.
Руководство по actionscript. часть 4, стр. 122
Для информации следующий код демонстрирует наш предыдущий сценарий с таймером в контексте класса RandomRectangles:
package { import flash. display.*; import flash events.*; import flash. utils.*;
public class RandomRectangles extends Sprite { public function RandomRectangles ( ) { stage. frameRate = 1; var timer:Timer = new TimerdOO, 0); timer. start( );
timer. addEventLi stenerdimerEvent. TIMER, timerListener);
}
private function timerListener (e:TimerEvent):void { var rect:Sprite = new Sprite( ); rect. graphi cs.1i neSty1e(1): rect. graphics. beginFill(OxOOOOFF); rect. graphics. drawRectCO. 0. 150. 75); rect. x = Math. f1oor(Math. random( )*stage. stageWidth); rect. у = Math. f1oor(Math. random( )*stage. stageHeight);
addChild(rect);
e. updateAfterEvent( )
}
}
}
В гл. 24 мы продолжим наше изучение класса Timer, применяя его для создания движения и других форм анимации.
Руководство по actionscript. часть 4, стр. 123
Ш4_ Q Метод setlnterval() языка ActionScript 2.0 может также использовать метод updateAfterEvent() } для вызова постсобытийного обновления экрана, однако вместо метода setlnterval() предпочтительнее использовать класс flash. utils. Timer, поскольку он предоставляет возможность запускать и останавливать события таймера, а также уведомлять о таймерных событиях сразу несколько приемников. Старайтесь избегать использования метода setlnterval() в языке ActionScript 3.0.
Автоматические постсобытийные обновления экрана
В приложении Flash Player 9 определенные «кнопочные» взаимодействия с объектами любого класса, наследуемого от класса Sprite, приводят к автоматическому постсобытийному обновлению экрана (аналогично тому, как программист вызывает метод updateAf terEvent ( ) ). В частности, автоматическое постсобытийное обновление экрана будут вызывать следующие взаимодействия:
? перемещение указателя мыши над экземпляром класса, который наследуется от класса Sprite, или за пределы этого экземпляра;
? нажатие или отпускание основной кнопки мыши, когда указатель мыши находится над экземпляром класса, наследуемого от Sprite;
? использование клавиши Пробел или Enter для активизации экземпляра класса, наследуемого от класса Sprite.
Существует небольшая вероятность, что в будущих версиях приложения Flash Player такое особое поведение, относящееся к автоматическому обновлению экрана, будет применяться только к объектам SimpleButton. В связи с этим вы, вероятно, не захотите полагаться на него в своем коде.
Область перерисовки
Как уже известно из разд. «Запланированные обновления экрана», среда выполнения Flash обновляет экран только в тех случаях, когда это необходимо (то есть когда графическое содержимое было изменено или добавлено). Точнее говоря, когда среда выполнения обновляет экран, она визуализирует только те области, которые изменились с момента последнего обновления. Например, представим анимацию, состоящую из двух кадров, первый из которых содержит круг, а второй — тот же круг, но вместе с треугольником. Когда среда Flash визуализирует второй кадр, она перерисовывает прямоугольную область, содержащую треугольник, но не перерисовывает круг. Прямоугольная область, включающая все содержимое, которое было изменено, называется областью перерисовки (в программировании графики данная область иногда называется измененным прямоугольником).
Руководство по actionscript. часть 4, стр. 124
^ I Чтобы отобразить область перерисовки в отладочных версиях среды выполнения, можно м$ 4 * щелкнуть правой кнопкой мыши в окне приложения Flash Player и в контекстном меню Щ< выбрать пункт show redraw regions (показать области перерисовки).
Оптимизация с использованием события Event. RENDER
Событие Event. RENDER — это особый тип события обновления экрана, используемый в сложных ситуациях, когда графическая производительность имеет первостепенное значение. Его основное назначение заключается в предоставлении
программисту возможности отложить выполнение всех пользовательских процедур рисования точно до того момента, когда произойдет визуализация экрана, позволяя тем самым избежать повторного выполнения процедур рисования. В отличие от остальных внутренних событий среды разработки Flash, событие Event. RENDER должно быть запрошено программистом вручную. Среда Flash осуществляет диспетчеризацию события Event. RENDER, когда выполняются два следующих условия:
? среда выполнения Flash собирается проверить необходимость обновления экрана (либо при прохождении какого-либо кадра, либо в результате вызова метода updateAf terEvent ( ) );
? программист вызвал метод stage. invalidate ( ) (с его помощью программист может попросить среду выполнения Flash осуществить диспетчеризацию события Event. RENDER в следующий раз, когда произойдет проверка обновления экрана).
Рассмотрим пример, который демонстрирует, как событие Event. RENDER может быть использовано для улучшения производительности. Предположим, что мы создаем класс Ell ipse, который представляет фигуру эллипса на экране. Для простоты предположим, что эллипс всегда заполняется белым цветом и имеет контур черного цвета толщиной 1 пиксел. Наш класс Ellipse должен делать следующее:
? управлять концептуальными данными эллипса (то есть хранить ширину и высоту эллипса);
? рисовать эллипс на экране, используя данные концептуального эллипса, и перерисовывать эллипс на экране при изменении данных концептуального эллипса.
Руководство по actionscript. часть 4, стр. 125
В соответствии с описанными назначениями, в листинге 23.2 представлен вариант реализации класса Ellipse, который приемлем в том случае, когда производительность не имеет решающего значения:
Листинг 23.2. Простейший класс Ellipse
package { import flash. display. Shape;
public class Ellipse extends Shape { private var w:Number; private var h:Number:
public function Ellipse (width:Number, height:Number) { w = width; h = height; draw( );
}
public function setWidth (newWidth:Number):void { w = newWidth; draw( );
}
public function getWidth ( ):Number {
return w;
}
public function setHeight (newHeight:Number):void { h = newHeight; draw( );
}
public function getHeight ( ):Number { return h;
}
private function draw ( ):void { graphics. lineStyle(l); graphics. beginFill(OxFFFFFF, 1); graphics. drawEllipse(0, 0, w, h);
}
}
}
Обратите внимание, что в классе Ellipse есть три места, где происходит изменение данных концептуального эллипса: метод setWidth ( ), метод setHeight ( ) и метод-конструктор класса Ell ipse. Чтобы обеспечить соответствие между концептуальным эллипсом и эллипсом, отображаемым на экране, мы должны убедиться, что перерисовка эллипса, отображаемого на экране, осуществляется в каждом из трех перечисленных мест. Код, представленный в листинге 23.2, для удовлетворения этого требования использует метод решения «в лоб»; он просто вызывает метод draw( ) всякий раз, когда выполняются методы getWidth ( ), getHeight ( ) или метод-конструктор класса Ellipse. Разумеется, если внутри одного цикла обновления экрана эти функции вызываются несколько раз, дублирующие вызовы метода draw ( ) являются избыточными. Это демонстрирует следующий код:
var е:El 1 ipse = new Ellipse (100, 200); // метод draw( ) вызывается здесь e. setWidth(25); // метод draw( ) вызывается здесь снова е. setHeight(50); // метод draw( ) вызывается здесь снова
При выполнении трех предыдущих строк кода метод draw ( ) вызывается три раза, однако на экране будут отображены результаты лишь последнего вызова. Первые два вызова являются избыточными и нерациональными. В простейшем приложении такая избыточность незаметна и, следовательно, может оказаться допустимой. Тем не менее в сложных приложениях подобная избыточность может приводить к тому, что процедуры рисования будут бесполезно выполняться сотни или тысячи раз, представляя собой потенциальный источник серьезных проблем, связанных с производительностью.
Руководство по actionscript. часть 4, стр. 126
Чтобы избавиться от избыточности в классе Ellipse, мы должны изменить подход, применяемый для рисования фигуры. Вместо того чтобы вызывать метод draw ( ) всякий раз, когда изменяются данные концептуального эллипса, мы будем откладывать вызов этого метода до момента обновления экрана. Эта новая стратегия приведет к усложнению кода класса Ellipse, но при этом улучшит его производительность.
Первый шаг в реализации новой стратегии «один вызов метода dr aw ( )» заключается в удалении вызова метода draw ( ) из методов setWidht ( ) и setHeight ( ), а также вызова метода-конструктора класса El 1 ipse. Вместо непосредственного вызова метода draw ( ) эти функции будут вызывать метод stage. invalidate ( ), который приказывает среде выполнения Flash осуществить диспетчеризацию события Event. RENDER при очередной проверке необходимости обновления экрана. Затем из функции-приемника события Event. RENDER мы вызовем метод draw ( ). В листинге 23.3 представлен измененный класс Ell ipse — отличия от кода из листинга 23.2 выделены полужирным шрифтом. Стоит отметить, что метод draw ( ) не должен вызываться, когда объект Ellipse не находится в списке отображения, поэтому вызов метода stage. inval idate ( ) происходит только в том случае, когда объект Ell ipse находится в списке отображения. Чтобы определить, находится ли объект Ellipse в списке отображения, мы проверяем значение унаследованной переменной экземпляра stage этого объекта. Когда значение переменной stage равно null, объект Ellipse не находится в списке отображения.
*«, -
I Объект, запрашивающий уведомление о возникновении события Event. RENDER, м$ л* получит это уведомление даже в том случае, если он не находится в списке отобра-_ ffi жения.
Обратите внимание, что на данном промежуточном этапе нашей разработки класс Ell ipse не является полнофункциональным, поскольку он не регистрирует приемники для событий Event. RENDER. Эту проблему мы решим в ближайшее время.
Листинг 23.3. Измененный класс Ellipse, часть 1
package { import flash. display. Shape;
public class Ellipselnterim extends Shape { private var w:Number; private var h:Number;
public function Ellipselnterim (width:Number, height:Number) { w = width; h = height;
// Если этот объект находится в списке отображения… if (stage!= null) {
// …запрашиваем диспетчеризацию события Event. RENDER
stage. invalidate( );
}
}
public function setWidth (newWidth:Number):void { w = newWidth;
if (stage!= null) { stage. invalidate( );
public function getWidth ( ):Number { return w;
}
public function setHeight (newHeight:Number):void { h = newHeight;
if (stage!= null) { stage. invalidate( );
}
}
public function getHeight ( ).-Number { return h;
}
// Приемник события вызывается перед обновлением // экрана, если был вызван метод // stage. invalidate( )
private function renderListener (e:Event):void { draw( );
}
private function draw ( ):void { graphics. clear( ); graphics.1ineStyle(l); graphics. beginFill(OxFFFFFF. 1); graphics. drawEllipse(0. 0. w, h);
}
}
}
Чтобы метод renderListener ( ) выполнялся всякий раз, когда среда Flash осуществляет диспетчеризацию события Event. RENDER, мы должны зарегистрировать метод renderListener ( ) в экземпляре класса Stage для событий Event. RENDER. Однако когда объект Ellipse не находится в списке отображения, его переменной экземпляра stage присвоено значение null и, следовательно, она не может применяться для регистрации события. Для того чтобы обойти эту проблему, мы определим в классе Ellipse две функции-приемника событий — addedToStageListener( ) и removedFomStageListener( ), которые получают уведомления о возникновении пользовательских событий StageDetector. ADDED_TO_SТAGE и StageDetector. REMOVED_FROM_STAGE. Диспетчеризация первого события происходит, когда объект добавляется в список отображения, и при получении этого события класс Ellipse будет регистрировать метод renderListener ( ) для события Event. RENDER. Диспетчеризация второго события происходит в том случае, когда объект удаляется из списка отображения, и при получении этого события класс Ellipse будет отменять регистрацию метода renderListener ( ) для событий Event. RENDER.
Руководство по actionscript. часть 4, стр. 127
В листинге 23.4 продемонстрирован измененный класс Ellipse (изменения, как и раньше, выделены полужирным шрифтом). Обратите внимание, что метод
addedToStageListener ( ) вызывает метод stage. invalidate ( ), гарантируя, что любые изменения, внесенные в объект Ellipse за то время, пока он не находился в списке отображения, будут визуализированы при добавлении этого объекта в список отображения.
*»4
В листинге 23.4 применяются пользовательские события StageDetector. ADDED_TO_STAGE и StageDetector. REMOVED_FROM_STAGE, диспетчеризация которых осуществляется поль-3?-’ зовательским классом StageDetector. Детальное рассмотрение данного класса можно найти в подразд. «События ADDED_TO_STAGE и REMOVED_FROM_STAGE» разд. «События контейнеров» гл. 20.
Листинг 23.4. Измененный класс Ellipse, часть 2
package { import flash. display. Shape; import flash. events.*;
public class Ellipselnterim extends Shape { private var w:Number; private var h:Number;
public function Ellipselnterim (width:Number, height:Number) { // Регистрируем приемники для получения уведомлений, // когда данный объект добавляется в список // отображения или удаляется из него var StageDetector-.StageDetector = new StageDetector (this); StageDetector. addEventLi stener(StageDetector. ADDEDJTJSTAGE,
addedToStageListener); StageDetector. addEventLi stener (StageDetector. REMOVEDFROMSTAGE,
removedFromStageListener);
w = width; h = height; if (stage!= null) { stage. invalidate( );
}
}
public function setWidth (newWidth:Number):void { w = newWidth; if (stage!= null) { stage. invalidate( );
}
}
public function getWidth ( ):Number { return w;
}
public function setHeight (newHeight:Number):void { h = newHeight;
if (stage!= null) { stage. invalidate( );
}
}
public function getHeight ( ):Number { return h;
}
// Приемник события вызывается при добавлении этой фигуры // в список отображения
private function addedToStageListener (e:Event):void { // Регистрируем приемник для получения уведомлений // об обновлениях экрана
stage. addEventListener(Event. RENDER, renderListener);
// Гарантируем, что любые изменения, внесенные в данный объект // за то время, пока он не отображался на экране, будут // визуализированы при его добавлении в список отображения, stage. invalidate( );
}
// Приемник события вызывается при удалении этой фигуры из списка // отображения
private function removedFromStageLi stener (e: Event) .-void { // Нет необходимости в получении событий // обновления экрана, когда данный объект // не находится в списке отображения stage. addEventListener(Event. RENDER, renderListener);
}
private function renderListener (e:Event):void { draw( );
}
private function draw ( ):void { graphics. clear( ); graphics. lineStyle(l); graphics. beginFill(OxFFFFFF, 1); graphics. drawEllipse(0. 0. w, h);
}
}
}
Класс Ellipse, представленный в листинге 23.4, теперь является полностью функциональным, однако в нем все еще остаются две существенные избыточности. Во-первых, метод addedToStageListener ( ) всегда вызывает метод stage. invalidate ( ), когда объект Ellipse добавляется в список отображения. В результате перерисовка фигуры происходит даже в тех случаях, когда в этом нет необходимости, поскольку за то время, пока эллипс не отображался на экране, данные концептуального эллипса не изменялись.
Руководство по actionscript. часть 4, стр. 128
Во-вторых, не забывайте, что событие Event. RENDER возникает в том случае, когда любой объект — не обязательно текущий — вызывает метод stage. invalidate ( ). Таким образом, в текущем состоянии метод renderListener ( ) будет вызывать метод draw ( ) всякий раз, когда любой объект в приложении вызывает метод stage. invalidate ( ). В приложении с множеством объектов такая избыточность может привести к серьезным проблемам, связанным с производительностью.
Чтобы решить эти две оставшиеся проблемы, мы внесем последний набор изменений в класс Ellipse — добавим новую логику, позволяющую определить, требуется ли перерисовка эллипса при вызове методов addedToStageListener ( ) и renderListener ( ). Во-первых, добавим новую переменную экземпляра changed, которая будет сообщать о том, требуется ли перерисовка объекта El 1 ipse при очередном обновлении экрана. Затем, чтобы устанавливать и сбрасывать значение переменной changed, а также проверять ее статус, добавим три новых метода: setChanged( ), clearChanged ( ) и hasChanged( ). Наконец, всякий раз при изменении эллипса (то есть всякий раз при вызове метода setwidth ( ), setHeight ( ) или метода-конструктора) мы будем присваивать переменной changed значение true.
В листинге 23.5 представлена окончательная версия класса Ellipse с комментариями, которые помогут вам понять код (изменения выделены полужирным шрифтом). Как отмечалось ранее, класс, представленный в листинге 23.5, безусловно, является более сложным, чем его первоначальная, неоптимизированная версия из листинга 23.2. Однако в приложениях, требующих максимальной графической производительности, подход с отложенной визуализацией, применяемый в листинге 23.5, является просто незаменимым. Дополнительную информацию по классам, представляющим фигуры и использующим отложенную визуализацию, можно найти в гл. 25.
Листинг 23.5. Окончательная, оптимизированная версия класса Ellipse
package { import flash. display. Shape; import flash. events.*;
public class Ellipse extends Shape { private var wiNumber; private var h:Number; private var changed:Boolean;
public function Ellipse (width:Number, height:Number) { // Регистрируем приемники для получения уведомлений, когда данный // объект добавляется в список отображения или удаляется из него var StageDetector:StageDetector = new StageDetector(this); StageDetector. addEventListener(StageDetector. ADDED_TO_STAGE,
addedToStageLi stener); StageDetector. addEventLi stener(StageDetector. REMOVED_FROM_STAGE.
removedFromStageListener); // Устанавливаем ширину и высоту w = width; h = height;
// Помечаем, что объект изменился setChanged( );
}
public function setWidth (newWidth:Number):void { w = newWidth; setChanged( );
}
public function getWidth ( ):Number { return w;
}
public function setHeight (newHeight:Number):void { h = newHeight; setChanged( );
}
public function getHeight ( ):Number { return h;
}
// Помечает, что в данной фигуре что-то изменилось private function setChanged ( ):void {
changed = true;
if (stage!= null) { stage. invalidate( );
}
}
// Помечает, что самые последние изменения // были отображены на экране private function clearChanged ( ):void { changed = false;
}
// Сообщает о том, содержит ли данная фигура изменения, // которые еще не были отображены на экране protected function hasChanged ( ):Boolean { return changed;
}
// Приемник события вызывается при добавлении этой фигуры // в список отображения
private function addedToStageListener (e:Event):void { // Регистрируем приемник для получения уведомлений // об обновлениях экрана
stage. addEventLi stener(Event. RENDER, renderLi stener);
// Если за то время, пока объект отсутствовал в списке отображения,
// произошли какие-либо изменения, нарисуем эти изменения
// при следующей визуализации экрана. Однако если с момента,
// когда объект находился в списке отображения, никаких изменений
// не произошло, в его отрисовке нет необходимости, if (hasChanged( )). { stage. invalidate( );
}
}
// Приемник события вызывается при удалении этой фигуры из списка // отображения
private function removedFromStageListener (e:Event):void { // Нет необходимости в получении событий обновления экрана, когда // данный объект не находится в списке отображения stage. addEventLi stener(Event. RENDER. renderLi stener);
}
// Приемник события вызывается перед обновлением экрана, если был вызван
// метод stage. invalidate( )
private function renderListener (e:Event):void {
// Вызываем метод draw ( ), если изменения, внесенные в данную фигуру,
// еще не были отображены.
Руководство по actionscript. часть 4, стр. 129
// Если это событие вызвал другой объект, но данный объект не был // изменен, перерисовка данного объекта выполняться не будет, if (hasChanged( )) {
draw( );
}
} -
private function draw ( ):void { graphics. clear( ); graphics. lineStyle(l); graphics. beginFill(OxFFFFFF. 1); graphics. drawEllipse(0, 0, w, h);
}
}
}
Заставим его двигаться!
Теперь, когда мы познакомились с системой обновления экрана среды выполнения Flash, можно рассмотреть, как с помощью кода создается анимация и движение. Звучит забавно? Увидимся в следующей главе.