27 октября 2008 в 13:43

Первые, но нелегкие шаги во Flex

Не так давно я начал свое знакомство с Flex Builder 3. Поскольку с программированием я дружу давно и по-всякому, проблем с задачами типа «Hello, world», сортировка массива и «а как сделать, чтобы изображение по кнопке ползало» не было. Однако я встретился с задачами, которые при внешней своей простоте простых решений в среде Flex не имели.


Для удобства изложения я выделил четыре самые интересные из них:
  1. Добавление множества ресурсов в проект;
  2. Многоязычность приложения;
  3. Модальное окно;
  4. Flex и CSS.

Задачи перечислены в порядке убывания сложности. Я буду рад, если кто-то решил эти же задачи проще (или иначе, чем я) и опишет эти решения в комментариях.

1. Добавление множества ресурсов в проект


Я очень быстро выяснил, что включить (embed) файлы любого типа в проект Flex легко:

[Embed(source="index.xml", mimeType="application/octet-stream")]
private static var index_xml : Class;

[Embed(source="picture.png")]
private static var picture_png : Class;

Таким образом, в момент выполнения приложения мы можем получить к ним доступ, просто создавая необходимый объект, а не подгружая файл из сети или с локального диска.

Однако практически сразу же я напоролся на ошибку:
1067: Неявное приведение значения типа int к несоответствующему типу Class.

Такую ошибку давал следующий код:

public class res {
[Embed(source="test.png")]
public static var test: Class;
public static var res_test: int;
}

Как оказалось, для embedded ресурсов Flex генерирует промежуточные AS классы. Для примера выше, был сгенерирован класс:

public class res_test extends mx.core.BitmapAsset {
public function res_test() {
super();
}
}

Выяснить это мне помогла полезная директива компилятора: keep-generated-actionscript. Если эта директива выставлена в true, компилятор для всех промежуточных генерируемых классов создает файлы в папке src/generated/.

Разобравшись с этой проблемой я задумался над следующим: как мне включить много embedded файлов? Не один-два, которые можно вписать руками, а десять-двадцать и больше. Ни help, ни книги, ни форумы не дали ответа. Честно говоря, я так и не понял: то ли никто такого не делал, то ли все делали, но молчат.

Поскольку строки типа [Embed(source=«folder\»)] Flex упорно отрицал, я решил пойти на хитрость: заархивировать папку с ресурсами. Таким образом, я получаю файл, который могу включить в проект, как и любой другой. Дело оставалось за библиотекой классов для работы с архивами. Найти такие библиотеки труда не составило. Например, AS3 Zip Library или FZip.

Теперь я мог в момент выполнения приложения использовать любой файл из архива. А в архив я мог поместить сколько угодно файлов без каких-либо трудозатрат. Вроде все как надо.

Но нет. Файлы из архива возвращаются в виде ByteArray. Если это текстовые файлы или бинарные данные – то никаких проблем. А вот если это изображение или, не дай бог, swf, то начинаются танцы с бубном.

Просто так преобразовать ByteArray в BitmapData или BitmapAsset оказалось невозможно. Не оказалось даже конверторов для различных типов данных: png, jpeg, swf и т.п. Во Flex есть PNGEncoder, JPEGEncoder и так далее, но почему-то нет PNGDecoder, JPEGDecoder.

Еще немного полистав help, я нашел класс Loader. Этот класс обладает методом loadBytes(), который получает в качестве параметра ByteArray, автоматически распознает содержимое и преобразует его к нужному классу Flex.

Смирившись с необходимостью подписываться на событие завершения загрузки и как-то выдирать мои данные из этого компонента, я ожидал, что мне сейчас «сделают красиво». Не тут-то было.

Тестируя это вариант из-под среды Flex, я не замечал никаких проблем. Однако когда я сделал релиз и запустил его, я получил ошибку. Ее суть сводилась к следующему: у меня нет прав для доступа к файлу «ФайлМоегоПриложения.swf.<<1>>.byteArray()». Иными словами, у меня нет прав для доступа к моему массиву. Вот это повергло меня в шок.

Да, я понимаю, что если при компиляции я не выставлял флаг use-network в false, то мое приложение не сможет использовать локальные файлы, если оно не принадлежит доверенной зоне, или не находится в local-trusted sandbox, или не подпадает еще под какое-нибудь правило безопасности Flash-плеера. Но я не понимаю, почему мой массив, который я сам только что создал и инициализировал, оказался каким-то локальным ресурсом, к которому я еще и не могу получить доступ через Loader!

Придя в себя, я решил бороться дальше. Следующей идеей было создать какую-то надстройку над Flex, которая при компиляции преобразовывала строки вида [Embed(source=«folder\»)] в класс AS3, где были бы явно перечислены с флагом embedded все файлы из этой папки. Плагин писать было лень, а каких-либо макросов во Flex я не нашел. Поэтому я обратился к другу, который пишет на Java в среде Eclipse. Я рассудил, что, раз Flex базируется на Eclipse, то, возможно, решения для Eclipse подойдут и для Flex.

