суббота, 29 января 2011 г.

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

Часть 3: От прототипа к компоненту

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

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



Исследование структуры компонентов, основанной на двух кадрах

У мувиклипа компонента есть два кадра. Первый кадр всегда содержит только один экземпляр на сцене, это аватар, который нужен только для определения размеров компонента, заданных по умолчанию. Второй кадр содержит активы, доступные коду ActionScript компонента во время выполнения. Структура компонентов с двумя кадрами очень похожа на структуру компонентов AS2, за исключением того, что в компонентах AS3 в первом кадре нет метода stop(). Фактически скрипт кадра не может быть использован, поскольку компонент наследуется от Sprite а не от MovieClip. См. врезку «Спрайт против мувиклипа» ("Sprite vs. MovieClip") для необходимого понимания этого вопроса.

Двухкадровое танго

Есть одна неприятность, связанная с использованием двух кадров в компонентах. Все, кто использует компоненты ActionScript 3.0 неизбежно замечали, что когда в файле FLA, использующем компоненты, возникает какая-либо ошибка, то Flash Player попеременно показывает оба кадра с установленной частотой. Это происходит, когда присутствуют ошибки компиляции, из-за которых байт-код не попадает в SWF файл, а это означает, что клип компонента не связан с каким-либо классом-наследником Sprite или MovieClip, и Flash Player проигрывает такой компонент как обычный мувиклип.Такое поведение рассматривается не только когда присутствуют ошибки в коде компонента, но и любые другие ошибки, поэтому все разработчики когда-либо с этим встречались. Это происходит с компонентами на основе FLA и SWC, которые используют двухкадровую структуру.

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

Изменив символы MenuBar на шкале времени, добъемся соответствия структуре с двумя кадрами (см. Рис. 1).

Рисунок 1. Добавлен второй кадр на Timeline в соответствии с базовой структурой компонента
















Слой с аватаром

Слой аватара содержит прямоугольник без заливки с толщиной линии hairline, размещенный в координатах (0, 0). Прямоугольник имеет ширину 400 и высоту 22 пикселя, которые определяют размеры компонента по умолчанию. Выбор дефолтных размеров довольно произволен, выбраны размеры прототипа равные 400x22.

Очень важно, что бы прямоугольник был нарисован hairline линией! Размеры границ формы из более толстой линии не всегда ограничены теми размерами, что Вы видите в инспекторе Property, и при выполнении размеры компонента на сцене могут отличаться от ожидаемых.

Аватаром может быть что угодно: простая форма, графический символ или символ мувиклипа. Важно то, что на сцене в первом кадре это должен быть единственный объект. Во время выполнения он будет удален со сцены. Как указывалось ранее во вставке «Понимание опции: Автоматическое объявление экземпляров сцены» (Understanding the option: Automatically declare stage instances) важно, чтобы экземпляр не имел имени (instance name).

Этот слой блокирован (поскольку его редактировать не надо) и не скрыт. Вы никогда не должны скрывать слои в мувиклипе Вашего компонента, поскольку опция «Export hidden layers» в параметрах публикации все равно может быть снята пользователем.

Слой активов

Существуют важные требования для всех символов библиотеки, которые будут динамически обрабатываться компонентом:
  • Символ должен быть во втором кадре символа компонента
  • Символ должен иметь настройку «Export for ActionScript»
  • Опция «Export on first frame» должна быть снята

Слой с активами содержит все символы, которые будут нужны во время выполнения компонента. В нашем простом случае в этом слое необходимо разместить только два символа, это компоненты List и TileList, таким образом на сцену помещены по одному их экземпляру. Как говорилось ранее, во вставке «Понимание опции: Автоматическое объявление экземпляров сцены» (Understanding the option: Automatically declare stage instances), у составляющих компонент экземпляров не должно быть имени (instance names) и все параметры для экземпляров приняты по умолчанию.

Когда пользователь перетаскивает компонент из панели компонентов, Flash импортирует символ компонента в библиотеку и также импортируются все символы составляющие этот компонент. Такое же поведение можно наблюдать при копировании и вставке символа из одного файла FLA в другой. Так что если какой-либо элемент не был во втором кадре, то он не будет импортирован в библиотеку. Кроме того, поскольку для символов снята опция «Export on first frame», то они не будут экспортироваться в SWF, и размещение во втором кадре гарантирует, что они будут экспортироваться в SWF вместе с символом компонента.

Слой активов не блокирован. Это место используется для размещения редактируемых пользователем символов. Данный символ компонента не настроен для редактирования пользователем, но в разделе «Работа с продвинутыми FLA структурами» (Working with advanced FLA structures) мы увидим, как добавить редактируемые скины в слой активов. Слой активов не скрыт.

Почему важно снимать флажок с «экспорт в первый кадр» для экземпляра символа?

Отключив опцию "Export on first frame" мы облегчаем пользователю жизнь, поскольку он может изменить флажок компонента и управлять экспортом его активов в SWF. Выбор «экспорт в первый кадр» для символа гарантирует, что его определение экспортируется в публикуемый SWF файл без размещения на сцене. Поскольку все активы компонента находятся на сцене во втором кадре символа компонента, они подлежат экспорту наряду с символом компонента.

Например, если пользователь имеет компонент Button в библиотеке, но не перетащил его на сцену и не установил флажок на "Export on first frame",этот компонент не будет экспортирован в SWF, как и все его активы, поскольку ни один из них не экспортируется в первый кадр. С другой стороны, если все символы будут выбраны для экспорта в первый кадр, то они попадут в SWF, увеличив его размер, даже несмотря на то, что компонент Button не был экспортирован, и пользователь вынужден будет в своей библиотеке во всех этих символах снимать галочки "Export on first frame". Компонент Button, являющийся одним из простых компонентов, насчитывает в активе 12 символов. Компонент DataGrid содержит 42 символа. Можете себе представить, как трудно было бы пользователю управлять свойствами этих активов в отдельности.

С помощью нашего подхода пользователь сможет поставить флажок непосредственно для компонента и все зависимые активы будут экспортированы в первый кадр. Если мы установим "Export on first frame" для каждого зависимого актива, пользователю надо будет изменить свойства многих различных символов библиотеки с экспортированными активами, что станет его головной болью.

Расширение fl.core.UIComponent

