Это часть 2 руководства, в котором показано несколько методов динамической установки размеров блока текста и обработки отложенного рендеринга составного вида в Appcelerator/Axway Titanium. В Части 1 этого урока мы рассмотрели динамическое изменение размера текстового блока после определения высоты строки текста с помощью события postlayout. Пример приложения представляет собой список плиток, каждая из которых состоит из изображения и текстового блока, в ScrollView. В этой части руководства мы рассмотрим, как отложить рендеринг всех наших тайлов, а затем плавно исчезнуть в ScrollView после того, как все наши тайлы будут загружены и визуализированы.

В этой статье предполагается, что у вас есть базовые знания о том, как использовать Titanium и Alloy. Пример проекта был разработан с использованием Ti SDK 6.2.2 на OS X 10.12.6 (с XCode 8.3.3) и протестирован на iOS 10.3 и Android 4 и 6.

Напомню, вот пример того, как будет выглядеть экран:

Если изображения удалены, а текст (и, возможно, даже местоположение изображения) предоставляется как удаленные данные, например, через интерфейс REST, загрузка данных будет происходить с задержкой. Если мы визуализируем ScrollView до того, как данные загрузятся, у нас будет много пустых мест. Даже если мы выберем предварительную загрузку данных, если у нас есть десятки тайлов для рендеринга, будет задержка рендеринга только из-за накладных расходов на графику. Имейте в виду, что мы выбрали очень простой пример, но в реальном мире расположение плитки может быть намного сложнее, чем это (например, с заголовком, датой, иконками и черт знает чем еще, а также изображение и текстовое резюме в макете). Один из возможных подходов состоит в том, чтобы отображать плитки с их текстом и оставлять изображения для загрузки одно за другим (иногда разработчики используют счетчик загрузки для каждого изображения, хотя я не одобряю такой подход, так как экран становится слишком занятым, когда есть загружается несколько изображений). Мы выбираем загрузку и рендеринг всех плиток в ScrollView перед окончательным исчезновением, создавая таким образом плавный, плавный переход.

Я не собираюсь на самом деле программировать интерфейс REST и загружать данные удаленно, так как это не является целью этого руководства, и вместо этого я буду моделировать задержку загрузки данных с тайм-аутом. Мы также собираемся добавить обратный вызов в наш контроллер itemTile.js, чтобы мы могли информировать родительское представление о завершении загрузки.

itemTile.xml:
<Alloy>
  <View id=”itemTile”>
    //we use a padding frame to create a margin or padding around our image and text
    <View id=”paddingFrame” layout=”vertical”>
      <ImageView id=”imageView”/>
      <Label id=”textLabel”/>
    </View>
  </View>
</Alloy>

itemTile.js:
var onRenderedCallback = $.args.onRenderedCallback,
    textLoaded = false,
    imageLoaded = false;
//pick a random timeout value in a range to simulate
//varying load times for text and images
var timeout1Duration = 1000 * (1 + Math.random() * 3);
var timeout2Duration = 1000 * (1 + Math.random() * 1);
setTimeout(function() {
    $.imageView.image = $.args.imagePath || ‘’;
    imageLoaded = true;
    if (textLoaded && onRenderedCallback) {
        onRenderedCallback();
    }
}, timeout1Duration);
setTimeout(function() {
    $.textLabel.text = $.args.text || ‘’;
    textLoaded = true;
    if (imageLoaded && onRenderedCallback) {
        onRenderedCallback();
    }
}, timeout2Duration);
.....etc

Мы моделируем состояние гонки, когда загружается более одного фрагмента данных, и мы не знаем, какой из них финиширует первым. Это типично для асинхронных веб-вызовов. В этом простом примере весьма вероятно, что текст будет загружаться быстрее, чем изображение, но в более сложном примере у нас могут быть все виды данных и, возможно, даже более одного изображения для загрузки. Таким образом, из приведенного выше кода мы видим, что обратный вызов «onRenderedCallback» вызывается только тогда, когда обе части данных загружены. После этого мы можем обновить ScrollView в родительском представлении. Кроме того, мы добавляем счетчик загрузки (ActivityIndicator), чтобы покрыть загрузку и рендеринг плиток, и устанавливаем непрозрачность ScrollView на 0, готовый к исчезновению.