После некоторых раздумий друг посоветовал мне Apache Ant. Для разработчиков на Java он заменяет make. При этом он может творить невообразимые вещи с файлами, папками, запускать приложения, редактировать текстовые файлы и много чего еще. Поковыряв немного эту утилиту, я не понял, как ее заточить под Flex, да и время уже поджимало. Поэтому я отказался от идеи использовать Apache Ant и у меня остался только один путь (который я смог придумать) добиться желаемого.

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

За двадцать минут на С++ я написал программку, которая генерирует этот AS3 файл. Единственная проблема, которая осталась – это не забывать запускать эту программу, когда меняются ресурсы в папке :). Код этой программы я здесь не привожу, потому как он примитивен.

Я все еще надеюсь, что кто-нибудь укажет мне более грамотное решение задачи включения множества ресурсов в проект.

2. Многоязычность приложения


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

Был создан класс:

public class LanguageProvider {
private var fobj_languageData: Array = new Array();

public function Clear(): void {
fobj_languageData = new Array();
}

public function AddLanguageData(fileName : String): void {
var id : String = "";
var parent : XML = null;

for each (var item: XML in ResourcesManager.FileAsXML(fileName)..*.(hasOwnProperty("@value"))) {
id = item.name();
parent = item.parent();
while (parent != null) {
id = parent.name() + "." + id;
parent = parent.parent();
}
fobj_languageData[id] = item.@value;
}
}

public function GetStringFor(item : String): String {
var result: String = fobj_languageData[item];
if (result != null) return result; else return "<-!->";
}
}

Также был создан файл локализации:
<menu value=«Меню»>
<new value=«Cоздать новый»/>
<settings value=«Настройки»>
<sound value=«Звук»/>
<music value=«Музыка»/>
</settings>
</menu>

Чем это лучше стандартного метода:
  • Используются xml файлы, а не ini. Это позволяет структурировать языковые данные;
  • Обращение к строке локализации выглядит примерно так: menu.settings.sound. Сразу понятно, что идет речь о звуке в меню настроек;
  • Этот метод позволяет более детально управлять процессом локализации – задавать строку по умолчанию для неопределенных строк локализации, генерировать или не генерировать исключения, оптимизировать процесс заполнения массива локализации и т.п.

Чем это хуже стандартного метода:
  • Вам необходимо все делать вручную: включать файлы локализации, загружать их, присваивать строки компонентам и т.п.

Я не претендую на истину в последней инстанции, но лично для меня мой подход удобнее. Может еще кому-то пригодится.

3. Модальное окно


Появилась необходимость создать свое модальное «окно». Наподобие «окна» Alert. Я пишу слово «окно» в кавычках потому, что на самом деле это просто панели, а не окна как таковые. Конечно же, я залез в исходники класса Alert и выцепил все необходимое оттуда.

<?xml version="1.0" encoding="utf-8"?>
<mx:Panel xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" width="400" height="300">
<mx:Script>
<![CDATA[
import mx.managers.PopUpManager;
import mx.events.CloseEvent;
import mx.core.Application;

// private
private var fbol_init: Boolean = false;
private var fobj_closeHandler: Function = null;

// protected
override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void {
super.updateDisplayList(unscaledWidth, unscaledHeight);

if (!fbol_init) {
var x:Number;
var y:Number;

if (parent == systemManager) {
x = (screen.width - width) / 2;
y = (screen.height - height) / 2;
}
else if (parent != null) {
x = (parent.width - width) / 2;
y = (parent.height - height) / 2;
}
else {
x = (Application.application.width - width) / 2;
y = (Application.application.height - height) / 2;
}

// Set my position, because my parent won't do it for me.
move(Math.round(x), Math.round(y));
fbol_init = true;
}
}

// public
public virtual function Show(closeHandler:Function = null): void {
fobj_closeHandler = closeHandler;
if (closeHandler != null) this.addEventListener(CloseEvent.CLOSE, closeHandler);
PopUpManager.addPopUp(this, Sprite(Application.application), true);

}

public virtual function Close(): void {
PopUpManager.removePopUp(this);
fbol_shown = false;
dispatchEvent(new CloseEvent(CloseEvent.CLOSE));
if (fobj_closeHandler != null) removeEventListener(CloseEvent.CLOSE, fobj_closeHandler);
}

]]>
</mx:Script>
</mx:Panel>


Все очень просто, но надеюсь, кому-нибудь пригодится :).

4. Flex и CSS


Ни для кого не секрет, что Flex и CSS дружат. И дружат очень сильно. Можно задавать CSS для всего приложения, и определять в нем, как будут выглядеть кнопки, панели и т.п.

