Рубрика: Flex
Adobe + NVIDIA = GPU + HD Video Acceleration
2 июня Adobe и NVIDIA объявили о партнерских отношениях в рамка проекта Open Screen Project, а это значит, что поддержка GPU ускорения Flash Player-ом на широком спектре различных устройств уже практически в кармане ваших брюк и от этого потеют ладошки.
Все читаем тут.
<Object/> vs <Embed/>. Backdoor.
Чуть-чуть истории, стандартов и браузеров. Чуть-чуть разногласий и принципиальных различий, технических нюансов и механизмов внедрения + немного субъективности и любознательности. Солянка готова.
Закиньте простыню с крестом в стирку и выпейте стаканчик молока если вдруг у кого к Microsoft-у сильная изжога имеется. А вообще-то, этот пост не про то. ![]()
Осторожно много букав!
Бумага не помогает? Помогут Balsamiq Mockups от Peldi Guilizzoni!
Balsamiq Mockups - приложения для быстрого(!) создания набросков.
Парочка примеров:
|
|
|
Идея проста. Приложения представляет из себя знакомый design-view c возможностью drag-and-drop элементов на рабочую область и редактированием многочисленных свойств. В библиотеке находятся 60+ готовых элементов на все(почти) случаи жизни.
Впоследствии, набросок можно экпортировать, например, в виде .png файла или в виде XML документа для будущего импортирования.
Оформить впечатление можно посмотрев и почитав. А для того чтобы пережить это, вам нужен всего лишь этот клик.
Существует версия для десктопа (в виде Air-приложения), а также интеграция с Confluence, XWiki и JIRA в виде плагинов.
Сейчас можно скачать бесплатную десктоп версию с обрезком функциональности, а именно без сохранения и загрузки мокапов. Хотя, и эту функциональность можно дополучить бесплатно
.
Стоит это чудо US $79 (за AIR версию). На плагины цены сильно варьируются.
SapphireSteel: Amethyst Beta 2
16 января SapphireSteel выпустила вторую бету Аметиста с интеллисэнс для ActionScript 3 и MXML.
Апдейты внизу.
Пара слов.
Как писал Рост, Amethyst - еще один плагин для Microsoft Visual Studio. Замечу, что он бесплатный (Amethyst Personal Edition) и ставится на бесплатную версию VS Shell, то есть по факту, чтобы использовать Amethyst как альтернативный Flex IDE, со вкусом от Microsoft, нужно вложить 0 денег.
Как я уже упомянул, Amethyst может быть установлен либо на коммерческую версию VS 2008, либо на специальную версию Visual Studio Shell, которая исключает поддержку основных языков от Microsoft. Другими словами, вы получаете ту же функциональность что и в “Express” версии VS, но без поддержки C#, C++ и VB .NET. Из-за конструктивных особенностей VS Express не поддерживает интеграцию со сторонними инструментами, поэтому Amethyst там не доступен.
Установка
Для инсталляции вам необходимы:
- Shell(free) или любая коммерческая версия Visual Studio 2008.
- Flex SDK и Flash 9/10.
- Java с Microsoft C библиотекой msvcr71.dll.
- И сам Amethyst Beta 2 инсталлятор.
В настоящее время доступны лишь инструкции по инсталляции, но в этом году планируется выпуск более продвинутого инсталлятора для Amethyst с возможностью доставки Visual Studio Shell и Flex SDK и прочего.
В будущем, для Amethyst Professional Edition, обещают “high-end” дебаггер и некий design view, с возможность drag-and-drop (в Personal Edition также будет дебаггер, но видимо не такой “high-end”
.
Ну-с, попробуем..
Updated
Для инсталляции необходима Visual Studio 2008 Shell (integrated mode), либо можно скачать all-in-one инсталлятор для Ruby in Steel Personal Edition 2008, в нем уже включена VS 2008 Shell, скинуть ненужные галочки, инсталлировать, а затем доставить Amethyst.
Updated 2
В общем, впечатления никакие особо.
Парочка мелочей, вполне субъективных (может у кого не так?):
- Если сохранить Build-настройки проекта, то при следующем билде полезут ошибки mxml компилятора.
- Дебаггера не обещали пока, на на trace() я (напрасно)надеялся.
- HTML шаблон слегка нервирует, вынуждая все время разрешать отображение ActiveX в IE6.
- Работа интеллисэнс позабавила чутка. Хехе, у <mx:Button/> в mxml не предлагает событие “click” ![]()
Сыровата она (на то и бета).
List растягивающийся под контент. AutoResizeableList.
Дело было ранней зимой и я как обычно засыпал на работе у батареи. Потом я проснулся, умылся, взбодрился и сел писать. К сожалению, я не сильно имею представление на сколько это актуально и свежо подходом к решению проблемы, но, по крайне мере, один человек очень получал от этого много удовольствия ![]()
Кстати, перед тем как начать читать, тем кто мало знаком lifecycle компонентов во Flex, я бы посоветовал познакомиться с этим, вот этим и с тем.
Начнем-с.
Необходимо было сделать компонент List само(ра)стягивающимся под данные по высоте, то есть визуализовать все данные находящиеся в дата провайдере. Если упростить понятие данных, то в этом случае это простой текст, который надо отобразить в полном объеме. Другими словами, в качестве рендерера должен выступать любой контейнер содержащий любой UIComponent, способный отображать не обрезанный многострочный текст.
Листинг 1. Например это такой рендерер:
- <mx:VBox xmlns:mx="http://www.adobe.com/2006/mxml"
- mouseChildren="false">
- <mx:Script>
- <![CDATA[
- ...
- ]]>
- </mx:Script>
- <mx:Canvas width="100%">
- <mx:Text id="tf" width="100%"/>
- </mx:Canvas>
- </mx:VBox>
Если же рендерер не содержит в себе особых компонентов и его размеры можно корректно высчитать за один проход валидации, то вот самый примитивный способ. Выставляем List#variableRowHeight=true (в случае если высота варьируется) и снаружи его можно обсчитать с помощью, например, List#measureHeightOfItems() и делать это на событие FlexEvent.UPDATE_COMPLETE.
Листинг 2. Примерно так, без напильников и предохранителей:
- <mx:Script>
- <![CDATA[
- private function setMeasuredHeight(list:List):void
- {
- var h:Number = list.measureHeightOfItems();
- list.height = h + list.getStyle("paddingTop") +
- list.getStyle("paddingBottom");
- }
- ]]>
- </mx:Script>
- <mx:List id="simpleList"
- width="400" variableRowHeight="true"
- itemRenderer="SimpleRenderer"
- dataProvider="{dp}"
- updateComplete="setMeasuredHeight(simpleList)"/>
Особые компоненты.
Что же такое “особые” компоненты и их особенное поведение? Есть стандартные компоненты, способные отображать многострочный текст на базе flash.text.TextField, но из-за странностей в реализации текстовых полей во FlashPlayer, они становятся не обычными. Для них не достаточно одного прохода валидации. Им нужно два, иначе после первого прохода мы получаем некорректный TextField#textHeight. И то, как такие компоненты с этим живут, можно увидеть, например, в mx.controls.Text#updateDisplayList().
Листинг 3. mx.controls.Text#updateDisplayList() строка 326.
- if (isSpecialCase()){
- var firstTime:Boolean = isNaN(lastUnscaledWidth) ||
- lastUnscaledWidth != unscaledWidth;
- lastUnscaledWidth = unscaledWidth;
- if (firstTime){
- invalidateSize();
- return;
- }
- }
Вкратце, если updateDisplayList компонента произошел в первый раз после апдейта данных (присваивание текста в текстовое поле), пинаем еще один measure и выходим из метода. И только во время следующего measure будет вычислен корректный размер и запущен второй updateDisplayList для коррекции визуального представления. Это незаметно снаружи, в случаях когда мы присваиваем текстовому полю какой-нибудь текст и сразу вызываем validateNow(), то мы получаем правильные размеры, но если заглянуть внутрь, то мы увидим, что measure() и updatedDisplayList() выполняются два раза и происходит это в одном кадре не без помощи LayoutManager-а.
Под проходом валидации я понимаю процесс, который затрагивает по крайней мере один из защищенных методов: commitProperties, measure и updateDisplayList. В нашем случае, нас больше итересуют два последних.
Пара слов о компоненте List.
Если List-у не задана высота напрямую пользователем и не жестко диктуется родительским контейнером, то инициализируясь он всегда вычисляет List#measuredHeight руководствуясь List#rowCount и List#rowHeight. Если они не заданы вручную, то List#measuredHeight будет всегда равен 140 px, потому как высота строки по умолчанию 20 и количество строк по умолчанию 7. В ситуации когда высота List-а задана напрямую (e.g List#height = 320) фаза measure будет пропущена. Если же нет, то мы получим высоту в 140 px независимо от количества данных в провайдере и количества данных в рендерерах, которые поместились в этих 140 px.
Еще немного занудных подробностей о lifecycle компонента List. В первый проход updateDisplayList List создаст и отвалидирует рендереры, но сделает это не корректно. Если остановить валидацию на первом проходе мы увидим примерно неопрятную картину.
Картинка 1.

В нормальных условиях особые рендереры, используя трюк с двойной валидацией, возбуждают List к повторному measure и updateDisplayList (см. Листинг 3). Ко второму updateDisplayList рендереры уже будут иметь корректные measured размеры и там List задаст им правильный лэйаут, то есть им будет выставлена правильная высота и тд.
Картинка 2.

Отмечу также, что мы не выставляем никаких значений для List#rowCount и List#rowHeight, так как они попросту не нужны в случае когда List#variableRowHeight=true. Вообще, фаза measure с вариативным рендерером (для стандартного List-a) на мой вкус довольно бесполезна, потому как уже говорилось выше List#measuredHeight всегда будет высчитана по умолчанию в 140 px. Мы постараемся внести в нее немного больше смысла, заставив List растягиваться под контент по высоте.
Итак. Скрытый рендерер.
Необходимо высчитать размер всех рендереров на базе данных и учесть стили. Так как, например, в самом начале при инициализации в фазе measure List не имеет никаких созданных рендереров, то нужда заставляет нас использовать стандартный внутренний механизм скрытого рендерера. Cкрытый рендерер - это рендерер созданный внутри самим List-ом на базе List#itemRenderer с visible=false. С его помощью List производит необходимые вычисления для лэйаута, например, это касается вертикального скролбара или вычисления List#rowHeight.
Листинг 4. Создание скрытого рендерера внутри компонента List. mx.controls.List#getMeasuringRenderer() строка 1732.
- if (!item) {
- item = createItemRenderer(data);
- item.owner = this;
- item.name = "hiddenItem";
- item.visible = false;
- item.styleName = listContent;
- listContent.addChild(DisplayObject(item));
- measuringObjects[factory] = item;
- }
Кульминация. Проблемные места.
Основная идея очевидна и проста. Необходимо пройти по всем данным пихая их в скрытый рендерер, валидируя и снимая с него measured размеры. Таким образом, складывая measured размеры одного и того же рендерера отвалидированного с разными данными в одну кучу, но с учетом паддингов и хрома, мы получаем общую measured высоту для List-a.
Листинг 5. com.riapriority.vertex.control.AutoResizableList#measure() строка 162.
- for (var i:int = 0; i < max; ++i) {
- // Reusing standart mechanism to get hidden renderer.
- renderer = getMeasuringRenderer(dp[i]);
- rect = validateAndMeasure(dp[i], renderer);
- h += rect.height + paddingTop + paddingBottom;
- }
- // We add top and bottom chrom + bottom offset
- measuredHeight = h + (viewMetrics.top + viewMetrics.bottom) + _bottomOffset;
- measuredMinHeight = measuredHeight;
- shouldBeUpdated = true;
На этом первый проход measure заканчивается и за ним следует updateDisplayList, в котором List по необходимости создает рендереры и выставляет им размеры и позиционирование. Но, как уже говорилось, если мы имеем дело с особыми компонентами, то в этот (первый) проход они отдадут неверные размеры, которые и будут им выставлены List-ом. Но эти рендереры сразу же попытаются возбудить List к повторному measure, который в свою очередь должен вызвать дополнительный updateDisplayList для корректировки лэйаута. В нашем случае с расширенным компонентом так и происходит, за исключением повторного вызова updateDisplayList и это не гут (на сколко это плохо мы наблюдали на картинке..). Почему? Все довольно просто, когда на List-е возбуждается вторая по счету всплывшая фаза measure (measure происходит снизу вверх) с подачи особых рендереров, то List снова обсчитывает все рендереры, снимая с них уже корректные размеры, делая это примерно также как я описал в предыдущем листинге и сравнивает результат со своими, а они были выставлены нами очень правильно в еще первый проход measure. В итоге, оба размера правильные и совпадают, а значит вызова updateDisplayList не произойдет вследствие отпимизационных введений. И это значит то, что в нашей ситуации ортодоксальное использование концепции жизненного цикла компонентов слегка хромает. Ибо основная концепция measure - вычисление предпочтительных размеров. Чтобы исправить положение мы воспользуемся ручным управлением.
Листинг 6.
com.riapriority.vertex.control.AutoResizableList#updateDisplayList() строка 204.
- if (shouldBeUpdated){
- shouldBeUpdated = false;
- callLater(function():void{
- invalidateDisplayListFlag = true;
- validateDisplayList();
- });
- }
Если попытаться пнуть это стандартным образом, например, используя List#invalidateDisplayList(), то фаза updateDisplayList не будет запущена по причине того, что мы в ней находимся в данный момент. Можно запихать List#invalidateDisplayList() в callLater и это будет работать, но мы проиграем в производительности, потому что окажемся в этой фазе только через два кадра, а жуть как хотелось бы на кадр пораньше.
Добавка о производительности.
С таким подходом мы удваиваем все вызовы связанные с валидацией, потому как сначала мы прогоняем валидацию сами, используя скрытый рендерер, а затем List, создавая визуальные рендереры, количество которых также соответствует данным в провайдере (так как мы выставили достаточную measuredHeight), прогоняет валидацию для них. Вообще, в рамках идеи “fit to content” производительность уже страдает, так как создаются все визуальные рендереры соответственно данным и если мы имеем очень много данных, то это опасный подход. В случае очень большого количества данных лучше размазывать процесс во времени и скорее всего не использовать List, но здесь все строго индивидуально. В этом примере, при количестве данных в ~500 единиц, мы получаем довольно ощутимую паузу перед рендерингом контрола на сцену, хотя, с моей точки зрения, даже так вполне бодро.
Другой подход?
Можно было бы избежать удваивание вызовов валидации, используя updateDisplayList для выставления явной высоты List-у и не используя measure вообще, но в это приведет к совсем плачевным результатам. Казалось бы, раз в updateDisplayList происходит создание рендереров для List-а, то вместо скрытого рендерера, для вычисление measured размеров, там можно использовать уже созданные. Но не тут то было. И я думаю, многим известно почему. List - достаточно оптимизированный компонент чтобы создавать ровно столько рендереров, сколько необходимо. И так как в measure высота вычисляется в 140 px, то и рендереров будет создано лимитированное количество. А именно, не больше 15 штук. То есть, при дата провайдере в 16 итемов List создаст 7 рендереров для 7 строк по умолчанию. Затем вычислит количество рендереров для буферизации сверху и снизу (16/2=8), но буферизацию для верха создавать не имеет смысла, так как очевидно, что при создании List-а мы и так будем находиться в крайнем верхнем положении с List#verticalScrollPosition=0.
Листинг 7.
mx.controls.listClasses.ListBase#makeRowsAndColumnsWithExtraRows() строка 1344.
- var desiredExtraRowsTop:int = offscreenExtraRows / 2;
- var desiredExtraRowsBottom:int = offscreenExtraRows / 2;
- offscreenExtraRowsTop = Math.min(desiredExtraRowsTop, verticalScrollPosition);
В следствии чего получаем 7+8=15. Соответственно, для провайдера длинной в 58 итемов, при проходе всех данных и снятии разсера через getExplicitOrMeasuredHeight, мы получим ексепшен на 36 рендерере, так как его не будет существовать. А значит, что этот подход нас не удовлетворяет и даже с выставленным List#offscreenExtraRowsOrColumns = List#dataProvider.length.
Пара нюансов.
При тестировании возникла пара проблем с drag-and-drop. Нельзя было перетащить итем в самый низ List-а. В нашем компоненте мы имеем свойство AutoResizableList#bottomOffset, которое позволяет задавать пространство снизу. Оно необходимо для возможности сделать drag-and-drop вниз List-a, а также предотвращает ошибку в случае перетаскивания в пустой List. Вы может продемонстрировать это себе сами, просто перетащите все из левого List-а в правый, а затем попытайтесь перетащить один из итемов обратно (эксепшен тихо проглатывается при отсутствии дебаг версии FlashPlayer). Хотя, этого эксепшена можно избежать перекрыв метод List#showDropFeedback() и пристрочив заплатку.
Наконец-то его можно потрогать и все остальное уже не важно:
Странно, но внутри тоже получилось много комментов.
Скачать пример отсюда. view source.
И наконец.
Eсли вам нет нужды использовать пряники компонента List, например, API по работе с данными и визуалом, drag-and-drop и etc, то тогда не надо заниматься извращениями, а надо просто использовать стандартный mx.controls.VBox и побыстрей забыть это стремное многобуквие ![]()
Ну а теперь самое главное.