Pull to refresh

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

Reading time 7 min
Views 5.7K
Не так давно я начал свое знакомство с 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.

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

Заранее благодарю за замечания и дополнения :).
Tags:
Hubs:
+26
Comments 24
Comments Comments 24

Articles