Опубликован: 02.04.2013 | Доступ: свободный | Студентов: 1117 / 53 | Длительность: 20:18:00
Специальности: Программист
Лекция 6:

Коллекции и элементы управления для вывода коллекций

Макеты и объединение ячеек

Свойство layout элемента управления ListView, которое вы можете задать в любое время, содержит обект, который используется для организации элементов списка. WinJS предоставляет два предустановленных макета: WinJS.UI.GridLayout и WinJS.UI.ListLayout. Первый, уже описанный ранее, обеспечивает горизонтальную прокрутку двумерного макета, который располагает элементы в столбцах (сверху вниз) и затем в строках (слева направо). Второй - это одномерный макет, располагающий элементы сверху вниз, подходит для вертикальных списков (как в прикрепленном режиме просмотра). И тот и другой следуют рекомендованным для представления коллекций подходам к дизайну.

Говоря техническим языком, свойство layout - это объект, содержащий некоторое количество других параметров вместе со свойством type. Обычно вы видите синтаксис layout: { type: <layout> } в строке data-win-options ListView, где <layout> - это WinJS.UI.GridLayout или WinJS.UI.ListLayout (технически - имя функции-конструктора). При декларативном использовании, layout может так же содержать особенные параметры, зависящие от его типа. Например, следующий код конфигурирует gridLayout, содержащий заголовки слева и четыре строки:

layout: { type: WinJS.UI.GridLayout, groupHeaderPosition: 'left', maxRows: 4 }

Если вы создаете объект макета в JavaScript, используя new для вызова конструктора напрямую (и присваивая её свойству layout), вы можете задать дополнительные параметры в конструкторе. Это исполняется в приложении, построенном по шаблону Приложение таблицы в методе initializeLayout в файле pages/groupedItems/groupedItems.js:

listView.layout = new ui.GridLayout({ groupHeaderPosition: "top" });

Кроме того, вы можете задать свойства объекта layout элемента управления ListView в JavaScript, при его создании, если хотите воспользоваться подобным подходом. Обычно изменение свойств обновляет макет.

В любом случае, каждый макет имеет собственные уникальные параметры. Для gridLayout это следующие:

  • groupHeaderPosition управляет расположением заголовков по отношению к их группам; может быть установлен в значения "left" или "top".
  • maxRows управляет количеством элементов, которые макет расположит вертикально, прежде чем начнёт располагать их в следующем столбце.
  • backdropColor предназначен для настройки цвета заставки по умолчанию (смотрите "Заставки" в предыдущем разделе), и disableBackdrops полностью отключает данный эффект.
  • groupInfo определяет функцию, которая возвращает объект, свойства которого показывают, следует ли использовать объединение ячеек и размер ячеек (смотрите ниже). Данный вызов осуществляется лишь однажды при обработке макета.
  • itemInfo определяет функцию для использования вместе с объединением ячеек, которая возвращает объект, свойства которого описывают точный размер для каждого элемента, и то, следует ли разместить элемент в новой колонке (смотрите ниже).

gridLayout так же имеет свойство только для чтения, которое называется horizontal и всегда имеет значение true. Для ListLayout свойство horizontal всегда false и не имеет других настраиваемых параметров.

