вторник, 25 января 2011 г.

Создание ActionScript 3.0 компонентов во Flash. Часть 2.

Часть 2: Прототип компонента MenuBar

Начнем разработку прототипа компонента MenuBar без использования инфраструктуры компонента UI. В-общем, это отличный способ для начала создания любого компонента. Чаще всего компонент управляется кодом и создает свои активы динамически во время выполнения на основе размеров, стилей и параметров. Однако чтобы упростить создание прототипа, поместим основные части на сцену и отложим создание динамических параметров «на потом». Также небольшие фрагменты кода начнем писать в кадрах вместо вынесения кода во внешние классы.

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



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

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

Шаг 1: Первый взгляд и ощущения

Создадим новый файл FLA с поддержкой ActionScript 3.0 и перетащим компонент TileList из панели компонентов на сцену. Компонент TileList подобен традиционному списку, но он может разместить свои ячейки сеткой или горизонтально. Выберем компонент TileList, поскольку он неплохо служит в качестве выпадающего меню. В Component Inspector для экземпляра TileList дважды кликнем на поле значений dataProvider, и в открывшемся диалоговом окне четыре раза нажмем кнопку «+». Установим в качестве значений label menu1, menu2, menu3 и menu4. В Live Preview экземпляра на сцене сразу увидим изменения (см. рис. 1).
Рисунок 1. Компонент TileList с четырьмя пунктами меню









Затем изменим ширину ячеек до 100 px (columnWidth) высоту до 22 px (rowHeight). Соответственно размеры самого экземпляра компонента подгоним под 400x22 (см. рис. 2).
Рисунок 2. После корректировки параметров TileList начинает походить на меню





Благодаря Live Preview есть возможность быстрой настройки внешнего вида компонента, без вызова команды Control > Test Movie. Протестируем ролик и увидим, что при нажатии одной плитки, она меняет цвет, пока не нажать другую (см. рис. 3)
Рисунок 3. При тестировании ролика выбранная плитка изменила цвет





Нежелательно, чтобы плитки так меняли цвет, поскольку такое поведение не соответствует меню. В закладке Parameters инспектора компонента нет параметра с именем selectability. Поэтому зададим его значение из кода. Сначала дадим экземпляру TileList имя myMenuBar, так что теперь можно ссылаться на него из кода. Откроем панель Actions и напишем первую строчку кода:


  1. myMenuBar.selectable = false;

Теперь экземпляр TileList похож на выпадающее меню, только пока без выпадающих списков.

Шаг 2: Добавление выпадающих списков

Перетащим четыре экземпляра компонента List на сцену, и расположим прямо под четырьмя пунктами меню. Назовем эти экземпляры menu1, menu2, menu3 и menu4. Заполним dataProvider для каждого списка. На данном этапе menu bar выглядит так (см. рис. 4).

Рисунок 4. После добавления четырех экземпляров List на сцену










Далее отрегулируем высоту каждого списка для удаления пустот. Изменим свойства selectable для каждого экземпляра List:

  1. menu1.selectable = false;
  2. menu2.selectable = false;
  3. menu3.selectable = false;
  4. menu4.selectable = false;

Эти четыре строки кода отменяют отображение выбранной строки меню. При дальнейшем просмотре видим, что ярлыки строкового меню не центрированы в ячейке по вертикали, а прикреплены к верху. Наверняка можно устранить эту проблему, настроив стиль экземпляра TileList, и поэтому почитаем справку по компонентам. В разделе стилей для TileList есть только два элемента, когда наследуемые стили скрыты. Очевидно, что виновен cellRenderer особенно после значения по умолчанию fl.controls.listClasses.ImageCell. После прочтения справки по пакету fl.controls.listClasses и классу fl.controls.List, обнаружим, что стиль cellRenderer для List по умолчанию это fl.controls.listClasses.CellRenderer. Нам надо, чтобы ячейки в экземпляре класса TileList расположились также как и в компоненте List. Изменение стиля cellRenderer fl.controls.listClasses.CellRenderer решит нашу проблему. Чтобы проверить, как меняется свойство, отобразим наследуемые методы в TileList и зададим в поиске по справке "style." В конце концов, найден метод setStyle(). Вот код:
  1. import fl.controls.listClasses.CellRenderer;
  2. myMenuBar.setStyle("cellRenderer", CellRenderer);

