Flex. Понимание itemRenderer'ов. Часть 1. Встроенные рендереры

Приношу свои извинения за некачественный перевод.

Питер Инт (Peter Ent)

Flex. Понимание itemRenderer'ов

Часть 1. Встроенные рендереры

Flex предоставляет много объектов управления (контролов), чтобы отобразить большие количества данных разными способами. Имеются контролы List, DataGrid, Tree, и классы визуализации, которые включают в себя диаграммы и AdvancedDataGrid. По умолчанию, во Flex списковые контролы отображают данные, которые им дают как простой текст. Но Flex способен к намного большему, и списковые контролы обеспечивают способ настроить их информационное наполнение, используя itemRenderer'ы. Давая Вам полный контроль над информационным наполнением каждой строки (или ячейки) списка, используя itemRenderer'ы, Flex дает возможность Вам писать более привлекательные, более творческие, и более полезные приложения, чем когда-либо прежде.

Эта серия статей обсуждает itemRenderer'ы во Flex, и как использовать их эффектно и эффективно. Первая часть статьи сосредотачивается на действующих itemRenderer'ах, которые закодированы в пределах описания тэгов MXML контрола List. Дальнейшие части статьи исследуют более сложные itemRenderer'ы, использующие и MXML и ActionScript.

Циклические рендереры

Одну вещь, которую много людей пытаются сделать, является доступ к itemRenderer'у извне списка. Например, Вы можете захотеть заставить ячейку в четвертом столбце пятой строки в DataGrid стать зеленой, потому, что Вы только что получили новые данные с сервера. Получив этот экземпляр класса itemRenderer, и изменяя его, внешне были бы огромным breech Flex-фреймворка и модели компонентов.

Чтобы понять itemRenderer'ы, Вы должны понять, в чём состояли наши намерения, когда мы проектировали их. Между прочим, когда я говорю, что "мы", я действительно подразумеваю техническую группу Adobe Flex. Я не имел никакого отношения к этому. Так или иначе, предположим, что у Вас есть 1 000 записей, которе Вы хотите показать. Если Вы думаете, что управление списка создает 1 000 itemRenderers, Вы неправы. Если список показывает только 10 строк, список создает около 12 itemRenderer'ов - этого достаточно, чтобы показать каждую видимую строку, плюс пара по причинам буферизации и производительности. Список первоначально показывает строки 1–10. Когда пользователь прокручивает список, список может теперь показывать строки 3–12. Но те те же самые 12 itemRenderer'ов всё ещё там: никакие новые itemRenderer'ы не создаются, даже после прокруток списка.

Вот, что делает Flex. Когда список прокручен, те itemRenderer'ы, которые будут все ещё показывать те же самые данные (строки 3–10) перемещяются вверх. Кроме того, что стали в новом расположении, они не изменились. itemRenderer'ы, которые показывали данные для строк 1 и 2, теперь перемещены ниже itemRenderer'а для строки 10. Тогда другие itemRenderer'ы дают данные для строк 11 и 12. Другими словами, если Вы не изменяете размеры списка, те же самые itemRenderer'ы являются "переиспользоваными-перецикленными" к новому расположению и теперь показывают новые данные.

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

Итак, как Вы производите изменения?

itemRenderer'ы должны изменить себя, основываясь на данных, которые им дают для показа. Если itemRenderer для списка, как предполагается, изменяет свой цвет, основанный на значении данных, то он должен смотреть на данные, которые ему даны, и изменить себя.

Встроенные рендереры

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

Во всех примерах я буду использовать те же самые данные: коллекция информации о авторе книги, заголовке, годе издания, эскизе изображения, и т.д. Каждая запись - узел XML, который выглядит следующим образом:

<book>
    <author>Peter F. Hamilton</author>
    <title>Pandora's Star</title>
    <image>assets/pandoras_star_.jpg</image>
    <date>Dec 3, 2004</date>
</book>

Я начинаю с простого itemRenderer'а, использующегося в контроле <mx:List>. Здесь автор перечислен следом за заголовком книги.

<mx:List x="29" y="67" dataProvider="{testData.book}" width="286" height="190">
    <mx:itemRenderer>
        <mx:Component>
            <mx:Label text="{data.author}:{data.title}"/>
        </mx:Component>
    </mx:itemRenderer>
</mx:List>

Этот itemRenderer настолько прост, что labelFunction вероятно был бы лучше, но это, по крайней мере, позволяет Вам сосредотачиваться на важных частях. Во-первых, встроенный itemRenderer использует тэг <mx:itemRenderer>, чтобы определить его. В пределах этого тэга tcnm тэг <mx:Component>. Этот тэг должен быть здесь, поскольку он говорит Flex-компилятору, что Вы определяете встроенный компонент. Я немного опишу то, в чём это действительно означает.