example.xml:
<Alloy>
  <Window id=”win”>
    <Label id=”hiddenLabel” visible=”false”/>
    <ScrollView id=”scrollView” layout=”horizontal” opacity=“0.0”/>
    <ActivityIndicator id=”activityIndicator” message=”Loading…”/
  </Window>
</Alloy>

example.js:
var Animation = require(‘alloy/animation’),
    numberOfTilesRendered = 0,
    FADE_DURATION = 500;
function onRenderedCallback() {
    numberOfTilesRendered++;
    if (numberOfTilesRendered === items.length) {
        fadeScrollViewOn();
        $.activityIndicator.hide();
    }
}
function fadeScrollViewOn() {
    Animation.fadeIn($.scrollView, FADE_DURATION, function () {
        $.scrollView.opacity = 1.0;
    });
}
.....etc

Напомним, что «элементы» — это наш массив данных, поэтому при обратном вызове из каждого тайла он проверяет, все ли тайлы вызвали ответный вызов, и если да, то скрывает индикатор загрузки (ActivityIndicator) и затухает в ScrollView с помощью стандартного пакета анимации Alloy (обратите внимание, что мы установите непрозрачность ScrollView на 1 в конце анимации, так как свойства могут вести себя неожиданным образом, если мы не делаем этого при их анимации).

Теперь на практике для изображения вы должны прослушивать событие «загрузка», которое запускается при загрузке удаленного изображения. Такие как:

itemTile.js:
$.imageView.addEventListener(‘load’, function(_evt) {
    imageLoaded = true;
    if (textLoaded && onRenderedCallback) {
        onRenderedCallback();
    }
});
$.imageView.image = $.args.imagePath || ‘’;

И, наконец, стоит упомянуть еще один более сложный метод мониторинга многочисленных условий гонки и загрузки данных с помощью наблюдателя переменных. Наблюдатель за переменными ищет изменения в значениях переменных и позволяет выполнять действия при их выполнении. Для этого я использовал некоторый код Яна Серлина в моем файле lib variableWatcher.js. Опять же, в таком простом примере, как наш, использование наблюдателя за переменными ничего не даст, но потенциально может быть полезно в более сложной ситуации с множеством одновременных асинхронных загрузок данных. В этом случае наш код:

itemTile.js
var VariableWatcher = require(‘variableWatcher’),
    //"loaded" is the object variable we are going to watch
    loaded = {
        image: false,
        text: false
    };
function checkIfReadyToRender() {
    if (loaded.image && loaded.text) {
        VariableWatcher.unwatch(loaded, ‘image’);
        VariableWatcher.unwatch(loaded, ‘text’);
        if (onRenderedCallback) {
            onRenderedCallback();
        }
    }
}
VariableWatcher.watch(loaded, ‘image’, checkIfReadyToRender);
VariableWatcher.watch(loaded, ‘text’, checkIfReadyToRender);
setTimeout(function() {
    $.imageView.image = $.args.imagePath || ‘’;
    loaded.image = true;
}, timeout1Duration);
setTimeout(function() {
    $.textLabel.text = $.args.text || ‘’;
    loaded.text = true;
}, timeout2Duration);

Вы можете увидеть полностью завершенный проект здесь на GitHub с 3 примерами различных методов, рассмотренных в частях 1 и 2 этого руководства.

Саймон Бэкингем — дизайнер, специалист по пользовательскому опыту, аниматор, креативный и технический директор и разработчик с более чем 20-летним опытом. Он занимается созданием кроссплатформенных мобильных приложений iOS/Android с помощью Titanium более 5 лет. Саймон руководит собственной компанией Icecandy Entertainment, базирующейся в Лондоне, Великобритания. Вы можете узнать больше о его цифровой карьере на сайте simonbuckingham.me.