С другой стороны, есть компонент TextArea. У этого компонента есть свойства htmlText и styleSheet. Таким образом, в этот компонент можно помещать HTML-текст, и он будет отображаться с использованием заданного в styleSheet стиля.

А теперь вопрос: как мне для всех TextArea в приложении задать CSS, с которым должен отображаться htmlText внутри них?

Если мы задаем CSS для всего приложения и в нем прописываем, как будет выглядеть TextArea, то текст в этом компоненте только так и будет выглядеть. Если мы описываем какие-то классы, которые используются в HTML-тексте в TextArea, то эти стили никак не применяются. Несколько усложняет все это тот момент, что свойство styleSheet у TextArea доступно только из AS3 и не имеет MXML аналога.

Не найдя стандартного решения, я придумал свое — создал наследника от TextArea:

<?xml version="1.0" encoding="utf-8"?>
<mx:TextArea xmlns:mx="http://www.adobe.com/2006/mxml"
width="100"
height="100"
creationComplete="OnCreationComplete()"
borderThickness="0"
editable="false"
horizontalScrollPolicy="off"
verticalScrollPolicy="off">

<mx:Script>
<![CDATA[
// static
private static var fobj_styleSheet : StyleSheet = null;

public static function SetStyleSheet(ss : StyleSheet):void {
fobj_styleSheet = ss;
}

// private
private function OnCreationComplete(): void {
if (fobj_styleSheet != null) this.styleSheet = fobj_styleSheet;
}
]]>
</mx:Script>
</mx:TextArea>

Как видно из кода, я определил статическое поле и метод у своего наследника. Теперь в приложениях я использую не TextArea, а своего наследника. Где-то в коде приложения я один раз загружаю объект CSS и передаю его в статическую функцию SetStyleSheet. Получается, при создании все объекты этого класса теперь добросовестно прописывают сами себе CSS.

Надеюсь, мой опыт будет кому-то полезен.

Заранее благодарю за замечания и дополнения :).
Маренков Алексей @amarenkov
карма
30,0
рейтинг 0,0
Самое читаемое