Чтобы использовать инфраструктуру сомпонента UI, Ваш класс компонента должен быть унаследован от fl.core.UIComponent. В случае с примером MenuBar расширяем напрямую UIComponent, используя код:

  1. import fl.core.UIComponent;
  2. public class MenuBar extends UIComponent {
Однако класс, связанный с символом мувиклипа компонента, не обязан расширять напрямую UIComponent. Напимер, fl.controls.Button расширяет fl.controls.LabelButton, который расширяет fl.controls.BaseButton,который расширяет fl.core.UIComponent. В зависимости от типа разрабатываемого компонента для лучшей работы можно расширить UIComponent, или можно расширить класс компонента CheckBox или ProgressBar, или Вы можете решить расширить один из общих базовых классов, используемых компонентами, как пример SelectableList, который является базовым классом для DataGrid, List и TileList.

Другое изменение, которое надо сделать, это добавить объявление переменных для экземпляров на сцене. В прототипе объявления переменных были добавлены автоматически. Как обсуждалось ранее, во вставке «Понимание опции: Автоматическое объявление экземпляров сцены» (Understanding the option: Automatically declare stage instances) важно, чтобы экземпляр мувиклипа компонента на сцене не имел названия. Кроме того, в классе компонента необходимо объявлять все переменные:

  1. // TileList для полосы меню
  2. protected var myMenuBar:TileList;
  3. // перечень List для выпадающих меню
  4. protected var myMenus:Array;
Первая версия нашего компонента претерпела множество изменений по сравнению с прототипом для перехода от menu1, menu2, menu3 и menu4 к массиву myMenus. Все комментарии будут в коде. (prototype_to_component.zip (ZIP, 917K))

Не расширяя UIComponent

Вполне возможно создать компонент не расширяя UIComponent или одного из его подклассов. Такой компонент не может полностью использовать инфраструктуру компонента UI, поэтому он не может использовать преимущества поддержки стилей и обложек (скинов), invalidation модель или управление фокусом. Инфраструктура делает многое за Вас, поэтому, если Вы решили создать компонент, не использующий инфраструктуру, Вам придется написать весь код самостоятельно.

Если построение Вашего компонента сильно не согласуется с этим фреймворком, то имеет смысл написать собственный код. Компонент FLVPlayback не основан на инфраструктуре по этой причине. Компонент FLVPlayback имеет несколько разных моделей обложки; он имеет достаточно простой пользовательский интерфейс, invalidation модель не является необходимой и он не поддерживает фокус клавиатуры. Компонент FLVPlayback включает в себя код, позволяющий ему делать некоторые вещи, которые делает инфраструктура. Например, он имеет код для установки свойства isLivePreview для удаления формы аватара, а также для переопределения свойств width, height, scaleX и scaleY устанавливая собственные размеры.

Маловероятно, что Вы найдете веские причины не использовать инфраструктуру компонента UI,и даже если решитесь, то создание будет очень сложным. Но, важно отметить, что вполне реально создание компонента без расширения UIComponent.

Определение метода configUI()

Метод configUI() вызывается конструктором fl.core.UIComponent после важной инициализации стилей и завершения поддержки invalidation. Поместите здесь свой код инициализации, который включает создание отображаемых объектов, инициализируя их и добавляя в список отображения. Метод configUI() является лучшим местом для этой инициализации, чем конструктор, так как Ваш созданный список отображения может взаимодействовать со стилями или процессами invalidation, особенно, если созданные отображаемые объекты являются подкомпонентами.

Реализация configUI() всегда должна вызывать сначала super.configUI(). UIComponent, осуществляющий configUI(), делает множество важных инициализаций, включая инициализацию свойств width и height так, что они могут быть использованы в Вашем коде configUI(). Они также проводят работу по удалению формы аватара из списка отображения, что должно быть выполнено прежде, чем код Вашего компонента начнет добавлять элементы списка отображения.

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

  1. public function MenuBar()
  2. {
  3.         // инициализация пустого Array для избежания
  4.         // ненужных проверок
  5.         myMenus = new Array();
  6.        
  7.         // инициализация dataProvider (чтобы не был null)
  8.         // для предохранения от дальнейших проверок
  9.         if (dataProvider == null)
  10.         {
  11.                 dataProvider = new DataProvider();
  12.         }
  13. }
  14.        
  15. override protected function configUI():void
  16. {
  17.         // всегда вызывается super.configUI() в Вашей реализации
  18.         super.configUI();
  19.        
  20.         // динамическое создание myMenuBar
  21.         myMenuBar = new TileList();
  22.         addChild(myMenuBar);
  23.        
  24.         // конфигурирование myMenuBar
  25.         myMenuBar.selectable = false;
  26.         myMenuBar.setStyle(“cellRenderer”, CellRenderer);
  27.         myMenuBar.addEventListener(MouseEvent.MOUSE_DOWN,
  28.                 menuBarMouseHandler);
  29. }
В конструкторе инициализируем свойства myMenus и dataProvider, не являющиеся объектами отображения. Даже если Вы не знакомы с DataProvider, должно быть очевидно, что это не отображаемый объект, поскольку для него не вызван addChild() или addChildAt(). В configUI() создадим и инициализируем myMenuBar, который является отображаемым объектом, добавленным в список отображения. Заметьте, что здесь не добавлен код, создающий экземпляры List, для выпадающего меню; не добавлен потому, что всегда есть один TileList для строки меню, но может быть любое число экземпляров List, в зависимости от содержания меню. Важно отметить, что не все Ваши отображаемые объекты должны быть созданы и настроены в configUI(), но, как правило, нужно добавить любые отображаемые объекты, которые будут созданы один раз при инициализации и затем храниться, пока используется компонент MenuBar.

Пример замены отображаемых объетов в configUI()

MenuList это класс, который будет добавлен позднее, когда добавится поддержка стиля, но он имеет интересный метод configUI(), поэтому немного забежим вперед и рассмотрим его сейчас:

  1. package fl.example.menuBarClasses
  2. {
  3.        
  4.         import fl.controls.List;
  5.         import fl.controls.ScrollPolicy;
  6.        
  7.         public class MenuList extends List
  8.         {
  9.        
  10.                 override protected function configUI():void
  11.                 {
  12.                         super.configUI();
  13.                        
  14.                         // удалим _verticalScrollBar и заменим
  15.                         // на NoScrollBar
  16.                         removeChild(_verticalScrollBar);
  17.                         _verticalScrollBar = new NoScrollBar();
  18.                        
  19.                         // удалим _horizontalScrollBar и заменим
  20.                         // на NoScrollBar
  21.                         removeChild(_horizontalScrollBar);
  22.                         _horizontalScrollBar = new NoScrollBar();
  23.                        
  24.                         // для безопасности установите оба
  25.                         // прокрутчика как OFF
  26.                         _horizontalScrollPolicy = ScrollPolicy.OFF;
  27.                         _verticalScrollPolicy = ScrollPolicy.OFF;
  28.                 }
  29.         }
  30. }
Причина, по которой создан подкласс MenuList класса List в том, что наш компонент MenuBar никогда не будет нуждаться в прокрутчике, но в реализации configUI() класса fl.containers.BaseScrollPane (суперкласс класса List и TileList) создается список из двух экземпляров ScrollBar. Поскольку компонент MenuBar никогда не будет использовать полосу прокрутки, удалим соответствующие элементы обложки из FLA файла, а это означает, что два экземпляра ScrollBar вызовут рантайм ошибку, если вызвать их методы draw(). Немедленным их удалением, возможно сохранить их от получения invalidation, поэтому они никогда не будут нарисованы.

Однако, оставляя свойства _horizontalScrollBar и _verticalScrollBar равными null мы получим ошибку выполнения, поэтому заменим их упрощенной версией NoScrollBar, который никогда не будет добавлен в список отображения и, значит, не будет видимым. Ранее было сказано, что недобавление отображаемого объекта в список отображения является необычным, и это как раз и есть тот самый необычный случай. NoScrollBar это очень простой подкласс ScrollBar который никогда ничего не нарисует:

  1. package fl.example.menuBarClasses
  2. {
  3.        
  4.         import fl.controls.ScrollBar;
  5.         import fl.core.InvalidationType;
  6.        
  7.         public class NoScrollBar extends ScrollBar
  8.         {
  9.                 /*
  10.                  * переопределение invalidate(), поэтому он ничего
  11.                  * не делает.  Это должно уберечь draw() от вызовов
  12.                  */
  13.                 override public function invalidate
  14.                         (property:String = InvalidationType.ALL,
  15.                          callLater:Boolean = true):void
  16.                 {
  17.                 }
  18.                
  19.                 /**
  20.                  * переопределение draw(), поэтому этот метод ничего
  21.                  * не делает, в случае, если будет вызван.
  22.                  */
  23.                 override protected function draw():void
  24.                 {
  25.                 }
  26.         }
  27. }
Этот пример кажется запутанным, не волнуйтесь. Этот раздел достаточно сложный, поэтому Вы можете не усвоить его сейчас, вернитесь к нему позже, когда закончите серию. Методом проб и ошибок было найдено это простое решения для взлома полосы прокрутки, и как всегда в таких случаях очень помогает отладчик с пошаговым выполнением кода. Пример компонента MenuBar намеренно сделан несколько сложным в надежде на то, что некоторые уловки окажутся полезными в создании компонентов.

В следующем разделе этой статьи мы рассмотрим, как работать с методом draw() и отображаемыми динамическими составляющими компонента MenuBar.

Реализация метода draw()


Пока configUI() является местом для инициализации, при создании компонента, draw() является местом для размещения кода, вызываемого каждый раз, когда компонент требует нарисовать себя. В случае с компонентом MenuBar, метод draw() экземпляра List для выпадающего меню создан и сконфигурирован. Это также место, где конфигурируется dataProvider для myMenuBar.


Вызов draw() запускается предварительным вызовом invalidate(), который будет рассматриваться более подробно при обсуждении invalidation в части 6. Ваш код никогда не должен вызывать метод draw() напрямую, хотя иногда Вы можете вызывать метод drawNow(), о чем рассказано в следующем разделе. В примере MenuBar я вызываю invalidate() из сеттера для dataProvider и из handleDataChange(), обработчик события вызывается, когда произведены изменения в dataProvider. Для других ситуаций, требующих invalidation (аннулирования), таких как изменение размеров через изменение свойств width или scaleY, изменение стиля и изменение свойства enabled, инфраструктура компонента UI обрабатывает необходимое invalidation (аннулирование).


В последней строке Вашего метода draw() Вы должны вызвать super.draw().Реализация метода draw() в UIComponent выполняет две важных функции: когда необходимо рисует focusRectSkin и вызывает validate(), который очищает состояние invalidation.

Большая часть нашей реализации draw() использует простую математику для выкладывания субкомпонентов:

  1. override protected function draw():void
  2. {
  3.         // сначала все очистим.
  4.         clearMenus();
  5.        
  6.         // изменение размеров строки меню
  7.         myMenuBar.width = width;
  8.         myMenuBar.height = height;
  9.         myMenuBar.rowHeight = height;
  10.        
  11.         // заполнение строки меню и создание выпадающих меню
  12.         // для каждой записи dataProvider
  13.         for (var i:int = 0; i < _dataProvider.length; i++) {
  14.                 initMenu(_dataProvider.getItemAt(i));
  15.         }
  16.        
  17.         // если меню есть, то устанавливаем rowHeights, heights,
  18.         // widths и всё размещаем
  19.         if (myMenus.length > 0) {
  20.                 // равномерное распределение меню по строке меню
  21.                 myMenuBar.columnWidth =
  22.                         (myMenuBar.width / myMenus.length);
  23.                 // разместим каждое выпадающее меню ниже
  24.                 // соответствующей ячейки строкового меню,
  25.                 // сделаем его такой же ширины, как и
  26.                 // соответствующая ячейка и приведем ее высоту
  27.                 // по содержимому
  28.                 for (var j:int = 0; j < myMenus.length; j++) {
  29.                         var theMenu:List = (myMenus[j] as List);
  30.                         theMenu.x = (myMenuBar.columnWidth * j);
  31.                         theMenu.y = myMenuBar.height;
  32.                         theMenu.width = myMenuBar.columnWidth;
  33.                         theMenu.height =
  34.                           theMenu.dataProvider.length * theMenu.rowHeight;
  35.                         // Эта строка очень важна!  Аннулирование
  36.                         // (Invalidating) подкомпонента во время
  37.                         // вызова validate() или draw() может
  38.                         // оставить вещи в непонятной области,
  39.                         // которая никогда не проверяет подкомпоненту.
  40.                         // Когда вещи не обновляются, как должны,
  41.                         // вызывается drawNow()!
  42.                         theMenu.drawNow();
  43.                 }
  44.         }
  45.         // см. предыдущий коментарий по использованию drawNow()
  46.         myMenuBar.drawNow();
  47.        
  48.         // в конце всегда вызывается super.draw()
  49.         super.draw();
  50. }
  51.        
  52. /*
  53.  * удаление всех слушателей событий, стираются данные
  54.  * из строкового меню и уничтожаются все
  55.  * выпадающие Lists из массива myMenus
  56.  */
  57. protected function clearMenus():void {
  58.         closeMenuBar();
  59.         myMenuBar.dataProvider = new DataProvider();
  60.         while (myMenus.length > 0) {
  61.                 var theMenu:List = (myMenus.shift() as List);
  62.                 removeChild(theMenu);
  63.         }
  64. }
  65.        
  66. /*
  67.  * настраиваются пункты меню для данного индекса.
  68.  * Захват элемента dataProvider для этого экземпляра,
  69.  * используя ярлык для метки элемента в строковом меню,
  70.  * создание выпадающего меню и анализируются данные,
  71.  * разделенные запятой для заполнения выпадающего меню.
  72.  */
  73. protected function initMenu(item:Object):void {
  74.         myMenuBar.dataProvider.addItem({label:item.label});
  75.         var str:String = item.data;
  76.         var tokens:Array = str.split(",");
  77.         var theMenu:List = createMenu();
  78.         for (var i:int = 0; i < tokens.length; i++) {
  79.                 theMenu.dataProvider.addItem({label:tokens[i]});
  80.         }
  81. }
  82.        
  83. /*
  84.  * создание экземпляра выпадающего списка.
  85.  */
  86. protected function createMenu():List {
  87.         var theMenu:List = new List();
  88.         theMenu.visible = false;
  89.         theMenu.selectable = false;
  90.         myMenus.push(theMenu);
  91.         addChildAt(theMenu, 0);
  92.         return theMenu;
  93. }
Экземпляр fl.data.DataProvider определяет содержание строки меню и выпадающих меню. Свойство dataProvider для MenuBar содержит те же данные, что и dataProvider для List; для обоих списков объектов со свойствами label и data. Для компонента MenuBar label идентифицирует подпись строкового меню и данные пунктов выпадающего меню в виде списка, разделенные запятой. Код, обрабатывающий каждый объект из DataProvider находится в initMenu. Более подробно свойство dataProvider обсудим в разделе «взаимодействие параметров компонента и метаданных ActionScript» ("Exposing component parameters and ActionScript metadata").

Каждый вызов метода draw() удаляет и создает заново все выпадающие меню. Позже, в части 6, использование модели аннулирования будет более элегантно в части изменений в методе draw().

Понимание важности drawNow()и validateNow()

По мере исследования метода draw() Вы заметите комментарий, указывающий на требование вызова метода drawNow(). Работая с компонентами нельзя забывать о функциях drawNow() и validateNow(), поскольку иногда они остаются единственным решением некоторой проблемы, заставляющей Вас ломать голову.

Если Ваши подкомпоненты правильно не обновляются и при этом код выглядит верным, можно начать добавлять вызовы drawNow(); если это не сработает, попробуйте validateNow(). Эти методы работают как предохранительные клапаны, если компоненты не работают правильно. Вы всегда должны стараться поставить drawNow() перед validateNow() поскольку в некоторых случаях validateNow() вызовет лишнюю перерисовку, что неэффективно.

drawNow() и validateNow() под колпаком

Вызов drawNow() непосредственно вызовет draw(). Вызов validateNow() приведет к полному invalidation (аннулированию) и немедленному вызову draw(). Код для этих двух методов, определенных в UIComponent, очень прост:

  1. public function validateNow():void
  2. {
  3.         invalidate(InvalidationType.ALL,false);
  4.         draw();
  5. }
  6.        
  7. public function drawNow():void
  8. {
  9.         draw();
  10. }

Выполнение drawNow() просто вызывает draw(). Это может показаться излишним, но необходимо, поскольку draw() защищен. В примере draw() выше из MenuBar, можно было не вызывать draw() экземпляра компонента, а вместо этого вызвать drawNow() поскольку он имеет модификатор public.

Метод invalidateNow() передает InvalidationType.ALL в качестве первого параметра invalidate(), гарантируя, что каждый путь кода в draw() будет проверен для любого типа invalidate (аннулирования). Далее, он передает false в качестве следующего параметра, предотвращая запланированный вызов draw() в следующем событии enterFrame. Затем, он вызывает draw().

В случае draw() реализации MenuBar, проблема заключается в том, что при аннулировании вызова draw() зачастую нельзя вызвать draw() при следующем событии enterFrame. Это сделано намеренно – если обработчик события enterFrame вызывает draw() после аннулирования, это не позволит выполнить запланированный draw() при следующем enterFrame. Это ограничение препятствует тому, чтобы компонент из недействительного метода draw() вызывал draw() чтобы всегда вызываться при каждом событии enterFrame.


Взаимодействие параметров компонента и метаданных ActionScript

Вы найдете метаданные ActionScript в коде любого компонента: на ActionScript 2.0 и ActionScript 3.0, во Flash и Flex. Метаданные ActionScript заключаются в квадратных скобках. У метаданных есть имя, сопровождающееся двумя списками имя/значение (name/value), помещенными в круглых скобках. Например, вот метаданные с именем Inspectable и одной парой имя/значение – defaultValue/Label:

  1. [Inspectable(defaultValue="Label")]
Если имеется только одно значение, метаданные фактически могут быть безымянными:

  1. [IconFile("FLVPlayback.png")]
Когда имеется несколько пар имя/значение, метаданные выглядят так:


  1. [Inspectable(defaultValue="vertical", type="list", enumeration="vertical,horizontal")]
Метаданные ActionScript применяются к определению, следующим за ними. Для компонентов Flash, метаданные всегда применяются к определению класса или описанию свойств. Отметьте, что теже метаданные не всегда поддерживаются для компонентов Flash как для компонентов Flex. Перейдите к Flex документации для получения дополнительной информации о поддерживаемых метаданных.


Метаданные свойства уровня должны один раз появиться перед определением свойств setter или getter; не размещайте дубликаты метаданных перед каждой функцией.

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

Inspectable метаданные

Inspectable метаданные самые общие. Они действуют на свойства и воздействуют на них, как параметры компонентов в инспекторе компонентов и инспекторе свойств. Метаданные Inspectable поддерживают свойства type, defaultValue, enumeration, verbose и name.

Type (тип)

Свойство type определяет тип параметра компонента. Оно не обязательно и если оно не задано, то определится типом свойства ActionScript (см. таблицу 1).


Таблица 1. Поддерживаемые значения для типов параметров

Тип параметра
Тип ActionScript информации
Значение по умолчанию
String
По умолчанию для String
Пустая строка
Number
По умолчанию для Number, int и uint
0
Boolean
По умолчанию для Boolean
false
Array
По умолчанию для Array
null
Color
Свойство должно быть типа uint
#000000
List
Свойство должно быть типа String
Первый элемент из перечисленных
Также существуют типы, специфичные для компонента FLVPlayack и они здесь не описаны.


defaultValue (значение по умолчанию)

Свойство defaultValue указывает первоначальное значение параметра, когда экземпляр компонента перетаскивается на сцену. Свойство defaultValue не обязательно и если оно не указано, то принимает значение по умолчанию, указанное в таблице. Значение по умолчанию для параметра типа Array должно быть указано в виде списка с запятой в качестве разделителя. Значение по умолчанию для Color должно быть записано в виде #RRGGBB. Значение по умолчанию для List должно быть одним из значений списка в перечисленных свойствах для параметра.


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


enumeration (перчисление)

Свойство enumeration используется только с параметрами типа List; если тип не определен и используется свойство enumeration, то тип считается списком (List). Свойство enumeration является списком возможных параметров с запятой в качестве разделителя. Пользователь сможет выбрать одно из этих значений в раскрывающемся списке в инспекторе компонентов. Вот отрывок кода из BaseScrollPane.as, с удаленными комментариями:

  1. [Inspectable(defaultValue="auto",enumeration="on,off,auto")]
  2. public function get horizontalScrollPolicy():String
  3. {
  4.         return _horizontalScrollPolicy;
  5. }
  6. public function set horizontalScrollPolicy(value:String):void
  7. {
  8.         _horizontalScrollPolicy = value;
  9.         invalidate(InvalidationType.SIZE);
  10. }

verbose

Если свойство verbose установлено true, то свойство появится только в инспекторе компонентов, и не будет отображаться в инспекторе свойств. Свойство verbose необязательно и значение по умолчанию false.

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

name (название)

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

Collection метаданные


Collection метаданные также предоставляют свойство в качестве параметра. Параметр, определенный метаданными Collection сформирует диалоговое окно Values, которое позволяет пользователю определить список объектов, соответствующих определенному шаблону. Collection поддерживает свойства collectionClass, collectionItem, identifier и name.

Для компонента MenuBar создадим очень простое свойство dataProvider, которое как и свойство dataProvider для List, берет поле label и поле data. Поле label определяет ярлык для пункта меню и поле data представляет разделенный запятыми список элементов для соответствующего раскрывающегося меню (см. рисунок 2).
Рисунок 2. Диалоговое окно отображаемых значений label и значений data




















Мы сделали это для начала осуществления нашего dataProvider, включая метаданные Collection, реализованные в SelectableList.as и тем самым сэкономили время и силы. Если сравнить наш код с кодом из SelectableList.as, Вы увидите, что функции getter и setter нашего dataProvider и наш обработчик DataEvent являются упрощенными версиями этого кода.

collectionClass

Свойство collectionClass определяет класс, который будет создан и используется для задания Вашего свойства. Это обязательно. Тип Вашего свойства не обязательно должен точно соответствовать свойству collectionClass, но тип должен быть суперклассом со свойством collectionClass или интерфейсом, который он реализует, или Вы столкнетесь с ошибками. Требования для класса, определенные свойством collectionClass:
  • Он должен иметь конструктор без аргументов.
  • Он должен иметь метод addItem(), который может принимать объекты типа, указанного в свойстве collectionItem. Возвращаемое значение не важно.
  • Этот класс не для осуществления определенного интерфейса или расширения определенного базового класса.
  • Указание класса в метаданных не обязательно вынуждает включать его в компилированный SWF файл. Этот класс должен где-либо еще ссылаться на Ваш код, для гарантированного включения.


collectionItem

Свойство collectionItem определяет класс, созданный для каждого элемента в сборке. Это обязательно. Список параметров inspectable для данного класса определяет список свойств, указанных в диалоговом окне Values для каждого элемента. Поэтому нельзя просто указать Object, необходимо указать пользовательский класс с метаданными Inspectable. Требования для класса, определенного свойством collectionItem:
  • Он должен иметь конструктор без аргументов.
  • Он должен иметь по крайней мере один параметр inspectable.
  • Параметры inspectable должны иметь тип String, Number или Boolean. Более сложные типы, как Color и Array, не поддерживаются.
  • Класс не должен осуществлять определенный интерфейс или расширять любой специфический базовый класс.
  • Указание класса в метаданных не обязательно вынуждает включать его в компилированный SWF файл. Этот класс должен где-либо еще ссылаться на Ваш код, для гарантированного включения.


identifier

Свойство identifier указывает, какие из параметров inspectable в классе collectionItem используются в качестве подписи свертываемой строки для каждого элемента в диалоговом окне Values. Этот параметр будет автоматически заполнен "<property name><increasing ordinal>" когда один пукт будет добавлен в диалоговом окне Values, поэтому его тип должен быть String. Например, параметр dataProvider для MenuBar определяет label как идентификатор. Как можно видеть выше, на рисунке 2, подпись значения использует складывающуюся строку. При добавлениии нескольких пунктов в диалоге Values для параметра dataProvider компонента MenuBar или List, поле label предварительно заполняется "label0", "label1"," label2", и т. д.

Свойство identifier не обязательно, и если оно не будет указано, то один из параметров inspectable класса collectionItem будет добавлен автоматически.

Пример метаданных Collection

Пример MenuBar заимствует свои метаданные Collection из списка, что делает его немного более сложным. Следующие классы – очень простой пример того, как работают метаданные Collection. (collection_metadata_example.zip)(ZIP, 10K)
Код для CollectionComponent.as:

  1. package fl.example
  2. {
  3.         import flash.display.Sprite;
  4.        
  5.         public class CollectionComponent extends Sprite
  6.         {
  7.                 private var _dataSource:Object;
  8.                
  9.                 // Эти несколько строк необходимы для компиляции
  10.                 // ItemExample, поскольку в метаданных Collection
  11.                 // не обязательно это делается.
  12.                 private var _forceItemExample:ItemExample;
  13.                
  14.                 public function CollectionComponent()
  15.                 {
  16.                         super();
  17.                 }
  18.                
  19.                 [Collection(collectionClass =
  20.                         "fl.example.CollectionExample",
  21.                         collectionItem = "fl.example.ItemExample",
  22.                         identifier="id")]
  23.                 public function get dataSource():Object
  24.                 {
  25.                         return _dataSource;
  26.                 }
  27.                 public function set dataSource(o:Object):void
  28.                 {
  29.                         _dataSource = o;
  30.                         // Эти несколько строк необходимы для компиляции
  31.                         // CollectionExample, поскольку в метаданных
  32.                         // Collection не обязательно это делается.
  33.                         var c:CollectionExample =
  34.                                 _dataSource as CollectionExample;
  35.                         if (c != null)
  36.                         {
  37.                                 for (var i:int = 0; i < c.length; i++)
  38.                                 {
  39.                                         trace(c.getItemAt(i));
  40.                                 }
  41.                         }
  42.                 }
  43.         }
  44. }
Код для CollectionExample.as:

  1. package fl.example
  2. {
  3.         public class CollectionExample
  4.         {
  5.                 private var _items:Array;
  6.                
  7.                 public function CollectionExample()
  8.                 {
  9.                         super();
  10.                         _items = new Array();
  11.                 }
  12.                
  13.                 public function addItem(item:Object):Boolean
  14.                 {
  15.                         if (item != null )
  16.                         {
  17.                                 _items.push(item);
  18.                                 return true;
  19.                         }
  20.                         return false
  21.                 }
  22.                
  23.                 public function clear():void
  24.                 {
  25.                         _items = new Array();
  26.                 }
  27.                
  28.                 public function getItemAt(index:Number):Object
  29.                 {
  30.                         return(_items[index]);
  31.                 }
  32.                
  33.                 public function get length():int
  34.                 {
  35.                         return _items.length;
  36.                 }
  37.         }
  38. }
Код для ItemExample.as:

  1. package fl.example
  2. {
  3.         public class ItemExample
  4.         {
  5.                 private var _id:String;
  6.                 private var _stringData:String;
  7.                 private var _numberData:Number;
  8.                
  9.                 [Inspectable(defaultValue="")]
  10.                 public function get id():String
  11.                 {
  12.                         return _id;
  13.                 }
  14.                 public function set id(s:String):void
  15.                 {
  16.                         _id = s;
  17.                 }
  18.                
  19.                 [Inspectable(defaultValue="string")]
  20.                 public function get stringData():String
  21.                 {
  22.                         return _stringData;
  23.                 }
  24.                 public function set stringData(s:String):void
  25.                 {
  26.                         _stringData = s;
  27.                 }
  28.                
  29.                 [Inspectable(defaultValue="0.0")]
  30.                 public function get numberData():Number
  31.                 {
  32.                         return _numberData;
  33.                 }
  34.                 public function set numberData(s:Number):void
  35.                 {
  36.                         _numberData = s;
  37.                 }
  38.                
  39.                 public function ItemExample()
  40.                 {
  41.                         _id = "";
  42.                         _stringData = "string";
  43.                         _numberData = 0.0;
  44.                 }
  45.                
  46.                 public function toString():String
  47.                 {
  48.                         return ("[id: \""
  49.                                 + _id + "\", stringData: \""
  50.                                 +_stringData + "\", numberData: "
  51.                                 + _numberData + "]");
  52.                 }
  53.         }
  54. }

Заметьте, для простоты примера не расширяетсякласс UIComponent. Поскольку это не настоящий компонент, и он не делает ничего существенного, мы избежали неприятностей. Смотрите "Без расширения UIComponent" (Not extending UIComponent) для получения дополнительной информации.

Метаданные IconFile

Метаданные IconFile являются классом метаданных уровня. Они определяют файл PNG с полным изображением для панели компонентов и панели библиотеки для использования при отображении компонента. Метаданные IconFile работают только с компонентами на основе SWC. Они берут единственное безымянное значение, указывающее путь к файлу PNG, относительно файла FLA, из которого экспортирован SWC. Выглядит так:

  1. package
  2. {
  3.         ...
  4.         [IconFile("FLVPlayback.png")]
  5.         ...
  6.         public class Foo extends ...

Поскольку все компоненты UI и компонент MenuBar основаны на FLA, ни один из них не использует метаданные IconFile. Для определения полного изображения для компонента FLA используйте диалоговое окно Component Definition (далее).

Другие метаданные

В исходном коде компонента UI, Вы найдете метаданные Style и Event на уровне класса во многих файлах ActionScript. Эти метаданные фактически не используются Flash. Они используются только в процессе создания автоматизированной документации внутри Adobe, наряду со специальными комментариями между /** и */. Объяснение этих метаданных выходит за рамки этой серии статей.


Flash CS3 не поддерживает другие метаданные. Любые неподдерживаемые метаданные игнорируются без появления ошибок.

Использование диалогового окна Component Definition

Имеем метаданные Collection рядом с определением свойства dataProvider, но нам необходимо ввести некоторую информацию в диалоговое окно Component Definition прежде, чем параметр dataProvider отобразится в инспекторе компонента и инспекторе свойств.

Перед открытием диалога Component Definition, необходимо добавить в classpath исходные файлы ActionScript для компонентов UI. Это необходимо для того, чтобы диалог Component Definition смог найти метаданные. Путь к этому источнику:
$(AppConfig)/Component Source/ActionScript 3.0/User Interface

Изменим classpath в окне ActionScript 3.0 Settings (см. рисунок 3).
Рисунок 3. Добавление пути к исходным файлам классов UI




























Теперь можно настроить компонент в окне Component Definition. Откроем это окно правым кликом на символе MenuBar и выберем Component Definition в контекстном меню. Далее сделаем следующее:
  1. Введем fl.example.MenuBar в поле Class.
  2. Кликнем по значку с выпадающим меню под полем Description (слева от него), и выберем Menu Icon.
  3. Поставим флажок на Display in Components panel.
  4. Поставим флажок на Require minimum player version, (Flash Player 9).
  5. Поставим флажок на Require minimum ActionScript version, (ActionScript 3.0).

После нажатия OK потребуется некоторое время, пока окно не исчезло. Это потому, что Flash обрабатывал исходный файл, fl.example.MenuBar, а также многие из файлов инфраструктуры UI, отыскивая метаданные. Когда снова откроем диалог, то увидим список из трёх параметров: dataProvider, enabled и visible (см. рисунок 4). Диалоговое окно получило сведения о dataProvider из MenuBar.as и другие свойства, полученные из метаданных ActionScript в UIComponent.as. При клике Cancel на этот раз окно закрылось сразу, поскольку ничего не изменилось.
Рисунок 4. Список из трех параметров после нового открытия Component Definition

































При выборе экземпляра MenuBar на сцене, теперь можно увидеть параметры, перечисленные в инспекторе компонентов с устанвленными значениями по умолчанию (см. рисунок 5).
Рисунок 5. Список параметров со значениями по умолчанию в инспекторе компонента






















Изображение значка

Для примера компонента MenuBar не создан собственный значек, но это несложно сделать. Просто в выпадающем списке для значка выберите Custom внизу списка и укажите файл PNG. Файл PNG должен быть 18×18 пикселей. Когда пользовательский значек выбран, иконка выпадающего меню останется прежней, но изменится изображение значка в панели Library.

Мой компонент не обновился!

Иногда, делая изменения в Component Definition, обновляете ли Вы значек компонента, параметры или Live Preview, можно не увидеть изменения, отраженные в файле FLA. Не рвите волосы, попробуйте скопировать компонент в другой файл FLA. Как вариант, можно сохранить файл FLA и перезапустить его, или еще лучше сохранить и сжать Ваш FLA файл.

Развертывание компонента на панели компонентов

На данный момент в проекте нашего FLA файла можно развернуть панель компонентов. Очевидно, предстоит сделать еще очень многое, у нас даже еще нет Live Preview. Но, изменения, сделанные в Component Definition — а именно, проверка отображения в панели компонентов — удовлетворяют минимальным требованиям для развертывания.

Для отображения MenuBar в панели компонентов, я сохраним MenuBar.fla, скопируем его в каталог компонентов (Components directory) и выберем Reload в контекстном меню панели компонентов. Есть два места для панели компонентов: первое можно найти в папке приложения Adobe Flash CS3 и одно расположено в папке конфигурации пользователя. Компонент, устанавливаемый в папку конфигурации пользователя, доступен только для этого пользователя. Если Вы распространяете компонент как файл MXP, который создан при помощи Flash Extension Manager, то Вам необходимо будет установить компонент в папку пользовательской конфигурации. Таблица 2 показывает, где можно найти эти папки при стандартной установке программы для Windows и Macintosh.

Таблица 2. Расположение директории с компонентами

Windows
Macintosh
C:\Program Files\Adobe\Adobe
Flash CS3\language\
Configuration\Components
/Applications/Adobe Flash CS3/
Configuration/Components
C:\Documents and Settings\
username\Local Settings\
Application Data\Adobe\
Flash CS3\language\
Configuration\Components
/Users/username/Library/Application
Support/Adobe/Flash CS3/
language/Configuration/Components

С компонентами на основе FLA, имя файла FLA определяет название папки, в которой появится компонент. Поэтому найдем компонент MenuBar в папке MenuBar (см. рисунок 6). Рядом также расположились компоненты List и TileList, что имеет смысл, поскольку они находятся в нашей библиотеке и они также имеют параметр проверки отображения на панели компонентов. (Позже Вы увидите, как это сделано.)
Рисунок 6. Компоненты List, MenuBar и TileList перечислены в панели компонентов в папке MenuBar























Если создать новый файл ActionScript 2.0 FLA, компонент MenuBar не появится в нем, поскольку указана требуемая версия ActionScript 3.0.

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

Развертывание компонентов SWC

Развертывание компонента SWC почти такое же, как и FLA. Компонент SWC помещается в одну из двух папок компонентов. Если его расположить на верхнем уровне папки компонентов, то он появится в панели компонентов в папке Standard Components. Чтобы изменить имя папки, просто создайте папку с нужным именем в папке Components и поместите в неё файл SWC. Вы не можете вложить файлы SWC глубже одной папки в пределах папки Components.

Компоненты SWC может оказаться в той же папке панели компонентов, что и компонент FLA с именем FLA-компонента. Например, в папке Video есть компонент FLA, созданный из Video.fla, и есть также компонент SWC, размещенный в папке Video из SWC.

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


Вам не надо предпринимать шаги для разграничения SWC файлов ActionScript 2.0 и ActionScript 3.0. Оба типа могут быть размещены рядом в папке Components и Flash определит тип SWC для соответствующей обработки.


В отличие от FLA файлов в каталоге Components, файл SWC в директории Components может быть переписан после загрузки на панели компонентов. Вы можете повторно экспортировать SWC файл и выбрать Reload в контектстном меню панели компонентов для обновления измененного компонента без перезагрузки Flash.

Осуществление Live Preview

Компонент MenuBar пока еще предстален черным прямоугольником на сцене, так что настало время добавить Live Preview. Благодаря Live Preview, мой компонент сможет прорисовать себя исходя из размеров и параметров из инспектора компонентов. Пользовательский файл SWF управляет живым предпросмотром. К счастью, в большинстве случаев, этот обычный файл SWF не потребует какой-либо код, отличный от кода компонента.

Для включения Live Preview для MenuBar надо сделать следующее:
  1. Правый клик на MenuBar в панели Library и выбрать Export SWC File в контекстном меню.
  2. Просмотреть каталог, содержащий мой MenuBar.fla и куда был сохранен MenuBar.swc.
  3. За пределами Flash открыть браузер файлов и просмотреть каталог с нашими FLA и SWC файлами.
  4. Переименовать MenuBar.swc в MenuBar.zip.
  5. Извлечь все файлы из MenuBar.zip.
  6. Переместить library.swf (это один из файлов в MenuBar.zip) в каталог с MenuBar.fla и MenuBar.zip.
  7. Переименовать library.swf в MenuBarPreview.swf. Это очень важно – не использовать имя library.swf при переименовании Вашего файла Live Preview SWF.
  8. Вернуться во Flash, правый клик по MenuBar в панели Library и выбрать Component Definition в появившемся контекстном меню.
  9. В диалоге Component Definition нажать кнопку Set рядом с Live Preview.
  10. В диалоге Live Preview выбрать опцию "Live Preview with .swf file embedded in .fla file".
  11. Ввести MenuBarPreview.swf в текстовое поле Live Preview .swf file.
  12. Кликнуть OK в диалоге Live Preview и в диалоге Component Definition (см. рисунок 7).
Рисунок 7. Ввод MenuBarPreview.swf в текстовое поле диалога Live Preview

































После этих шагов получится работающий Live Preview. Он теперь не похож, как без набора установленных параметров dataProvider — он выглядит как серый прямоугольник вместо черной рамки. Как только мы добавили некоторые пункты меню в dataProvider, Live Preview зразу обновился. Можно изменить dataProvider и MenuBar немедленно обновится. Можно изменить размеры компонента и меню сразу также изменится. Наконец, можно задать параметр visible как true и Live Preview станет невидимым.

Live Preview для SWC-компонентов

По умолчанию, Live Preview для SWC-компонента поступает из SWF, который опубликован Flash и сохранен в SWC. Шаги, которые изложены для создания Live Preview для каждого компонента UI, используют факт генерации файла SWC и извлечения SWF файла, который будет использован для Live Preview, если компонент был развернут как SWC-компонент.

Если Вы хотите использовать пользовательский Live Preview SWF для Вашего SWC-компонента, просто следуйте тем же шагам, что и для FLA-компонента.

Обновление Live Preview

На предыдущих шагах описывалось, как настроить Live Preview в первый раз, но после первой установки Вам необходимо будет проделать почти теже шаги при существенных изменениях в коде. Выполните те же действия, но при открытии диалога Live Preview для окна Component Definition, кнопка Update будет теперь включена. Нажмите кнопку Update и затем нажмите OK в диалоге Live Preview и Component Definition (см. рисунок 8).
Рисунок 8. После создания Live Preview, при последующем открытии диалога Live Preview кнопка Update будет активна













Следуя шагам для обновления Live Preview, помните, что необходимо взять library.swf из файла SWC и переименовать его. Не используйте SWF из SWC, у которого уже есть правильное имя, например MenuBarPreview.swf. Этот файл будет Live Preview SWF, который Вы уже определили. Также помните, что нельзя использовать имя "library.swf" для Live Preview SWF. Использование имени "Preview.swf" является хорошей практикой при создании Live Preview компонентов UI.

isLivePreview

UIComponent объявляет защищенную (protected) булеву переменную isLivePreview. В реализации configUI(),UIComponent инициализирует isLivePreview как true если SWF используется для Live Preview или false если SWF не используется. Во многих случаях Вам не понадобится эта информация, например компонент MenuBar не нуждается в ней, но иногда она может оказаться полезной. Например, компонент FLVPlayback использует переменную, чтобы знать использовать ли черный прямоугольник со значком FLV над ним или нет, и следует ли использовать параметр preview, влияющий только на Live Preview. В большинстве случаев Вам не нужен isLivePreview и предпросмотр Вашего компонента должен работать.

Создание пользовательского Live Preview SWF


Есть некоторые компоненты, у которых Live Preview во время работы значительно меняется. Одним из примеров является компонент FLVPlaybackCaptioning, который фактически не имеет никакого визуального представления во время выполнения, но имеет один Live Preview, чтобы пользователь смог увидеть его на сцене. Другим примером является компонент UILoader, который во время выполнения только выглядит независимо от загружаемого содержания, но нуждается в некотором визуальном представлении на сцене для позиционирования. В обоих примерах используется отдельный файл Live Preview SWF, а не isLivePreview с введением дополнительной логики в код компонента.

Есть несколько основных требований при создании пользовательского Live Preview относительно файла FLA:
  • Вы должны установить класс документа fl.livepreview.LivePreviewParent в инспеторе Property.
  • Вы должны установить размер сцены в соответствии с размером по умолчанию для компонента, т. е. теже размеры, что и у аватара в первом кадре мувиклипа компонента.
  • У Вашего файла FLA должен быть только один кадр и только один мувиклип на сцене. Мувиклип должен быть размещен в координатах (0,0) и его размеры должны соответствовать размерам сцены.
  • Вы должны связать мувиклип с классом, осуществляющим метод setSize(width:Number, height:Number):void, который вызывается при изменении размеров компонента.
  • Ваш связанный класс должен реализовать сеттеры для любых параметров инспектора компонента, которые будут использоваться в режиме предпросмотра, но это не требуется для осуществления любого из них.
Если Вы создаете пользовательский Live Preview SWF файл, шаги для подключения его к компоненту похожи на шаги для создания Live Preview SWF файла, извлеченного из SWC:
  1. Поместите файл FLA пользовательского Live Preview в ту же директорию, где расположен файл FLA Вашего компонента.
  2. Протестируйте ролик или опубликуйте пользовательский Live Preview файл FLA
  3. В файле FLA Вашего компонента правый клик по символу компонента в библиотеке и выберите Component Definition в появившемся контекстном меню.
  4. В диалоге Component Definition нажмите кнопку Set рядом с Live Preview.
  5. В диалоге Live Preview выберите опцию "Live Preview with .swf file embedded in .fla file".
  6. Введите имя файла Вашего пользовательского Live Preview SWF в поле Live Preview .swf file.
  7. Нажмите OK в окне Live Preview и в Component Definition.
При обновлении Вашего пользовательского Live Preview SWF файла, Вы должны следовать тем же шагам, но также должны будете нажать кнопку Update в окне Live Preview.

Пользовательский Live Preview SWF для UILoader

Далее пройдем через создание Live Preview SWF файла для UILoader как пример создания пользовательского Live Preview SWF. (uiloader_live_preview.zip)(ZIP, 22K)

Первым делом укажем класс документа fl.livepreview.LivePreviewParent. Classpath не надо менять, поскольку LivePreviewParent находится в пути классов по умолчанию. Далее установим размер сцены 100×100 пикселов для размеров UILoader по умолчанию. На данный момент сцена настроена.

Потом, создадим черный прямоугольник 100×100 с толщиной линии hairline и разместим его на сцене в координатах (0,0). Выделим прямоугольник и нажмем F8 для преобразования его в символ мувиклипа с именем Avatar. Пока экземпляр символа Avatar на сцене выбран, дадим ему имя avatar_mc. Используем avatar_mc в качесве заполнителя для установления размеров клипа и позже удалим его вместе с ActionScript, так же, как делает исходный код компонента.

Далее нажмем F8 чтобы вложить avatar_mc внутрь другого символа с именем UILoaderLivePreview. Экспортируем этот символ в ActionScript с тем же именем. UILoaderLivePreview будет классом, содержащим код для функциональности Live Preview.

Внутри UILoaderLivePreview создадим еще один слой и создадим динамическое текстовое поле на сцене, назвав label_txt и расположив в (0,0). В инспекторе свойств для текста выберем опцию multiline,шрифт Arial с размером 13 и установим темно-серый цвет (#333333). Затем выберем опцию embed all punctuation and basic Latin characters for the font (кнопка Embed).Будем использовать текстовое поле label_txt для отображения текста fl.controls.UILoader. Поместим динамическое текстовое поле на сцене вместо того, чтобы динамически создавать экземпляр TextField только потому что, так проще настроить шрифт, размер, цвет и включить шрифт через инспектор свойств текста, чем писать код.

Заметьте, что в отличие от символа компонента, мы не используем структуру двух кадров и поместим экземпляры кроме аватара на кадре 1. Хотя Вы модете установить пользовательский Live Preview FLA файл как компонент, и даже иметь символ, расширяющий UIComponent, обычно пользовательский Live Preview может быть очень простым. Использование инфраструктуры в данном случае может все усложнить.

Прежде чем реализовать UILoaderLivePreview сначала откроем диалог ActionScript 3.0 Settings и снимем флажок с "Automatically declare stage instances". Это не обязательно и на самом деле, это вопрос личных предпочтений. В отличие от компонента, ActionScript код пользовательского Live Preview для UILoader должен работать только в данном FLA файле. Отметьте, что когда Вы объявляете в коде экземпляры, размещенные на сцене, необходимо добавить им модификатор public.

Ниже код для UILoaderLivePreview:

  1. package
  2. {
  3.         import flash.display.*;
  4.         import flash.text.*;
  5.        
  6.         public dynamic class UILoaderLivePreview extends Sprite
  7.         {
  8.                 private var _width:Number;
  9.                 private var _height:Number;
  10.                
  11.                 public var avatar_mc:DisplayObject;
  12.                 public var label_txt:TextField;
  13.                
  14.                 public function UILoaderLivePreview()
  15.                 {
  16.                         _width = super.width;
  17.                         _height = super.height;
  18.                         removeChild(avatar_mc);
  19.                         draw();
  20.                 }
  21.                
  22.                 public function setSize(w:Number, h:Number):void
  23.                 {
  24.                         _width = w;
  25.                         _height = h;
  26.                         draw();
  27.                 }
  28.                
  29.                 public override function get width():Number
  30.                 {
  31.                         return _width;
  32.                 }
  33.                 public override function set width(w:Number):void
  34.                 {
  35.                         setSize(w, height);
  36.                 }
  37.                
  38.                 public override function get height():Number
  39.                 {
  40.                         return _height;
  41.                 }
  42.                 public override function set height(h:Number):void
  43.                 {
  44.                         setSize(width, h);
  45.                 }
  46.                
  47.                 public function draw():void
  48.                 {
  49.                         graphics.clear();
  50.                         graphics.beginFill(0xEEEEEE, .8);
  51.                         graphics.drawRect(0, 0, _width, _height);
  52.                         graphics.endFill();
  53.                         graphics.lineStyle(1, 0x333333);
  54.                         graphics.drawRect(0, 0, _width, _height);
  55.                        
  56.                         // попробуем полное имя
  57.                         label_txt.width = _width - 6;
  58.                         label_txt.text = "fl.containers.UILoader";
  59.                         if (label_txt.numLines > 1)
  60.                         {
  61.                                 label_txt.text = "UILoader";
  62.                                 if (label_txt.numLines > 1)
  63.                                 {
  64.                                         label_txt.text = "";
  65.                                 }
  66.                         }
  67.                         label_txt.x =
  68.                                 Math.max(0,
  69.                                 (_width -label_txt.width) >>1);
  70.                         label_txt.y =
  71.                                 Math.max(0,
  72.                                 (_height - label_txt.height) >>1);
  73.                 }
  74.         }
  75. }

UILoaderLivePreview управляет своими width и height аналогично тому, как это делают компоненты, поэтому он захватывает начальные значения width и height. Затем он удаляет экземпляр avatar_mc после того, как width и height будут известны, поскольку прямоугольник нужен был лишь для заполнения клипа нужной шириной и высотой. В конце, конструктор вызывает метод draw(). Начиная с UILoaderLivePreview подкласса Sprite, нет никакой модели аннулирования и, хотя мы немного подражали ей при использовании метода draw(), необходимо вызвать этот метод напрямую.

Осуществим setSize(), который просто изменяет width и height и вызывает draw(). Также переопределим свойства width и height, которые фактически не нужны но это было несложно.

Метод draw() использует flash.display.Graphics для отрисовки прямоугольника. Затем он использует простую логику для определения, есть ли место в label_txt для fl.containers.UILoader, только UILoader или места для любого текста не хватает и центрирует результирующий текст в поле.

Пока мы точно настраивали код draw(), не публиковали SWF файл и обновляли UILoader's Live Preview SWF каждый раз, когда делали изменения. Вместо этого добавим второй слой на главной временной шкале, и поместим код на этом слое, который вызывает onResize() с различными ширинами и высотами. Live Preview вызывает метод onResize() когда размеры экземпляра изменяются, таким образом непосредственный вызов этого метода имитирует то, что будет происходить в Live Preview. Закомментируем весь этот код перед развертыванием UILoaderPreview.swf.

Если нам надо чтобы UILoader Live Preview обрабатывал параметры компонента, то просто осуществим эти параметры. Для проверки параметров тестового ролика дадим имя экземпляру UILoaderLivePreview на сцене и установим для него свойства непосредственно кодом, аналогично непосредственному вызову onResize().

Использование отдельного тестового FLA

Для улучшения диалога Component Definition, добавим источник ActionScript UI в пути класса в MenuBar.fla. К сожалению, побочный эффект изменения classpath заключается в увеличении времени компиляции из MenuBar.fla при тестировании фильма. Как решение, можно начать использовать отдельный тестовый FLA файл для тестирования компонента при выполнении изменений.

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

Еще одно преимущество использования отдельного тестового FLA файла заключается в отсутствии затрат времени на фиктивный Live Preview или других ошибок, связанных с изменением в Component Definition без немедленного отображения.

Всякий раз, когда при изменении любых визуальных активов во Flash обновляется диалоговое окно Symbol Properties, диалог Linkage или Component Definition, всегда делаем это в MenuBar.fla. MenuBar.fla это транспорт для развертывания компонента, поэтому все изменения должны быть выполнены в нем. После внесения изменений, перетащим компонент MenuBar из библиотеки MenuBar.fla в библиотеку test.fla и выбирем Replace existing component (замена существующего компонента) в диалоге Resolve Component Conflict (см. рисунок 9).
Рисунок 9. Выбор опции для замены существующего компонента в диалоге Resolve Component Conflict












Если Вы скачаете исходники компонента (prototype_to_component.zip(ZIP, 917K)) и просмотрите test.fla файл, Вы увидите, что на сцене расположены некоторые кнопки и написаны несколько простых скриптов в кадре, которые делают простые изменения компонента после его создания. Неправильная обработка и последующее обновление компонента является распространенной ошибкой в написании кода компонента, поэтому выполнение простых тестов, как этот, помогает отловить ошибки на раннем этапе.

Компиляция из исходника или из ComponentShim

ComponentShim это скомпилированный клип, который содержит весь предварительно собранный байт-код для каждого определения инфраструктуры компонента UI. (все классы, кроме автоматически сгенерированных классов для символов обложки.) Пользователь не нуждается в файлах ActionScript для этих классов и других определений в его пути классов (classpath) из-за ComponentShim.

Однако, если пользователь имеет файлы ActionScript UI в пути класса, то компилятор ActionScript 3.0 предпочтет их определения вместо определений в ComponentShim. Недостаток – увеличенное время компиляции. Преимущество – Вы можете отлаживать исходный файл и вносить в него изменения.

Вы увидите такой же результат с любым скомпилированным клипом. Например, если компонент FLVPlayback находится в Вашем FLA файле, Вы добавляете следующий путь класса (classpath):
$(AppConfig)/Component Source/ActionScript 3.0/FLVPlayback

Вы заметите меньшее время компиляции. Вы также можете установить точки останова в исходнике FLVPlayback и провести построчную отладку.

Другой большой рабочий процесс при тестировании небольших изменений в коде скомпилированного клипа это копировать только исходники ActionScript, которые Вы меняете в classpath и затем производить изменения в этих исходниках. Таким образом, Вам не надо перекомпилировать все файлы, которые не были изменены. Например, ели Вы исследуете ошибочную работу в компоненте DataGrid, Вы можете копировать DataGrid.as рядом с Вашим файлом FLA, как всегда вложенных в труктуру папок, соответствующей структуре пакета, например, fl/controls. Затем Вы можете производить изменения в DataGrid.as и Flash скомпилирует DataGrid из исходника, в то время, как все определения инфраструктуры компонента UI могут быть получены из ComponentShim. Это позволит сэкономить много времени при постоянном внесении небольших изменений и последующим их тестированием.

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

  1. Спасибо за перевод, будем ждать продолжения, особенно 6-й части Invalidation model.

    ОтветитьУдалить
  2. При использовании встроенный компонент, когда перетаскиваешь компоненту на сцену, а библиотеке создается папка Component Assets, с ресурами этой компоненты.
    Если я перетаскиваю свою компоненту, то такой папки не создается, а она нужна, чтобы могли править ее дизайн без исходников. Как это сделать?

    ОтветитьУдалить