Pull to refresh

Архитектура Adobe Flex 3 компонентов для новичка

Reading time 7 min
Views 3.2K
Знакомиться со Adobe Flex мне пришлось в почти боевых условиях. Неожиданно понадобилось писать что-то работающее, пользуясь лишь примерами из Интернета и помощью старших товарищей. Поблизости не было ни одной книги по Flex, да и времени на чтение тоже, что сильно сказалось на теоретической базе. Многое делалось на ощупь простым русским методом проб и ошибок. Чтож, попробовать пришлось порядочно. Сейчас, глядя назад, я понял, что многие грабли можно было избежать, если бы имелась под рукой простая вводная статья объясняющая что к чему. Под катом собраны 3 вопроса, из-за недопонимания которых у меня чаще всего возникали проблемы. Надеюсь, ответы на них помогут начинающим быстрее разобраться в тонкостях построения компонентов этого фреймворка.



Начнем, как водится, с рождения


Вопрос: Создаю компонент показывающий фотки голых баб некоторые внутренние элементы интерфейса. Где их лучше инициализировать, и что это за createChildren?

В конструкторе класса лучше не делать ничего ресурсоемкого. Он обычно используется только для инициализации переменных и добавлении eventListener'ов.
Для создания же вложенных элементов во флексе предусмотрена специальная функция createChildren(). Она автоматически вызывается при добавлении компонента к какому либо displayList'y. Поэтому, если нам требуется что-то внутри, необходимо переопределить createChildren и поместить туда код, создающий внутренние элементы. Здесь есть одна тонкость – обязательно проверяйте, не создана ли уже очередная пикантная картинка. Сделано это по двум причинам. Первое – это то, что createChildren может быть вызвана несколько раз, если ваш компонент будет перемещаться из одного displayLista в другой. Например itemRenderer'ы, которые часто удаляются и заново добавляются. Таким образом мы можем получить множество копий одних и тех же голых баб, а это, как известно, не интересно. Второй причиной является возможность изменения поведения в классах-наследниках. Если класс-наследник решит, что вместо Памеллы Андерсон в левом верхнем углу должна быть Анжелина Джоли, он сам создаст этот внутренний элемент в нужном ему виде, а затем вызовет super.createChildren(). Компонент-родитель увидит, что элемент уже создан и не будет утруждать себя работой.

Пример:
override protected function createChildren():void
{
    // проверяем, не существует ли уже компонент
    If (!ourNestedComponent)
    {
        // если нет, тогда создаем
        ourNestedComponent = new NestedComponent();
        addChild(ourNestedComponent);
    }
    // не забываем вызвать createChildren выше по иерархии
    super.createChildren();
}

Теперь самое интересное – жизнь и взаимодействие с пользователем


Вопрос: Хочу поменять пару свойств, для этого тоже есть какое-то волшебство?

Есть, и очень удобное. Называется оно отложенная валидация. Не пугайтесь слова «валидация», в данном контексте оно обозначает изменение состояния компонента: применение свойств, перерисовывание фона, изменение позиции внутренних элементов и тд. Как это работает? Менять свойства непосредственно в сеттерах свойств – плохая затея. Во-первых, это может негативно сказаться на визуальной плавности работы интерфейса при действии пользователя. Особенно если изменения ресурсоёмки. Во-вторых, если одно свойство зависит от другого, или свойства должны применяться в определенном порядке — придется задуматься о каком то механизме ожидания и синхронизации. Отложенная валидация – это способ избежать всего этого. Вместо немедленного применения какого-либо свойства, оно откладывается, и изменение вступает в силу непосредственно перед отрисовкой экрана.

Валидация изменений происходит в три этапа:
  1. Изменяются свойства (функция commitProperties)
  2. Рассчитываются предпочтительные размеры (функция measure)
  3. Рассчитываются реальные размеры, размеры и координаты внутренних элементов, а так же рисуется все что должно быть нарисовано. (функция updateDisplayList)

Каждая из этих функций вызывается автоматически во время события render для всех ожидающих валидации компонентов. Чтобы пометить компонент как ожидающий валидации, надо вызвать специальный функцию компонента: invalidateProperties() для commitProperties, invalidateSize() для measure и invalidateDisplayList() для updateDisplayList.
Теперь перейдем к практике и попутно расскажем подробнее про каждый из пунктов валидации.
Этап первый — функция commitProperties