Ни одно из определений пакетов fl.* инфраструктуры компонента UI автоматически не импортируется, поэтому класс CellRenderer необходимо импортировать.


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

Шаг 3: Добавление базовой функциональности

После создания ролика с экземплярами компонентов TileList и List, настало время добавить код, который при наведении указателя мыши над верхним меню, раскрывал бы соответствующий список. Добавим обработчики событий мыши, чтобы показать или скрыть выпадающие списки. В этих обработчиках добавим функцию trace, выводящую в панели Output выбранный пункт меню. Далее читаем комментарии в коде.


Наилучший способ понять последовательное выполнение кода это загрузить прототип (menubar_prototype.zip) и пошагово разбирать код, установив точки остановки и выбрав Debug > Debug Movie. Также здесь полезно попытаться закомментировать строки или менять код, чтобы лучше понять, что каждая строка делает.


Вот сценарий в целом:
  1. // выключение свойства selectable заставит экземпляр
  2. //класса TileList вести себя как строка меню
  3. myMenuBar.selectable = false;
  4. // выключение свойств selectable заставит экземпляры
  5. //класса List вести себя как раскрывающееся меню
  6. menu1.selectable = false;
  7. menu2.selectable = false;
  8. menu3.selectable = false;
  9. menu4.selectable = false;
  10. // По умолчанию стиль cellRenderer для TileList это
  11. // fl.controls.listClasses.ImageCell, который размещает надпись
  12. // (label) под изображением.  В нашем случае он помещает метку
  13. // (label) вверху ячейки, а не в центре, так что
  14. // используем fl.controls.listClasses.CellRenderer
  15. import fl.controls.listClasses.CellRenderer;
  16. myMenuBar.setStyle("cellRenderer", CellRenderer);
  17. // keepMenuOpen используется для передачи слушателю событий сцены
  18. // что не надо закрывать меню, если событие было получено
  19. // меню TileList или сначала выпадающим меню List
  20. var keepMenuOpen:Boolean = false;
  21. // необходимо слушать событие mouseDown строкового меню TileList
  22. // для запуска обработчика меню. Другой обработчик события мыши
  23. // будет добавлен после первого mouseDown на меню TileList
  24. // и удален, когда меню закрыто.
  25. myMenuBar.addEventListener(MouseEvent.MOUSE_DOWN,
  26.         menuBarMouseHandler);
  27. // интерфейс ICellRenderer реализуется классом
  28. // fl.controls.listClasses.CellRenderer и другими классами,
  29. // которые могут быть использованы стилем cellRenderer.
  30. // В то время, как множество классов cellRenderer - субклассы
  31. // CellRenderer, не нуждающиеся в нем, поэтому Вы должны
  32. // использовать интерфейсы. В текущей форме прототипа,
  33. // cellRenderer ВСЕГДА будет CellRenderer, но он в конечном
  34. // счете будет выставлен через стиль и может быть любым
  35. // классом, в то время реализующим ICellRenderer,
  36. // поэтому мы используем лучшую практику
  37. // сразу, чтобы позже не забыть.
  38. import fl.controls.listClasses.ICellRenderer;
  39. /*
  40.  * Обработчик события мыши для меню TileList
  41.  */
  42. function menuBarMouseHandler(e:MouseEvent):void {
  43.     // цель события мыши часто будет ICellRenderer для ячейки,
  44.     // над которой произошел клик или проведен курсор.
  45.     // Используя ключевое слово as, мы гарантируем, что имеем
  46.     // экземпляр класса, реализующего интерфейс ICellRenderer,
  47.     // и если цель не является экземпляром того класса,
  48.     // получаем null. Оказывается, в некоторых случаях целью
  49.     // может быть сам TileList, и в таком случае событие нас
  50.     // не интересует, поэтому проверяем на null
  51.     // и стразу возвращаемся, если да.
  52.     var cellRenderer:ICellRenderer = e.target as ICellRenderer;
  53.     if (cellRenderer == null) return;
  54.     // вместо того, чтобы иметь обработчик для каждого обращения
  55.     // MouseEvent, упростим код и будем иметь один обработчик
  56.     // и включим в него тип события.
  57.     switch (e.type) {
  58.     case MouseEvent.MOUSE_DOWN:
  59.         // выпадающее меню List имеет имя экземпляра
  60.         // которое совпадает с ярлыком для соответствующего
  61.         // пункта меню TileList, поэтому мы можем получить
  62.         // правильный экземпляр List, ссылаясь на ярлык,
  63.         // полученный из свойств данных экземпляра ICellRenderer
  64.         openMenuBar(this[cellRenderer.data.label]);
  65.         // смотри обработчик MOUSE_UP ниже
  66.         // для обсуждения keepMenuOpen
  67.         keepMenuOpen = true;
  68.         break;
  69.     case MouseEvent.MOUSE_OVER:
  70.         hideAllMenusExcept(this[cellRenderer.data.label]);
  71.         break;
  72.     case MouseEvent.MOUSE_UP:
  73.         // это событие попадет в первый mouseUp только после
  74.         // первого mouseDown, открывшего меню. Нам нужно его
  75.         // обработать для предотвращения обработки mouseUp из
  76.         // закрытых меню. Мы можем остановить прослушивание
  77.         // сценой всех событий, вызвав e.stopPropagation(), но
  78.         // поскольку этот код со временем станет кодом
  79.         // компонента, который может использоваться в
  80.         // произвольном приложении, то становится опасно
  81.         // останавливать распространение события, поскольку мы
  82.         // не знаем, какие события пользователь будет
  83.         // обрабатывать над нашим компонентом. Поэтому, вместо
  84.         // этого мы используем keepMenuOpen:Boolean.
  85.         myMenuBar.removeEventListener(MouseEvent.MOUSE_UP,
  86.                         menuBarMouseHandler);
  87.         keepMenuOpen = true;
  88.         break;
  89.     }
  90. }
  91. /*
  92.  * Обработчик события мыши для меню List
  93.  */
  94. function menuMouseHandler(e:MouseEvent):void {
  95.     // смотри комментарии в menuBarMouseHandler для разъяснения
  96.     // следующих двух строк кода
  97.     var cellRenderer:ICellRenderer = e.target as ICellRenderer;
  98.     if (cellRenderer == null) return;
  99.     switch (e.type) {
  100.     case MouseEvent.MOUSE_UP:
  101.         // не делаем какой-либо реальной диспетчеризации событий,
  102.         // просто отслеживаем
  103.         trace(cellRenderer.data.label);
  104.         // кроме того, содержание текстового поля на сцене только
  105.         // для прототипа, когда перейдем к компоненту, удалим его
  106.         selectedMenu_txt.text = cellRenderer.data.label;
  107.         closeMenuBar();
  108.         break;
  109.     case MouseEvent.MOUSE_DOWN:
  110.         // не допустить слушателю сцены закрывать меню.
  111.         // Больше об использовании keepMenuOpen в комментарии
  112.         // для события mouseUp в menuBarMouseHandler()
  113.         keepMenuOpen = true;
  114.         break;
  115.     }
  116. }
  117. /*
  118.  * обработчик для событий мыши на сцене.  mouseUp или mouseDown
  119.  * на сцене закрывает меню, за исключением первого клика
  120.  * на строке меню TileList или выпадающем меню List.
  121.  */
  122. function stageMouseHandler(e:MouseEvent):void {
  123.     if (keepMenuOpen) {
  124.         // keepMenuOpen используется событием mouseUp строки
  125.         // меню TileList для сигнализации ему, что следующее
  126.         // событие mouseUp сцены не должно закрывать меню.
  127.         // Мы установим его в false, так что последующее
  128.         // событие сцены mouseUp только закроет меню.
  129.         keepMenuOpen = false;
  130.     } else {
  131.         closeMenuBar();
  132.     }
  133. }
  134. /*
  135.  * Показ выпадающих меню передается в качестве параметра
  136.  * и добавляет все события, использующиеся для отслеживания
  137.  * открытых меню и решения, когда их закрыть. Только слушатель
  138.  * события mouseDown строки меню TileList активен
  139.  * до активных меню.
  140.  */
  141. function openMenuBar(menuToOpen:List):void {
  142.     // открытие выпадающего меню List
  143.     hideAllMenusExcept(menuToOpen);
  144.     // мы должны обработать mouseOver, как только меню откроется
  145.     myMenuBar.addEventListener(MouseEvent.MOUSE_OVER,
  146.                 menuBarMouseHandler);
  147.     // мы хотим обработать первое событие mouseUp после первого
  148.     // mouseDown на строке меню TileList для предотвращения
  149.     // слушателем mouseUp сцены закрытия меню
  150.     myMenuBar.addEventListener(MouseEvent.MOUSE_UP,
  151.                 menuBarMouseHandler);
  152.     // если мы получим mouseDown где угодно на сцене, кроме
  153.     // строки меню TileList или выпадающих меню List,
  154.     // мы закроем меню
  155.     stage.addEventListener(MouseEvent.MOUSE_DOWN,
  156.                 stageMouseHandler);
  157.     // если первый mouseUp после инициализации mouseDown для
  158.     // открытия меню не над строкой меню TileList или выпадающих
  159.     // меню List, мы должны закрыть меню
  160.     stage.addEventListener(MouseEvent.MOUSE_UP,
  161.                 stageMouseHandler);
  162.     // Когда мы получим mouseUp на выпадающем меню List, это
  163.     // означает, что пользователь сделал выбор, который
  164.     // мы должны обработать
  165.     menu1.addEventListener(MouseEvent.MOUSE_UP,
  166.                 menuMouseHandler);
  167.     menu2.addEventListener(MouseEvent.MOUSE_UP,
  168.                 menuMouseHandler);
  169.     menu3.addEventListener(MouseEvent.MOUSE_UP,
  170.                 menuMouseHandler);
  171.     menu4.addEventListener(MouseEvent.MOUSE_UP,
  172.                 menuMouseHandler);
  173.     // нам просто нужно обработать mouseDown выпадающих меню List
  174.     // для предотвращения слушателем сцены закрытия меню
  175.     menu1.addEventListener(MouseEvent.MOUSE_DOWN,
  176.                 menuMouseHandler);
  177.     menu2.addEventListener(MouseEvent.MOUSE_DOWN,
  178.                 menuMouseHandler);
  179.     menu3.addEventListener(MouseEvent.MOUSE_DOWN,
  180.                 menuMouseHandler);
  181.     menu4.addEventListener(MouseEvent.MOUSE_DOWN,
  182.                 menuMouseHandler);
  183. }
  184. /*
  185.  * скрытие всех выпадающих меню List и удаление всех слушателей
  186.  * событий, добавленных в openMenuBar()
  187. */
  188. function closeMenuBar():void {
  189.     // закрытие всех меню
  190.     hideAllMenusExcept(null);
  191.     // сброс состояния keepMenuOpen, только для правильного
  192.     // управления (housekeeping)
  193.     keepMenuOpen = false;
  194.     // удаление всех слушателей, добавленных в openMenuBar()
  195.     myMenuBar.removeEventListener(MouseEvent.MOUSE_UP,
  196.                 menuBarMouseHandler);
  197.     myMenuBar.removeEventListener(MouseEvent.MOUSE_OVER,
  198.                 menuBarMouseHandler);
  199.     stage.removeEventListener(MouseEvent.MOUSE_DOWN,
  200.                 stageMouseHandler);
  201.     stage.removeEventListener(MouseEvent.MOUSE_UP,
  202.                 stageMouseHandler);
  203.     menu1.removeEventListener(MouseEvent.MOUSE_UP,
  204.                 menuMouseHandler);
  205.     menu2.removeEventListener(MouseEvent.MOUSE_UP,
  206.                 menuMouseHandler);
  207.     menu3.removeEventListener(MouseEvent.MOUSE_UP,
  208.                 menuMouseHandler);
  209.     menu4.removeEventListener(MouseEvent.MOUSE_UP,
  210.                 menuMouseHandler);
  211.     menu1.removeEventListener(MouseEvent.MOUSE_DOWN,
  212.                 menuMouseHandler);
  213.     menu2.removeEventListener(MouseEvent.MOUSE_DOWN,
  214.                 menuMouseHandler);
  215.     menu3.removeEventListener(MouseEvent.MOUSE_DOWN,
  216.                 menuMouseHandler);
  217.     menu4.removeEventListener(MouseEvent.MOUSE_DOWN,
  218.                 menuMouseHandler);
  219. }
  220. // необходимо импортировать List для использования его
  221. // в качестве параметра в hideAllMenusExcept()
  222. import fl.controls.List;
  223. /*
  224.  * принимаем параметр, указывающий, какое выпадающее меню List
  225.  * должно быть открыто. Другие меню будут делаться невидимыми.
  226.  * Передаем null для скрытия всех выпадающих меню.
  227.  */
  228. function hideAllMenusExcept(except:List):void {
  229.     menu1.visible = (except == menu1);
  230.     menu2.visible = (except == menu2);
  231.     menu3.visible = (except == menu3);
  232.     menu4.visible = (except == menu4);
  233. }