В пределах тэга <mx:Component> Вы определяете свой itemRenderer. Для этого примера - один <mx:Label> с его текстовым полевым устанавливается к выражению привязки данных: {data.author}:{data.title}. Это очень важно. Контрол List дает каждому экземпляру класса itemRenderer запись в dataProvider, устанавливая свойство data itemRenderer'а. Рассматривая код выше, это означает, что для любой данной строки списка, действующему экземпляру класса itemRenderer установят его свойство data в XML-узле <book> (такой же, как тот, что выше). Поскольку Вы просматриваете список, свойство data изменяется, поскольку itemRenderer'ы переработаны для новых строк.

Другими словами, экземпляра класса itemRenderer для строки 1 может теперь установить свой data.author в "Peter F. Hamilton", но когда он прокручивается и становиться не виден, itemRenderer будет переработан и свойство data - для которого то же самый itemRenderer - может теперь установить data.author в "J.K. Rowling". Все это случается автоматически при прокрутке списка - Вы не волнуетесь об этом.

Здесь представлен более сложный встроенный itemRenderer, где снова используется контрол <mx:List>:

<mx:List x="372" y="67" width="351" height="190" variableRowHeight="true" dataProvider="{testData.book}">
   <mx:itemRenderer>
       <mx:Component>
           <mx:HBox >
               <mx:Image source="{data.image}" width="50" height="50" scaleContent="true" />
               <mx:Label text="{data.author}" width="125" />
               <mx:Text  text="{data.title}" width="100%" />
           </mx:HBox>
       </mx:Component>
   </mx:itemRenderer>
</mx:List>

Он действительно не очень отличается. Вместо <mx:Label>, itemRenderer это <mx:HBox> с <mx:Image>, <mx:Label> и <mx:Text>. Привязка данных всё ещё связывает визуальное с записью.

Таблица

Вы можете также использовать встроенные itemRenderer'ы в таблице. Здесь один применяется к колонке:

<mx:DataGrid x="29" y="303" width="694" height="190" dataProvider="{testData.book}" variableRowHeight="true">
    <mx:columns>
        <mx:DataGridColumn headerText="Pub Date" dataField="date" width="85"/>
        <mx:DataGridColumn headerText="Author" dataField="author" width="125"/>
        <mx:DataGridColumn headerText="Title" dataField="title">
            <mx:itemRenderer>
                <mx:Component>
                    <mx:HBox paddingLeft="2">
                        <mx:Script>
                        <![CDATA[
                            override public function set data(value:Object):void {
                                super.data = value;
                                var today:Number = (new Date()).time;
                                var pubDate:Number = Date.parse(data.date);
                                if(pubDate > today) setStyle("backgroundColor",0xff99ff);
                                else setStyle("backgroundColor",0xffffff);
                            }
                        ]]>
                        </mx:Script>
                        <mx:Image source="{data.image}" width="50" height="50" scaleContent="true"/>
                        <mx:Text width="100%" text="{data.title}"/>
                    </mx:HBox>
                </mx:Component>
            </mx:itemRenderer>
        </mx:DataGridColumn>
    </mx:columns>
</mx:DataGrid>

Как Вы можете видеть, он намного более сложен, чем последние два примера, но у него есть та же самая структура: <mx:itemRenderer> с <mx:Component> определённым внутри него.

Цель <mx:Component> состоит в том, чтобы обеспечить синтаксис MXML для того, чтобы он создал в коде правильный класс ActionScript. Изобразите код, который появляется в блоке <mx:Component>, вырезая и помещая в отдельный файл и данное имя класса. Когда Вы смотрите на встроенный itemRenderer, это действительно похоже на законченный файл MXML, не так ли? Имеется корневой тэг (<mx:HBox> в этом случае) и даже блок <mx:Script>.

Цель блока <mx:Script> в этом примере - перегрузить функцию set data так, чтобы цвет фона itemRenderer, мог быть изменён. В этом случае, фон изменён из белого всякий раз, когда данные о публикации книги находится в будущем. Запомните, что itemRenderer'ы перерабатываются, таким образом, цвет должен также быть установлен в белый, если тест терпит неудачу. Иначе, все itemRenderer'ы в конечном счёте станут фиолетовыми, поскольку пользователь прокручивает список.

Внешний документ