Допустим, в нашей галерее «знаменитостей», помимо непосредственно фоток, имеются некоторые элементы управления. Пользователь может поменять раздел с помощью выпадающего меню и настроить степень раздетости ползунком, а так же поменять эти параметры одновременно через некие «фейворитс». Таким образом, есть два свойства: «раздел» и «степень раздетости», которые могут меняться как по отдельности, так и одновременно. Если обрабатывать изменения непосредственно в сеттерах, то возможно придется выполнить лишнюю работу. Действительно, если пользователь выбрал из фейворитс фото из раздела «Перис Хилтон» со степенью раздетости «крайне бесстыдно», придется сначала загрузить разел «Перис Хилтон» с какой-то дефолтной раздетостью, а уже потом применить «крайне бесстыдно» и грузить картинки по новой. Чтобы избежать этого, в сеттерах мы только сохраняем новое состояние, а само изменение проводим в функции commitProperties.
Пример:

public function set section(value:String):void
{
    _section = value; // сохраняем новое значение в приватную переменную
    _sectionChanged = true; // запоминаем, что именно изменилось
    invalidateProperties(); // подписываем компонент на вызов commitProperties при следующей итерации
}
public function set nudeDegree(value:String):void
{
    _nudeDegree = value;
    _nudeDegreeChanged = true;
    invalidateProperties();
}

override protected function commitProperties():void
{
    If (_sectoinChanged || _nudeDegreeChanged)
    {
        // здесь мы делаем всю работу по загрузке нужных фоток
        ….

        // снимаем флаг об изменении
        _sectionChanged = false;
        _nudeDegreeChanged = false;
    }
    
    // применяем другие свойства
    …

    // не забываем вызвать commitProperties выше по иерархии
    super.commitProperties();

}
С помощью commitProperties все наши свойства применяются единожды и в нужной нам последовательности.

Следующий этап валидации это функция measure()

Эта функция служит для расчета предпочтительных (measuredWidth и measuredHeight) и минимальных (measuredMinWidth и measuredMinHeight) размеров компонента. Родительские компоненты используют эти значения для определения размеров, в случае если они не указаны явно. Например, если мы хотим, чтобы наша галерея автоматически изменяла размеры в зависимости от количества картинок внутри, мы должны рассчитать предпочтительные размеры. Для этого добавим в функцию measure код, который будет складывать размеры всех картинок, а так же учитывать размеры элементов управления, и присваивать полученные значения в measuredWidth и measuredHeight.

Чтобы подписать компонент на функцию measure, необходимо вызвать функцию invalidateSize(). Функция measure выполняется только в случае, если размеры компонента не заданы, и расчитывает дефолтные значения, в противном случае используются заданные размеры.

Ну и наконец финальный этап валидации: функция updateDisplayList

Все, что касается изменения размеров или координат любых внутренних элементов, должно находиться именно здесь. updateDisplayList принимает два параметра: unscaledWidth и unscaledHeight. Они определяются родительским компонентом, и именно они (а не width и height) должны быть использованы при расчете координат и размеров внутренних компонентов.
Так же необходимо придерживаться правил изменения свойств внутренних компонентов:
  1. изменять их размеры с помощью функции setActualSize(width,height), а не на прямую, задавая конкретные значения
  2. изменять координаты с помощью функции move(x,y), а не на прямую, задавая конкретные значения x и y
  3. Эти правила в большинстве случаев избавят от проблем с неправильным поведением компонентов

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


Вопрос: Во флексе столько разных свойств обозначающих размеры. Зачем они все?

Размеры задаются несколькими способами.
  1. Предпочтительные размеры (measuredWidth, measuredHeight). Их определяет сам компонент. Эти размеры используются, если специально не указано что то другое, и если компоненту хватает для этого места. Если через MXML поставить например кнопку и не указать размеров, она будет именно такой величины, какая определена с помощью функции measure
  2. Минимальные размеры (measuredMinWidth и measuredMinHeight). Их также определяет сам компонент. В случае, когда размеры не заданы (те используется measuredWidth и measuredHeight), и предпочтительные размеры больше размеров родительского компонента, тот пытается его уменьшить, чтобы вместить компонент в себя. Минимальные размеры как раз задают нижний предел для этих попыток
  3. Размеры в процентах (percentageWidth и percentageHeight) – это размеры в процентах, от размеров компонента-родителя
  4. Размеры указанные явно (explicitWidth и explicitHeight) – это размеры компонента в пикселях. Так же задаются непосредственно через width и height

Размеры в процентах и размеры в пикселях не зависят от предпочтительных и минимальных и могут принимать любые значения.

Почитать по теме:
www.slideshare.net/Constantiner/flex-component-lifecycle-overview — слайды от замечательной презентации Павла Кожина, к сожалению без звукового сопровождения. Это практически все что я нашел по теме в русских интернетах.
Implementing the component[ENG] — ливдокс от самих Adobe. Подробно, но немножко сухо и на английском. Тем не менее к прочтению обязателен.
Tags:
Hubs:
+27
Comments 12
Comments Comments 12

Articles