Комментарии (24)

  • 0
    Да, я тоже недавно начал изучать Flex и сталкнулся с embed. Точно так же не решил проблему (точнее решил, тупо перечисляя все вствляемые файлы). Кстати, насколько я знаю, скольк раз сделаешь embed столько раз файл и присоеденится в прожект, даже если он уже есть.
    • 0
      Я аж решил попробовать :).

      Действительно — если включаешь один и тот же файл несколько раз — он увеличивает размер приложения. Впрочем, это логично — подключаешь же ты под разными классами. Flex не считает себя в праве проверять, на один ли они все файл ссылаются :).
  • 0
    3. PopUpManager.centerPopUp() не подошло?
    • 0
      Честно говоря, даже не пробовал. Я переделывал из Alert, а там было сделано именно так. Я думаю, у них были какие-то веские причины не использовать centerPopUp().
  • 0
    Сразу бросилась в глаза функция clear, которая «чистит» массив. Не знаю как с другими языками, но с флешем — чистка массива методом arr = new Array(); приводит к выделению ему дополнительной памяти. Лучше в while пока длина массива > 0 делать pop();. Хотя в этом случае это не столь важно.
    • 0
      Я на самом деле полагался на сборщик мусора :). У меня есть подозрение, что если делать для каждого элемента pop(), то может получиться менее производительно по скорости и распределению памяти.
      • +1
        Возможно, не спорю. Просто у меня была ситуация, когда чистил массивы таким же методом (их было порядком не мало), начинало притормаживать. Как раз while и спас ситуацию. А еще быстрей работает в данной ситуации do..while. Не знаю почему сборщик не справился со своей задачей. Просто говорю как было :)
  • +2
    С массовым эмбедингом ресурсов вам может помочь метапрограммирование. Напишите простейший скрипт на php, ruby или даже вижуал бейсике, который будет генерить as файл с эмбед директивами для всех файлов в директории.

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

    Модальное окно центруется именно centerPopUp(), то что в коде фреймворка по другому — это просто один из бесконечной череды примеров индусского когда в худшем смысле там.

    Вот сделайте базовый класс для окошка, с эффектом «распахивания» — это задача и полезней и познавательней. В конце концов это флэш, и надо чтобы это было видно — нужна не навязчивая динамика и анимация.
    • 0
      Ну с массовым эмбедингом — примерно так и получилось. С локализацией — согласен. Модальное окно — возможно, Вы правы — попробую переделать. Эффект «распахивания» — это чтобы оно из точки как-бы раздвигалось в конечное положение?
      • 0
        Не просто раздвигалось :) Чтобы смотрелось не скучно, окно должно стартовать с 30-40% размера, с ускорением распахнуться до 120%, а потом стать на 100%. Общая длительность около полусекунды. И в обратном порядке при закрытии. Тогда это выглядит не так уныло, как просто «тык, появилось».

        Привязываться к статик методам для создания окна (метод show у Alertа) тоже не очень практично. Т. к. его нельзя унаследовать. Обычно у вас куча окон, которые различаются только содержимым, а так как класс для каждого будет свой, и придется статический show реализовать в каждом из них
        • +2
          > Тогда это выглядит не так уныло, как просто «тык, появилось».

          «Тык, появилось» — лучший метод показа окон, imho.

          Визуальные примочки интересны только первые полчса, потом они начинют раздражать. К тому же, добавлять к открытию окна аж полсекунды — минус к юзбилити.
  • +1
    Хорошая статья, подробненько так для начала, а кому надо дальше сами раскопают.

    Еще хорошо бы о применении Flex для реальных приложений с учетом производительности — как сделать наиболее оптимальное многомодульное приложение.
    • 0
      Спасибо. Как только доделаю свое приложение — попробую потестировать на производительность и, возможно, тогда опишу, что получилось.
  • 0
    Вот спасибо за статью. Не могу сказать, что сильно помогла в чем-то, но кто знает, когда и с чем конкретно придется столкнуться. А в условиях явного дефицита русскоязычных мануалов и книг по Flex, любая информация лишней не будет. Еще раз спасибо.
  • –1
    *с довольной усмешкой* теперь однако флекс — тематика хабра :) habrahabr.ru/blogs/flex/38475/#comment_913223
  • +1
    Отличная статья. Как раз для начинающих флексеров. Автор наступил на многие грабли — молодец, это хороший опыт.

    Можно высказать пожелание? Было бы очень здорово, если бы приходящие из Си программисты постарались делать по-меньше культа вокруг отчистки памяти. Единственное, что надо делать — это не оставлять ссылок на неиспользуемые объекты и все. Всякие хаки по форсированию GC и прочее — это от лукавого.

    Еще, по-возможности, не называйте методы с заглавной буквы и используйте CamelCase для разделения слов в названиях. Только не расценивайте это как упрек — это пожелание. Вот есть например замечательная библиотека Box2D, портированная из Си во ActionScript, но с сохранением кодинг-конвенций из Си. Флэшерам с ней очень трудно работать — постоянно глаза режет.

    А вообще замечательно, что в блог «Adobe Flex» начали активно писать.
    • 0
      И тем не менее, бывает, что уберешь аккуратно все ссылки, все листнеры, у всего чего только можно dispose повызываешь и т. п., а память не освобождается. А ткнешь garbage collector, и сразу становится хорошо :) Понятно, что хак — он и есть хак, сегодня работает, завтра не работает (в лучшем случае, а в худшем все ломает), но все-таки мы живем в реальном мире, приходится искать компромисы между хорошем стилем и производительностью :)
  • –2
    Мда, почитал я тут статьбю и комменты… ну и извраты приходится вам применять, да и тормозит все это наверно. Ну ее нафик, такую кривую технологию((( Я по крайней мере флекс-приложениями теперь точно пользоваться не хочу(
    • 0
      Кстати, мое впечатление от Флекс очень положительное :)). Действительно, то что я описал — лишь некоторые сложности, с которыми я столкнулся.

      А если писать, в чем Флекс облегчил мне жизнь — одного поста не хватит.

      Я бы Вам все-таки посоветовал попробовать по-работать с ним ;).

  • 0
    тоже совсем недавно начал изучать флекс и тоже впечатления очень положительные, затыки переодически возникают, но всё решаемо. из последних например, не удалось убрать реакцию на фокус и овер во воженном рендерере, то есть есть лист у него есть рендерер, тот в свою очередь для отображения одного из елементов данных вызывает HorizontalList со своим рендерером. В итоге потратил час, а потом переписал на AS через цикл и addChild, вроде нормально, но отображается заметно медленнее. не подскажете? ))))
  • 0
    большое спасибо за метод локализации
    • 0
      Всегда пожалуйста :).
  • 0
    Обычной практикой является отделение всех ассетов от кода. В таком случае не нужно будет перекомпилировать приложение каждый раз, когда поменяется картинка, текст или стиль. То есть правильнее, хотя и гемморнее, было бы создать хмл со ссылками на картинки, и грузить их в рантайме. А хмл уже генерить из пхп на лету или с помощью jsfl во флеше.
    А, еще картинки можно было сохранить в флеш-библиотеку .swc и из нее уже таскать. Это кстати по сути тот же зип архив с вложенными ассетами.
    • 0
      Именно так сейчас и делаем — храним ресурсы в swf или swc. Но на тот момент с Flash CS я не разбирался и пытался решить проблему исключительно методами FlexBuilder.

      Также для той задачи вариант грузить что-либо в рантайме не подходил.

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.