Область видимости также изменяется. Что я имею в виду, переменные, которые Вы определяете внутри <mx:Component>, имеют действие только к тому компонентному/встроенному itemRenderer'у. Аналогично, содержание за пределами <mx:Component> находится в другой области видимости, так же, как если бы этот компонент был определён в отдельном файле. Например, предположим, что Вы добавляете контрол Button к этому itemRenderer'у, который позволяет пользователю покупать книгу у розничного продавца в режиме онлайн. Кнопки вызывают функции при событии ckick, таким образом, Вы можете определить кнопку как здесь:

<mx:Button label="Buy" click="buyBook(data)"/>

Если бы buyBook() функция была определена в блоке <mx:Script> файла, то Вы получили бы ошибку, говорящую, что buyBook() является неопределенным методом. Поэтому buyBook() определен в пределах файла, а не в пределах <mx:Component>. Так как это - типичный случай использования, есть другой путь с использованием идентификатора outerDocument:

<mx:Button label="Buy" click="outerDocument.buyBook(data)"/>

Идентификатор outerDocument изменяет область видимости, чтобы рассмотреть файл, или внешний документ, в отношении к <mx:Component>. Теперь остерегайтесь: функция должна быть public function, не protected или private. Помните, что <mx:Component> рассматриваются как внешне определенный класс.

"Всплывающие" события

Теперь взгляд на другой, ещё более сложный пример. Это контрол TileList, использующий те же самые данные.

<mx:TileList x="29" y="542" width="694"
dataProvider="{testData.book}" height="232" columnWidth="275"
rowHeight="135" >
    <mx:itemRenderer>
        <mx:Component>
            <mx:HBox verticalAlign="top">
                <mx:Image source="{data.image}" />
                <mx:VBox height="115" verticalAlign="top" verticalGap="0">
                    <mx:Text text="{data.title}" fontWeight="bold" width="100%"/>
                    <mx:Spacer height="20" />
                    <mx:Label text="{data.author}" />
                    <mx:Label text="Available {data.date}" />
                    <mx:Spacer height="100%" />
                    <mx:HBox width="100%" horizontalAlign="right">
                        <mx:Button label="Buy" fillColors="[0×99ff99,0×99ff99]">
                           <mx:click>
                               <mx:Script>
                                <![CDATA[
                                    var e:BuyBookEvent = new BuyBookEvent();
                                    e.bookData = data;
                                    dispatchEvent(e);
                                ]]>
                                </mx:Script>
                            </mx:click>
                        </mx:Button>
                    </mx:HBox>
                </mx:VBox>
            </mx:HBox>
        </mx:Component>
    </mx:itemRenderer>
</mx:TileList>

itemRenderer выглядит как на Рисунке 1, когда приложение запущено:

itemRenderer реализованный в TileList.
Рисунок 1. itemRenderer реализованный в TileList.

Этот itemRenderer достаточно близок к используемому в DataGrid, но при событии click на кнопке Buy, не использует outerDocument, чтобы вызвать функцию. В этом случае click создает пользовательское событие, которое выталкивает из itemRenderer'а через TileList, и принимается некоторым более высоким компонентом в визуальной цепочке.

Это очень обычная проблема: у Вас есть itemRenderer, который имеет некоторый интерактивный контрол, обычно Button, LinkButton или другой компонент, который, как предполагается, заставляет выполняться некоторое действие, когда имеет место нажатие. Возможно, это должно удалить строку или как в этом случае, купить книгу.

Неблагоразумно ожидать, что itemRenderer сделает работу. В конце концов, задача itemRenderer'а - сделать хороший просмотр списка. "Всплываюшее" событие позволяет itemRenderer'у выдавать работу к чему-то ещё. Пользовательское событие полезно здесь, потому что событие связано с данными в строке; итак, почему бы не включать эти данные в событие? Таким образом, получатель события не должен будет идти вниз отслеживать это.

Заключение

Использование встроенных itemRenderer'ов это хороший и быстрый способ придать Вашим спискам пользовательский вид. В конце концов, рассматривайте встроенные itemRenderer'ы как отдельные классы ActionScript, они - рассматриваются, как будто они уже были. Если Вы должны ссылаться на функции или свойства в тексте файла, используйте идентификатор outerDocument, чтобы изменить область видимости. Если Вам нужно передавать информацию как результат взаимодействия с itemRenderer, используйте пользовательское, "всплывающее" событие.

И запомните: не пытайтесь удерживать itemRenderer'ы - они рециклические по назначению. Делайте их ответственными только за переданные им данные.

Файлы примеров: itemrenderers_pt1.zip

Источник


Вперёд->