ICellRenderer против CellRenderer

В коде обработки события в прототипе, используем свойство target событий типа flash.controls.listClasses.ICellRenderer. Этот интерфейс должен быть реализован любым классом, в котором передается в качестве стиля cellRenderer для любого компонента, основанного на flash.controls.SelectableList, включая List, TileList и DataGrid. Особенно в контексте нашего прототипа, в котором мы явно установили свойство cellRenderer TileListCellRenderer, есть соблазн привести к этому классу вместо реализации интерфейса, и такой подход будет прекрасно работать в прототипе.

Однако в конечном счете этот код прототипа станет компонентом со своим собственным стилем cellRenderer — который мог быть установлен в любой определяемый пользователем класс, который реализует ICellRenderer, но не наследуется от CellRenderer. Фактически, стили cellRenderer характерные для DataGrid, fl.controls.dataGridClasses.DataGridCellEditor и fl.controls.dataGridClasses.HeaderRenderer не наследуются от CellRenderer.

Приведение экземпляров к интерфейсу более предпочтительно, чем к классу не только при создании компонентов, но и в повседневном программировании. Конкретный экземпляр ICellRenderer против CellRenderer это первое, что нужно помнить при создании компонентов использующих SelectableList.

В следующем шаге этой статьи Вы увидите, как переместить код из кадра в полноценный класс ActionScript 3.0.