Теперь, так как свойство layout ListView - это лишь объект (или имя конструктора для подобного объекта), можете ли вы создать собственную функцию, определяющую макет? Да, вы можете: создайте класс, который обеспечивет те же самые открытые методы, что и встроенные макеты, как описано в WinJS.UI.Layout (http://msdn.microsoft.com/library/windows/apps/br211781.aspx) (данная тема пока недостаточно документирована). Здесь объект макета может предоставлять любые другие параметры (свойства и методы), которые к нему применимы.

Сейчас, прежде чем вы начали размышлять над тем, нужен ли вам макет собственной разработки, хочу отметить, что gridLayout предоставляет кое-что, называемое объединением ячеек (cell spanning), что позволяет вам создавать элементы различных размеров (для ListLayout это неприменимо). Именно для этого существуют свойства groupInfo и itemInfo, как показано в Сценариях 4 и 5 примера "Шаблоны элемента для HTML ListView" (http://code.msdn.microsoft.com/windowsapps/ListView-item-templates-7d74826f), как показано на Рис. 5.9.

Пример шаблонов элемента ListView показывает использование элементов различных размеров с использованием объединения ячеек

увеличить изображение
Рис. 5.9. Пример шаблонов элемента ListView показывает использование элементов различных размеров с использованием объединения ячеек

Основная идея объединения ячеек - это задать ячейку для gridLayout, основываясь на размере наименьшего элемента (включая стили отбивки (padding) и полей(margin)). Для лучшей производительности делайте ячейки настолько большими, насколько это возможно, чтобы размер любого другого элемента в ListView был кратен размеру этого элемента.

Включается возможность объединения ячеек посредством свойства groupInfo gridLayout. Это функция, которая возвращает объект с треямя свойствами: enableCellsSpanning, которое следует установить в true, cellWidth и cellHeight, которые содержат размеры минимальной ячейки в пикселях (что, кстати, использует возможность gridLayout для вывода заставки в подобной ситуации). В примере (смотрите js/data.js), эта функция названа groupInfo, так же как свойство макета. Здесь, для ясности, я дал ей другое имя:

function cellSpanningInfo() {
return {	
enableCellSpanning: true,
cellWidth: 310,	
cellHeight: 80	
};	
}

Затем эту функцию задают как часть свойства layout в data-win-options:

layout: { type: WinJS.UI.GridLayout, groupInfo: cellSpanningInfo }

Или вы можете задать layout.groupInfo из JavaScript. В любом слуае, как только вы объявили об использовании объединения ячеек, шаблон элемента должен установить свойства каждого элемента style.width и style.height, и - подходящие значения отбивки, для увеличения ваших cellWidth и cellHeight в соответствии со следующими формулами (которые являются разными представлениями одной и той же формулы):

templateSize = ((cellSize + margin) x multiplier) - margin (размерШаблона = ((размерЯчейки + поле) х множитель) - поле)

cellSize = ((templateSize + margin) / multiplier) - margin
(размерЯчейки = ((размерШаблона+поле)/множитель) - поле)

В примере, эти стили установлены путём задания каждому из элементов одного из трёх имён классов: smallListIconTextItem, mediumListIconTextItem, и largeListIconTextItem, CSS-код которых приведен ниже: (из css/scenario4.css иcss/scenario5.css):

.smallListIconTextItem {
width: 300px;	
height: 70px;	
padding: 5px;	
}
.mediumListIconTextItem {
width: 300px;	
height: 160px;	
padding: 5px;	
}
.largeListIconTextItem {
width: 300px;	
height: 250px;	
padding: 5px;	
}

Так как каждый из этих классов имеет отбивку, их реальные размеры из CSS равняются 310х80, 310х170 и 310х260. Поля, которые используются в формуле, исходят из стиля win-container в таблице стилей WinJS, где они равны 5 пикселей (px). Таким образом:

((80 + 10) * 1) - 10 = 80; минус 5px отбивка сверху и снизу =  высота 70px в CSS 
((80 + 10) * 2) - 10 = 170; минус 5px отбивка = высота 160px
((80 + 10) * 3) - 10 = 260; минус 5px отбивка = высота 250px

Разница между Сценарием 4 и Сценарием 5 лишь в том, что первый присваивает имена классов элементам посредством функции шаблона. Последний же делает это в декларативной разметке и привязывает данные об именах классов к полям данных элемента, содержащим эти значения.

Что касается функции itemInfo, это способ оптимизации производительности listView при использовании объединения ячеек. Без присваивания функции этому свойству, gridLayout "вручную" определяет ширину и высоту каждого элемента при выводе и это может замедлить прокрутку, если вы быстро прокручиваете большое число элементов. Так как вам, возможно, уже известны размеры элементов, вы можете предоставить эту информацию посредством функции itemInfo. Эта функция принимает индекс элемента и возвращает объект, содержащий свойства элемента width и height. (Скоро мы увидим работающий пример).

function itemInfo(itemIndex) {
//определяет значения itemWidth и itemHeight по заданному itemIndex
return {	
newColumn: false,	
itemWidth: itemWidth,	
itemHeight: itemHeight	
};	
}

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

Возможно, вы так же заметили свойство newColumn в возвращаемом значении. Как вы можете догадаться, установка его в true принуждает gridLayout начать новый столбец с этой ячейки, позволяя вам управлять данным поведением. Вы даже можете использовать newColumn самостоятельно, если хотите, с самыми маленькими списками.

Сейчас вы можете задаться вопросом: что произойдёт, если я задам различные размеры в шаблоне элемента, но не объявлю об объединении ячеек? Всё закончится перекрывающими (и весьма странно выглядящими) ячейками. Это происходит потому, что gridLayout воспринимает первый элемент в группе в качестве базового, задающего размеры всех остальных элементов (и, так же, сетки из заставок). Он не пытается автоматически изменить размер каждого элемента в зависимости от его содержимого. Испытайте это в Сценариях 4 и 5: удалите свойство layout.group из data-win-options ListView в html/scenario4.html или html/scenario5.html и перезапустите приложение. Вы увидите, как элементы среднего и большого размера заходят друг на друга, как показано ниже:

Затем, пройдите в js/data.js и установите стиль первого элемента в массиве myCellSpanningData на largeListIconTextItem, и перезапустите приложение. ListView теперь будет иметь макет с данным размером, установленным в качестве базового размера элемента:

Использование размеров первого элемента, как здесь, подчёркивает тот факт, что ListView с объединением ячеек требует больше времени на вывод, так как он должен измерить каждый из элементов в том виде, в котором он размещается, либо с помощью функции itemInfo, либо без неё. По этой причине, возможно, лучше избегать объединения ячеек при работе с большими списками.

Гораздо интереснее это всё становится, если подумать о том, как gridLayout поступает с элементами разной ширины. Да и в примере этого нет. В базовом алгоритме он всё еще располагает элементы сверху вниз и слева направо, но сейчас он заполняет пустые пространства небольшими элементами, когда большие элементы создают подобные пустоты. Для того, чтобы это показать, изменим пример таким образом, чтобы наименьший элемент имел размеры 155х80 (половина оригинального размера), средний был размером 310х80, и большой - 310х160. Вот какие изменения позволят нам это реализовать:

  1. Отменим любые изменения из предыдущих испытаний: в html/scenario4.html, вернем назад groupInfo в data-win-options, и в js/data.js, изменим класс первого элемента myCellSpanningData обратно к значению smallListIconTextItem.
  2. В js/data.js, изменим cellWidth в groupInfo на 155 (половина от 310), и оставим cellHeight в значении 80. Для ясности, кроме того, добавим значение приращения к началу текста каждого элемента в массиве myCellSpanningData.
  3. В css/scenario4.css:
    • Изменим ширину (width) smallListIconTextItem до 145px. Применим формулу, ((145+10)*1)-10=145. Высоту (height) изменим на 70px.
    • Изменим ширину mediumListIconTextItem на 310px, высоту - на 70px.
    • Изменим ширину largeListIconTextItem на 310px, выс оту на 160px. Для высоты применим формулу: ((80+10)*2)-10=170px.
    • Установим стиль width в правиле #listview в значение 800px, height - в 600px (для того, чтобы было больше места, в котором можно видеть макет)

Рекомендую выполнять эти изменения в Blend, где ваши правки отражаются на результате гораздо быстрее, чем когда вы, для проверки, запускаете приложение из Visual Studio. В любом случае, результаты, которые показаны на Рис. 5.10, где числа показывают нам порядок, в котором расположены элементы (извиняюсь за обрезанный текст… чем-то приходится жертвовать). Копию этого примера вы можете найти в дополнительных материалах к курсу.

Измененный пример работы с шаблонами элементов ListView, более полно показывающий объединение ячеек

увеличить изображение
Рис. 5.10. Измененный пример работы с шаблонами элементов ListView, более полно показывающий объединение ячеек

В измененный пример я включил функцию itemInfo в js/data.js, как вы уже могли заметить. Она возвращает размеры элементов в соответствии с типами, заданными для этих элементов:

function itemInfo(index) {	
//getItem(index).data получает массив элементов из WinJS.Binding.List
var item = myCellSpanningData.getItem(index).data;	
var width, height;	
switch (item.type) {	
case "smallListIconTextItem":
width = 145;	
height = 70;	
break;	
case "mediumListIconTextItem":
width = 310;	
height = 70;	
break;	
case "largeListIconTextItem":
width = 310;	
height = 160;	
break;	
}	
return {	
newColumn: false,	
itemWidth: width,	
itemHeight: height
};	
}

Вы можете установить в этой функции точку останова и убедиться в том, что она вызывается для каждого элемента. Кроме того, вы можете увидеть, что это приводит к одному и тому же результату. Теперь измените возвращаемое значение newColumn так, как показано ниже, для того, чтобы принудительно начать вывод с новой колонки перед элементом №7 и №15 на Рис. 5.10, так как они странно располагаются по столбцам.

newColumn: (index == 6 || index == 14),  //Разрыв на элементах 7 и 15 (индексы 6 и 14)

Результат этого изменения показан на Рис. 5.11.

Вывод с новой колонки при объединении ячеек на элементах 7 и 15

Рис. 5.11. Вывод с новой колонки при объединении ячеек на элементах 7 и 15

Последнее, что я отметил, экспериментируя с данным примером, это то, что если размеры элементов в правиле стиля наподобие smallListIconTextItem меньше, чем размер дочернего элемента, такого, как .regularlistIconTextItem (который включает поля и отбивки) больший размер выигрывает в макете. В ходе собственных экспериментов вы можете захотеть удалить поля по умолчанию в 5px, которые установлены для win-container. Это то, что создаёт пустые пространства между элементами на Рис. 5.10, но это должно быть добавлено в уравнение. Следующее правило устанавливает это поле в 0px:

#listView > .win-horizontal .win-container {
margin: 0px;
}