Шаг 4: Преобразование кода из кадра в класс

Как заключительный шаг нашего прототипа, упакуем строку меню TileList и выпадающие меню List в символ с именем MenuBar. Экспортируем этот символ для ActionScript как fl.example.MenuBar и скопируем весь код из кадра во внешний файл ActionScript. Импортируем необходимые классы:

  1. import flash.display.Sprite;
  2. import flash.events.MouseEvent;
  3. import fl.controls.listClasses.CellRenderer;
  4. import fl.controls.listClasses.ICellRenderer;
  5. import fl.controls.List;


Объявим класс MenuBar, расширяющий Sprite. Позже будем использовать расширение fl.core.UIComponent, но пока не будем добавлять ненужные сложности для простого прототипа.

  1. public class MenuBar extends Sprite {
  2.     // keepMenuOpen используется для оповещения слушателя событий сцены,
  3.     // что он не должен закрывать меню, если событие было получено
  4.     // меню TileList или первым меню List
  5.     protected var keepMenuOpen:Boolean = false;

Sprite или MovieClip

Все классы, связанные с символом мувиклипа, должны наследоваться от flash.display.Sprite. Заметим, что такие классы не должны расширять Sprite. Например, класс flash.display.MovieClip, являющийся подклассом Sprite, часто используется как базовый класс и является фактически заданным по умолчанию классом для мувиклипов, экспортируемых для ActionScript.


Класс Sprite в языке ActionScript 3.0 по существу является мувиклипом с одним кадром. Он не имеет некоторых методов и свойств для управления таймлайном, харктерных для MovieClip, это — play(), gotoAndPlay(), currentFrame и currentScene, но имеет все другие API для управления мувиклипом, такие как startDrag() и graphics. Мотивация наследования от Sprite вместо MovieClip является вопросом чистого дизайна и передового опыта. Если мувиклип не будет использовать анимацию или кадры (в-общем все, что связано со шкалой времени) не рекомендуется размещать код в кадре, лучше использовать внешние файлы ActionScript. Дополнительную информацию о различиях между классами Sprite и MovieClip можно получить в справке по языку ActionScript 3.0.

Все Flash CS3 и Flex компоненты наследуются от Sprite, а не от MovieClip. Класс fl.core.UIComponent расширяет Sprite, поэтому любые компоненты на основе инфраструктуры компонента UI наследуются от Sprite, а не от MovieClip.

Когда класс подключен к мувиклипу, расширяющему Sprite, накладываются некоторые ограничения:

  • Мувиклип не может иметь скриптов в кадре.
  • Мувиклип не может иметь любые экземпляры компонента на сцене с настраиваемыми параметрами. Экземпляры компонента должны иметь параметры по умолчанию.
  • Мувиклип не может иметь любые экземпляры на сцене с доступной информацией, примененной к нему через панель Accessibility.
  • Шкала времени мувиклипа никогда не будет проигрываться. Воспроизводящая головка останется на кадре 1.

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

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

Концептуально мувиклип, имеющий класс, расширяющий Sprite но не MovieClip, должен иметь только один кадр, поскольку Sprite вообще не имеет шкалы времени. Поэтому спрайты подходят для Flex, который не имеет Timeline. Таким образом, может показаться, что несколько кадров в спрайте недопустимо. Но, компоненты Flash ActionScript 3.0 состоят также из двух кадров, как и компоненты ActionScript 2.0, второй кадр в них никогда не должен отображаться. Подход с двумя кадрами подробно изложен в разделах «Исследование структуры компонентов основанной на двух кадрах» (Examining the basic two-frame structure of components) и «Работа с продвинутыми FLA структурами» (Working with advanced FLA structures).

Рассмотрим код:
  1. // keepMenuOpen используется для сигнала слушателю событий сцены
  2. // что он не должен закрывать меню, если событие получено
  3. // строкой меню TileList или сначала выпадающим меню List
  4. protected var keepMenuOpen:Boolean = false;

При объявлении keepMenuOpen добавим модификатор protected. Модификаторы не допускаются в сценарии кадра, но они очень полезны в классах. Например, в MenuBar не используется private, что зачастую ограничивает класс, который может быть дополнен другими разработчиками. При разработке необходимо использовать модификаторы protected или public.

Важно отметить, что до сих пор не добавлены объявления для экземпляров на сцене: myMenuBar, menu1, menu2, menu3 и menu4. Этого не нужно делать, поскольку опция «автоматическое объявление экземпляров сцены» («Automatically declare stage instances») была выбрана в параметрах публикации в окне ActionScript 3.0 Settings файла MenuBarPrototype.fla.

Понятие опции: Автоматическое объявление экземпляров на сцене (Automatically declare stage instances)

Существует очень важный флажок в диалоге ActionScript 3.0 Settings в параметрах публикации на закладке Flash (см. рис. 5). Важно понимать эту настройку для общей разработки на ActionScript 3.0 но особенно важно для разработки компонентов.
Рисунок 5. Диалоговое окно ActionScript 3.0 Settings с выбранным флажком "Automatically declare stage instances"



































Что делает этот флажок?

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

В заключительном шаге прототипа MenuBar, выйдем из опции проверки в файле FLA, поэтому не нужно делать объявлений в реализации MenuBar:
  1. public var myMenuBar:TileList;
  2. public var menu1:List;
  3. public var menu2:List;
  4. public var menu3:List;
  5. public var menu4:List;

Так как мы сохраниил опцию проверки, то уже не должны импортировать fl.controls.TileList; когда экземпляры автоматически обновляются, необходимый импорт также добавляется автоматически. Объявления имен экземпляров на сцене должны быть с модификатором public.

Если добавить эти объявления в MenuBar.as и не снять флажок с "Automatically declare stage instances", то получим сообщение об ошибке компиляции о том, что одна переменная объявлена дважды. Ошибка будет выглядеть так:
1151: A conflict exists with definition myMenuBar in namespace internal.

Если же не добавить эти объявления и снять флажок с "Automatically declare stage instances", то получим ошибку выполнения о том, что переменная не объявлена. Выглядеть эта ошибка будет так:
ReferenceError: Error #1056: Cannot create property asdf on fl.example.FooFoo.
    at flash.display::Sprite/flash.display:Sprite::constructChildren()
    at flash.display::Sprite$iinit()
    at flash.display::MovieClip$iinit()
    at fl.example::FooFoo$iinit()

Вообще, включение этого чекбокса – личное дело разработчика. Главное, понять, что эта опция дает и написать код в соответствии с ним.


Почему этот флажок важен для разработки компонента?

На самом деле, этот флажок не так важен для SWC компонентов, но имеет решающее значение для FLA компонентов. Хотя FLA компоненты являются мувиклипами, скопированными в пользовательском FLA файле, и хотя эти флажки могут быть поставлены (или нет) в этом FLA файле, все FLA компоненты должны быть правильно составлены, независимо от настройки этого флажка.

Это означает, что компоненты FLA не могут иметь ни одного экземпляра символа на сцене с именем экземпляра (instance names). Они также не могут иметь экземпляры компонентов на сцене с настроенными параметрами, так как этот флажок автоматически генерирует имена экземпляров. (Дополнительные сведения по этому вопросу смотри в предыдущей вставке «Спрайт против Мувиклипа» "Sprite vs. MovieClip").

При тестировании компонента FLA, очень важно проверить работу с включенной и выключенной опцией "Automatically declare stage instances", чтобы гарантировать отсутствие ошибок символа компонента в файле FLA.

Рассмотрим код:
  1. public function MenuBar()
  2. {
  3.    // отключение selectable делает поведение
  4.    // образца TileList более похожим на меню
  5.    myMenuBar.selectable = false;
  6.    // отключение selectable делает поведение
  7.    // образца List более похожим на выпадающее меню
  8.    menu1.selectable = false;
  9.    menu2.selectable = false;
  10.    menu3.selectable = false;
  11.    menu4.selectable = false;
  12.    // Дефолтный стиль cellRenderer для TileList это
  13.    // fl.controls.listClasses.ImageCell, который
  14.    // располагает ярлык под изображением.  В нашем случае
  15.    // это позволяет поместить ярлык вверху
  16.    // ячейки, не в центре, таким образом, вместо этого
  17.    // стиля мы используем fl.controls.listClasses.CellRenderer
  18.    // который центрирует ярлык в ячейке.
  19.    myMenuBar.setStyle("cellRenderer", CellRenderer);
  20.    // необходимо прослушивать событие mouseDown на строке
  21.    // меню TileList для запуска обработчика меню.
  22.    // Другие обработчики события мыши будут добавлены
  23.    // после инициализации mouseDown на строке меню TileList
  24.    // и удалены, когда меню будут закрыты.
  25.    myMenuBar.addEventListener(MouseEvent.MOUSE_DOWN,
  26.                 menuBarMouseHandler);
  27. }


Вся инициализация кода, соответствующая сценарию кадра, для класса не будет работать. Таким образом, вытащим весь код инициализации в конструктор.

Кроме новой вставки остальной код остался практически без изменений, за исключением одной строки в функции menuMouseHandler()
  1.         /*
  2.         * Обработчик событий мыши для выпадающих меню List.
  3.         */
  4.         private function menuMouseHandler(e:MouseEvent):void {
  5.                 // смотри комментарий в menuBarMouseHandler для
  6.                 // понимания следующих двух строк кода
  7.                 var cellRenderer:ICellRenderer = e.target as
  8.                                                    ICellRenderer;
  9.                 if (cellRenderer == null) return;
  10.                
  11.                 switch (e.type)
  12.                 {
  13.                         case MouseEvent.MOUSE_UP:
  14.                                 // пока не делаем какой либо диспетчеризации
  15.                                 // события, пока только отслеживаем
  16.                                 trace(cellRenderer.data.label);
  17.                                 // кроме того, содержание TextField на сцене
  18.                                 // предназначено для прототипа, удалим его,
  19.                                 // когда перейдем к компоненту
  20.                                 parent["selectedMenu_txt"].text =
  21.                                         cellRenderer.data.label;
  22.                                 closeMenuBar();
  23.                                 break;
  24.                         case MouseEvent.MOUSE_DOWN:
  25.                                 // препятствуем слушателю сцены закрывать
  26.                                 // меню используя keepMenuOpen. Комментарии
  27.                                 // для события mouseUp
  28.                                 // в menuBarMouseHandler()
  29.                                 keepMenuOpen = true;
  30.                         break;
  31.                 }
  32.         }
  33.        
  34.         ...
  35.        
  36.         }
  37. }

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


От переводчика: я так и не разобрался с тем, как автору удалось сделать выпадающие списки List изначально невидимыми (невидимыми в среде разработки Flash IDE, а соответственно и в итоговом ролике).
Позже я нашел, что во Flash IDE закладку Parameters можно открыть двумя способами. Первый – Window>Properties>Parameters, второй – Window>ComponentInspector>закладка Parameters. Так вот, в первом случае в этой закладке не отображается параметр visible, во втором – отображается.


Более подробно: parent.selectedMenu_txt против parent["selectedMenu_txt"]

В ActionScript 2.0 можно использовать следующую строку кода:
  1. _parent.selectedMenu_txt.text = cellRenderer.data.label;

для обращения к динамическому текстовому объекту с именем экземпляра selectedMenu_txt. Однако, в ActionScript 3.0, с включенным строгим режимом эта строка приведет к ошибке компиляции. Потому что объявленный тип parent это flash.display.DisplayObjectContainer, который не объявлен как dynamic.


Напротив, следующий код будет работать без ошибок компиляции и ошибок воспроизведения:
  1. var theParent:MovieClip = parent as MovieClip;
  2. theParent.selectedMenu_txt.text = cellRenderer.data.label;

Этот код компилируется, поскольку MovieClip объявлен как dynamic. Это значит, что даже в строгом режиме можно ссылаться на любое свойство или метод экземпляра MovieClip. Ошибки выполнения не будет, так как родительский - это тип MovieClip, и что родитель на самом деле обладает свойством selectedMenu_txt, которое имеет свойство с именем text.

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

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

Файлы примеров:
menubar_component.zip (5.4 mb)
menubar_prototype.zip (1.5 mb)

2 комментария:

  1. Потерялись файлы исходников!

    ОтветитьУдалить
    Ответы
    1. Вот ссылка на часть исходной статьи. http://www.adobe.com/devnet/archive/flash/articles/creating_as3_components_pt2.html
      Там есть рабочие ссылки.

      Удалить