Система макетирования Maqetta: Часть 3. Установка прототипа UI Maqetta с помощью PhoneGap

Использование утилиты PhoneGap для установки прототипа UI Maqetta на любое мобильное устройство

Из предыдущих статей этого цикла, знакомящих с инструментом Maqetta, читатель узнал, как создать и обогатить прототип интерактивного мобильного интерфейса пользователя (UI) с применением функций Dojo и Dojo Mobile. Работая с Maqetta в браузере, мы построили правдоподобный прототип, не написав ни одной строки кода, а затем расширили его функциональные возможности с помощью специального кода JavaScript. Теперь настало время объединить Maqetta с PhoneGap, чтобы собрать и установить новый прототип мобильного UI на реальных устройствах.

Тони Эрвин, инженер-программист, IBM

Фото автораТони Эрвин (Tony Erwin) — программист группы новых интернет-технологий IBM и один из основных членов группы разработки Maqetta. Работает в IBM с 1998 года и имеет большой опыт проектирования и разработки пользовательских интерфейсов с применением широкого спектра технологий и инструментов. До прихода в IBM получил ученую степень магистра информатики в университете штата Индиана и бакалавра информатики в технологическом институте Rose-Hulman.



12.08.2013

Введение

Об этом цикле статей

Этот цикл статей демонстрирует, как использовать систему Maqetta для прототипирования пользовательских интерфейсов HTML5.

  • В первой части речь шла об основных функциях Maqetta в процессе создания прототипа многофункционального мобильного приложения.
  • Во второй части мы перевели прототип приложения на следующий уровень, написав специальный код JavaScript для добавления интерактивных функций.
  • Теперь мы воспользуемся инструментом PhoneGap, чтобы превратить прототип, сгенерированный Maqetta, в настоящее мобильное приложение, готовое к установке на реальные устройства.

Подробнее о работе с Maqetta можно прочесть в блоге Тони на сайте developerWorks.

Сочетание Maqetta и PhoneGap обеспечивает мощную платформу для быстрого прототипирования, разработки и установки мобильных приложений. В последней статье этого цикла мы построим новый прототип приложения с нуля, доведя мобильное приложение Maqetta от реализации до установки. Примером для этой статьи будет служить мобильное приложение на базе GPS, которое показывает пользователю его текущее местоположение на карте. Сначала мы спроектируем прототип пользовательского интерфейса в редакторе страниц Maqetta без написания какого-либо кода (как в части 1). Затем добавим некоторый код JavaScript, дополнив прототип интерактивными функциями (как в части 2). После этого мы добавим новый уровень кода JavaScript, который позволит использовать функции API Geolocation из PhoneGap.

Когда прототип будет готов, мы проверим функцию определения местоположения с помощью расширения Ripple Emulator для Google Chrome и воспользуемся службой Adobe PhoneGap Build, чтобы превратить наш GPS-локатор, созданный с помощью Maqetta, в "родное" мобильное приложение. В заключение мы протестируем функциональность GPS-локатора с эмулятором Android, который входит в SDK Android.

Опора на приобретенные знания

Если вы следили за статьями этого цикла с самого начала, то создание и расширение прототип GPS-локатора пройдет гладко — и предоставит хорошую возможность закрепить уже приобретенные знания о Maqetta. Если же мобильное приложение в Maqetta уже есть, и нужно только собрать его с помощью PhoneGap и установить на мобильное устройство, можно сразу перейти к разделу Экспорт приложения в PhoneGap.


Проектирование прототипа GPS-локатора

На первом шаге мы создадим прототип UI GPS-локатора. Он позволит видеть на карте свое географическое местоположение, изменять стиль карты, определять текущую широту и долготу и изменять местоположение нажатием кнопки.

Начнем с создания нового проекта в Maqetta и HTML-файла, как мы это делали в части 1:

  1. Войдите на сервер Maqetta по адресу Maqetta.org. (Этот шаг предполагает, что вы зарегистрированы как пользователь на Web-сайте Maqetta).
  2. Создайте новый проект, выбрав пункт меню Create > Project.... Введите gpsLocator в качестве имени проекта и нажмите кнопку Create.
  3. Создайте новое мобильное приложение в новом проекте, выбрав из меню Create > Mobile Application.... Введите имя файла index.html (это имя требуется для PhoneGap) и нажмите кнопку Create.

Начнем с представления

Первым шагом по разработке пользовательского интерфейса будет создание представления; для данного приложения мы сделаем это с помощью виджета Dojo Mobile ScrollableView.

  1. Перетащите виджет ScrollableView из раздела Views палитры виджетов в центр силуэта устройства Maqetta. (В случае затруднений с поиском виджета можно ввести его имя в поле поиска в верхней части палитры виджетов.)
  2. В палитре свойств измените ID нового представления на mainView.

Добавление сегментированной панели вкладок

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

  1. Перетащите виджет TabBar из раздела Controls > Toolbars, ButtonBars палитры виджетов в верхнюю часть текущего представления.
  2. Измените значение в текстовом поле на Road, Satellite, Hybrid (Рисунок 1) и нажмите кнопку ОК.
    Рисунок 1. Создание виджета TabBar
    Создание виджета TabBar
  3. В разделе Widget палитры Properties измените атрибут barType на segmentedControl и fixed на top (Рисунок 2).
    Рисунок 2. Свойства TabBar
    Свойства TabBar
  4. Выберите первую кнопку панели TabBar, помеченную Road. На палитре Properties измените ID на roadButton и удалите текст из атрибутов icon1 и icon2 (Рисунок 3).
    Рисунок 3. Свойства TabBarButton
    Свойства TabBarButton
  5. Выберите вторую кнопку панели TabBar с меткой Satellite. На палитре Properties измените ID на satelliteButton и удалите текст из атрибутов icon1 и icon2.
  6. Выберите третью кнопку панели TabBar с меткой Hybrid. На палитре Properties измените ID на hybridButton и удалите текст из атрибутов icon1 и icon2.

После этих действий панель с вкладками должна выглядеть как на следующем рисунке (Рисунок 4).

Рисунок 4. Окончательный вид TabBar
Окончательный вид TabBar

Создание окна карты

Теперь мы настроим компонент заполнителя для карты нашего GPS-локатора. В конечном итоге карта будет демонстрировать пользователю его местонахождение.

  1. Перетащите виджет RoundRect из раздела Containers > Dojo палитры виджетов в верхнюю часть текущего представления под TabBar.
  2. Откройте раздел HTML палитры виджетов, отображающий несколько разделов с множеством элементов HTML5, которые Maqetta предоставляет дизайнерам (см. Рисунок 5).
    Рисунок 5. HTML-виджеты
    HTML-виджеты на палитре виджетов Maqetta
  3. Перетащите виджет <img> из подраздела Images, Media, Iframes в RoundRect, чтобы он стал дочерним элементом прямоугольника.
  4. В появившемся диалоговом окне Select a source (Выбрать источник) скопируйте и вставьте URL из Листинг 1 в поле File URL и нажмите кнопку ОК. Этот URL-адрес использует API Google Static Maps для извлечения карты, в центре которой находится штаб-квартира IBM.
    Листинг 1. URL-адрес карты (скопируйте и вставьте одной строкой)
    http://maps.googleapis.com/maps/api/staticmap?
    markers=41.108556,-73.720729&zoom=13&size=284x216
    &sensor=false&scale=2
  5. В разделе Layout палитры Properties измените width на 100% и height на 44%.
  6. Замените ID<img> на mapImg.

После этого изображение должно выглядеть как на следующем рисунке (Рисунок 6).

Рисунок 6. Изображение карты
Скриншот изображения карты

Добавление широты и долготы

Надо добавить еще один виджет для отображения текущих значений широты и долготы:

  1. Из раздела Lists палитры виджетов перетащите RoundRectList под RoundRect. Введите Latitude (Широта) в одной строке и Longitude (Долгота) в следующей (Рисунок 7), затем нажмите кнопку ОК.
    Рисунок 7. RoundRectList
    RoundRectList для широты и долготы
  2. Выберите первый элемент списка с меткой Latitude. В разделе виджетов на палитре свойств измените ID на latitudeListItem и rightText на "41.108556" (значение широты из URL-адреса, который мы указали на изображении карты). После этого элемент списка должен выглядеть как на рисунке (Рисунок 8).
    Рисунок 8. Элемент списка Latitude
    Элемент списка, соответствующий широте
  3. Теперь выберите второй элемент списка с меткой Longitude. Измените ID на longitudeListItem и rightText на "73.720729" (значение долготы из URL-адреса, который мы указали на изображении карты).

Обновление местоположения

Наконец, завершим представление, добавив кнопку, которая позволит обновлять местоположение на карте:

  1. Перетащите RoundRect из раздела Containers > Dojo палитры виджетов в представление под RoundRectList.
  2. Перетащите Button из раздела Controls > Buttons палитры виджетов в RoundRect. Наберите в текстовом поле "Update Location" (Обновление местоположения) (Рисунок 9) и нажмите кнопку ОК.
    Рисунок 9. Добавление кнопки обновления местоположения
    Добавление кнопки обновления местоположения
  3. На палитре свойств измените ID новой кнопки на updateLocationButton.

Предварительный просмотр прототипа

Мы создали красивый макет, не написав никакого кода. Давайте выполним его предварительный просмотр. Из частей 1 и 2 вы должны помнить, что для предварительного просмотра любого прототип Maqetta достаточно нажать на значок Preview in Browser (Просмотр в браузере) на панели инструментов Maqetta. Проделав это с GPS-локатором, вы увидите новую вкладку браузера, подобную той, что показана на следующем рисунке (Рисунок 10).

Рисунок 10. Отображение GPS-локатора
Отображение GPS-локатора

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

  • исходное положение (местоположение на карте) жестко закодировано и не имеет ничего общего с фактическим положение пользователя;
  • кнопки Road, Satellite и Hybrid не влияют на тип карты;
  • нажатие кнопки Update Location не влияют на широту и долготу; координаты, показанные на карте, остаются неизменными;
  • изменение ориентации силуэта устройства приводит к искажению карты. Причина в том, что URL-адрес, отправляемый в Google, имеет жесткий размер, а размеры элемента <img> изменяются в зависимости от ориентации устройства.

Как и во второй части этого цикла статей, чтобы устранить эти недостатки и улучшить интерактивность прототипа GPS-локатора, добавим некоторый специальный код JavaScript.


Обогащение приложения с помощью JavaScript

Надеюсь, что вы уже приобрели некоторый опыт (возможно, после части 2) работы с файлом прототипа Maqetta app.js, добавляя код JavaScript для улучшения интерактивности UI. В этом случае шаги, перечисленные в этом разделе, закрепят многое из того, что вы уже знаете. Следуйте инструкциям, копируя и вставляя отдельные фрагменты кода JavaScript, приведенные здесь, в свой собственный файл app.js. Многие из фрагментов кода представляют собой тестируемые расширения приложения, поэтому я буду предлагать выполнить предварительный просмотр внесенных изменений. Те же, кто предпочитает ускоренный курс, могут просто загрузить окончательный вариант моего app.js и заменить им свой собственный в рабочей области Maqetta.

Необходимые модули

Как и в части 2, начнем с указания модулей Dojo, которые нам понадобятся. Для этого замените свой app.js на код, приведенный в следующем листинге (Листинг 2). (Напомню, что для того чтобы открыть app.js, нужно дважды щелкнуть на нем в палитре файлов.)

Листинг 2. Указание модулей Dojo
/*
 * Этот файл предназначен для специальной логики JavaScript, которая может 
 * потребоваться для ваших HTML-файлов.
 * Этот файл JavaScript входит в Maqetta по умолчанию в составе HTML-страниц, 
 * созданных в Maqetta.
 * 
 */
require(["dojo/ready", 
        "dojo/dom", 
        "dojo/dom-style", 
        "dijit/registry", 
        "dojo/on", 
        "dojo/aspect"], 
function(ready, dom, domStyle, registry, on, aspect){
     ready(function(){
        // здесь должна быть логика, которая требует полной инициализации Dojo

     });
});

Приведенный выше код загружает следующие модули:

Ссылки на виджеты

Далее нам нужно получить ссылки на все виджеты, с которыми мы собираемся взаимодействовать в своем коде. Как показано в листинге (Листинг 3), мы будем использовать registry.byId для ссылки на виджеты Dojo и dom.byId для элемента DOM — который представляет собой элемент <img>, содержащий нашу карту. Мы также вставим блок if, чтобы гарантировать, что все виджеты будут найдены. Если найдены не все виджеты, мы получим сообщение об этом, что облегчит процесс выявления и исправления ошибок.

Листинг 3. Ссылки на виджеты
        /* *******************************************
         * Получение ссылок на все необходимые виджеты и элементы DOM
         *
         *********************************************/
        var mainView = registry.byId("mainView");
        var roadButton = registry.byId("roadButton");
        var satelliteButton = registry.byId("satelliteButton");
        var hybridButton = registry.byId("hybridButton");
        var updateLocationButton = registry.byId("updateLocationButton"); 
        var latitudeListItem = registry.byId("latitudeListItem");
        var longitudeListItem = registry.byId("longitudeListItem");
        var mapImg = dom.byId("mapImg");

        // Проверка того, что мы нашли все виджеты
        if (!mainView ||
            !roadButton || 
            !satelliteButton || 
            !hybridButton || 
            !updateLocationButton || 
            !latitudeListItem || 
            !longitudeListItem || 
            !mapImg) {

            // Вывод сообщения об ошибке для определения того,
            // какие виджеты не удалось найти
            alert("could not find at least one of the widgets:\n" + 
                "\t mainView = " +  mainView + ",\n" + 
                "\t roadButton = " +  roadButton + ",\n" + 
                "\t satelliteButton = " +  satelliteButton + ",\n" +
                "\t hybridButton = " +  hybridButton + ",\n" +
                "\t updateLocationButton = " +  updateLocationButton + ",\n" +
                "\t latitudeListItem = " +  latitudeListItem + ",\n" +
                "\t longitudeListItem = " +  longitudeListItem + ",\n" +
                "\t mapImg = " +  mapImg);

            // Возврат, чтобы не запускать другие модули JavaScript
            return;
        }

Пойдем дальше и скопируем этот код прямо в функцию ready в файле app.js. Затем можно сохранить файл и выполнить предварительный просмотр приложения, чтобы убедиться, что все виджеты найдены.

Добавление заполнителя карты

Далее, в Листинг 4 определяем переменную с именем mapData. Она будет играть роль заполнителя для данных карты, необходимых для составления URL-адреса, который будет использоваться для получения карты. Сначала поместим в этот заполнитель данные по умолчанию, но по мере взаимодействия пользователя с приложением будем изменять эти данные и использовать их для заполнения виджетов на странице. Вы можете заметить, что параметрам latitude и longitude присвоено значение undefined (не определено), потому что мы пока не знаем начального местоположения пользователя. Добавьте этот код в свой файл app.js; отметим только, что поведение прототипа в окне предварительного просмотра не изменится.

Листинг 4. Данные карты
        /* ****************************************************
         * Инициализация заполнителя для всех данных карты,
         * которые мы хотим передать в Google.
         ******************************************************/
        var mapData = {
            latitude: undefined,
            longitude: undefined,
            zoom: 13,
            width: 284, 
            height: 216, 
            sensor: false,
            scale: 2,
            mapType: "roadmap"
        }

Составление URL-адреса карты

Далее определим функцию с именем getMapUrl, которая создает URL с помощью API Google Static Maps (Листинг 5). Функция getMapUrl использует mapData, чтобы заполнить требуемые параметры для данного URL-адреса. Перед составлением URL-адреса выполняется проверка, заданы ли значения широты и долготы пользователя. Если нет, то задаются координаты по умолчанию 0, 0 (с уровнем увеличения 1), так что мы составим URL-адрес для получения уменьшенной карты мира. Это укажет пользователю, что мы еще не знаем его фактического местоположения.

Листинг 5. Получение URL карты
        /* ****************************************************
         * Функция создания url карты Google, которую мы хотим получить.
         * Она заполняет параметры URL-адреса данными из нашей переменной mapData.
         * 
         ******************************************************/
        var getMapUrl = function() {
            // Если нет действительных значений широты и долготы, 
            // то настроим параметры так, чтобы получить карту мира с центром 0, 0
            // и уровнем увеличения 1.
            var latitude = mapData.latitude ? mapData.latitude : 0;
            var longitude = mapData.longitude ? mapData.longitude : 0;
            var zoom = mapData.latitude ? mapData.zoom : 1;

            // Составление URL-адреса
            var url = 
                "http://maps.googleapis.com/maps/api/staticmap?" + 
                "markers=" + latitude + "," + longitude + "&" +
                "zoom=" + zoom + "&" +
                "size=" + mapData.width + "x" + mapData.height + "&" + 
                "sensor=" + mapData.sensor + "&" +
                "scale=" + mapData.scale + "&" +
                "maptype=" + mapData.mapType;  

            return url;
        };

Мы пока не вызываем эту функцию, так что ее добавление в app.js никак не повлияет на поведение прототипа.

Обновление виджетов

Далее, поместим в файл функцию с именем updateWidgets (Листинг 6). Она обновляет содержимое виджетов на странице. Сначала она использует getMapUrl и записывает результат в srcmapImg. Затем обновляет значения широты latitudeListItem и долготы longitudeListItem в mapData. Если широта и долгота еще не заданы, в каждом из элементов списка будет отображаться заполнитель (--).

Листинг 6. Функция обновления виджетов
        /* ****************************************************
         * Функция обновления виджетов на странице на основе текущих значений mapData.
         * 
         ******************************************************/
        var updateWidgets = function() {
            //Получение URL-адреса карты и обновление изображения
            var url = getMapUrl();
            mapImg.src = url;

            //Обновление широты на дисплее
            var latitudeString = 
                    mapData.latitude ? mapData.latitude.toFixed(6) : "--";
            latitudeListItem.set("rightText", latitudeString);

            //Обновление долготы на дисплее
            var longitudeString = 
                    mapData.longitude ? mapData.longitude.toFixed(6) : "--";
            longitudeListItem.set("rightText", longitudeString);
        };

Добавьте этот фрагмент кода в app.js, но опять же, никаких изменений в поведении при предварительном просмотре заметно не будет.

Обработчики кнопок выбора типа карты

Один из недостатков прототипа заключался в том, что при нажатии кнопок Road, Satellite или Hybrid ничего не происходит. Изменения, уже внесенные в код JavaScript прототипа, позволяют нам легко решить эту проблему. Зарегистрируем функцию, которая будет вызываться при нажатии любой из трех кнопок (Листинг 7). Затем в каждом обработчике настроим mapType в mapData на соответствующее значение. Наконец, вызовем функцию updateWidgets, описанную в предыдущем разделе.

Листинг 7. Обработчики кнопок
        /* ****************************************************
         * Обработка нажатия кнопок на сегментированной панели вкладок
         ******************************************************/
        // Изменение mapType и обновление виджетов при нажатии кнопки roadButton
        on(roadButton, "click", function() {
            mapData.mapType = "roadmap";
            updateWidgets();
        });

        // Изменение mapType и обновление виджетов при нажатии кнопки satelliteButton
        on(satelliteButton, "click", function() {
            mapData.mapType = "satellite";
            updateWidgets();
        });

        // Изменение mapType и обновление виджетов при нажатии кнопки hybridButton
        on(hybridButton, "click", function() {
            mapData.mapType = "hybrid";
            updateWidgets();
        });

После добавления этого кода (Листинг 7) в app.js должно быть отчетливо видно изменение поведения прототипа. Начальный экран по-прежнему будет содержать карту корпоративной штаб-квартиры IBM (вскоре мы это исправим). Однако при нажатии любой из трех кнопок будет составлен новый URL-адрес карты с использованием mapType для нажатой кнопки. Поскольку мы не настроили значения широты и долготы пользователя, URL-адрес будет автоматически извлекать уменьшенное изображение глобуса. Например, Рисунок 11 демонстрирует карту мира после нажатия кнопки Satellite. Также отметим, что элементы списка latitude и longitude содержат заполнитель (--).

Рисунок 11. Изменение типа карты
Изменение типа карты для трех представлений

Изменение размеров изображения

Еще один недостаток прототипа заключался в искажении изображения карты при изменении размеров mapImg (например, когда меняется тип или ориентация устройства). Это происходит потому, что URL-адрес, используемый для извлечения карты, требует указания высоты и ширины изображения в пикселах. Конечно, нам не известны эти значения для всех устройств и ориентаций, так что же делать?

Листинг 8 демонстрирует решение с использованием вызова функции aspect.after после выполнения функции resize из mainView. В указанной функции используется domStyle.get для вычисления ширины и высоты mapImg с возвратом обновленных значений в mapData. Затем вызывается метод updateWidgets, который в конечном итоге приводит к составлению нового URL и его использованию для mapImg.

Листинг 8. Обработка событий изменения размера
        /* ****************************************************
         * Будем отслеживать изменение размеров нашего mainView
         * (которые могут произойти, например, при изменении ориентации).
         * Это позволит получить новые размеры
         * mapImg и сделать новый запрос
         * правильных размеров карты.
         ******************************************************/
        aspect.after(mainView, "resize", function() {
            // Обновление данных карты в соответствии с фактическими значениями ширины 
            // и высоты mapImg
            mapData.width = Math.round(domStyle.get(mapImg, "width")); 
            mapData.height = Math.round(domStyle.get(mapImg, "height"));

            // В mapData произошли изменения, поэтому обновляем виджеты
            updateWidgets();
        });

Теперь обновим app.js и выполним предварительный просмотр приложения. Поскольку первоначальное изменение размеров происходит заранее, updateWidgets будет вызываться сразу, и мы увидим карту мира немедленно. Если изменить ориентацию устройства, изображения карты перезагрузится с правильными размерами, что исключит искажение. Однако заметим, что после изменения ориентации для того чтобы увидеть кнопку Update, нужно выполнить прокрутку экрана. Это можно исправить путем настройки высоты mapImg в обработчике изменения размеров, чтобы все помещалось без прокрутки.

Рисунок 12. Более гладкое изменение ориентации
Перезагрузка изображения карты без искажений

Итак, мы обновили свой прототип с помощью специального кода JavaScript, чтобы сделать некоторые из его ключевых особенностей более отзывчивыми на действия пользователя. Пока все, что мы сделали, отражает те знания, которые вы, вероятно, уже почерпнули из первой и второй статей этого цикла. В следующем разделе мы начнем включать новые функциональные возможности, которые позволят нам работать с API PhoneGap.


PhoneGap и Maqetta

Важной особенностью GPS-локатора является способность обновлять местоположение, отображаемое на карте, в зависимости от физического местоположения пользователя. В конечном итоге мы хотим превратить этот прототип в "родное" мобильное приложение, построенное с помощью PhoneGap. Когда приложение будет выполняться непосредственно на устройстве, у нас будет доступ ко всем API PhoneGap. Эти API позволяют обращаться к собственным службам мобильных устройств (таким как определение местоположения, акселерометр и видеокамера) через общий уровень JavaScript. Мы будем использовать API Geolocation PhoneGap, который соответствует спецификации W3C Geolocation API. Эта спецификация реализована во всех настольных браузерах, поддерживаемых Maqetta — Chrome, Firefox и Safari. В результате, когда мы напишем свой код, мы сможем обращаться к физическому местоположению пользователя в окне предварительного просмотра Maqetta, даже несмотря на отсутствие PhoneGap.

С учетом вышесказанного давайте обратимся к функции updateCurrentPosition (Листинг 9). Первое, что мы сделаем, ― отключим кнопку Update Location. Потому что получение данных о местоположении устройства через GPS иногда занимает некоторое время, и нам не нужно, чтобы пользователь многократно нажимал эту кнопку. Затем вызовем navigator.geolocation.getCurrentPosition из API Geolocation. Эта функция работает асинхронно, поэтому мы должны передать ей функцию, которая будет вызываться после определения местоположения. Это функция onGetPositionSuccess (мы передадим также функцию onGetPositionError, которая будет вызываться в случае ошибки).

При вызове onGetPositionSuccess она получает объект Position со всеми видами информации о местоположении пользователя ― помимо широты и долготы, он также содержит сведения о высоте над уровнем моря, направлении, скорости и многом другом. Мы просто обновляем переменные latitude и longitude из mapData, а затем вызываем функцию updateWidgets для обновления изображения. Мы также восстанавливаем кнопку Update Location, потому что теперь пользователь может делать новый запрос.

У нас (пока) нет никакого кода, который вызывал бы updateCurrentPosition. Поэтому после обновления app.js приведенным ниже кодом в поведение приложения при предварительном просмотре никаких изменений заметно не будет.

Листинг 9. Получение положения карты
        /* ****************************************************
         * Функция Обратного вызова, когда GPS успешно получен от PhoneGap API
         * (см. функцию updateCurrentPosition ниже).
         * Она принимает объект Position, который содержит
         * текущие координаты GPS /
         ******************************************************/
        var onGetPositionSuccess = function(position) {
            mapData.latitude = position.coords.latitude;
            mapData.longitude = position.coords.longitude;

            // В mapData произошли изменения, поэтому обновляем виджеты
            updateWidgets();

            // Восстановление updateLocationButton
            updateLocationButton.set("disabled", false);
        };

        /* ****************************************************
         * Функция обратного вызова в случае ошибки получения данных GPS с 
         * использованием API PhoneGap (см. функцию updateCurrentPosition ниже).
         * Она принимает объект PositionError, содержащий
         * информацию об ошибке.
         ******************************************************/
        var onGetPositionError = function(error) {
            //Вывод сообщения об ошибке
            alert('geolocation failure! code: ' + error.code    + '\n' +
                   'message: ' + error.message + '\n');

            // Восстановление updateLocationButton
            updateLocationButton.set("disabled", false);
        }

        /* ****************************************************
         * Функция, используемая для получения нового положения GPS в зависимости от 
         * среды, в которой мы работаем (PhoneGap или нет).
         * После получения GPS-положения происходит обновление виджетов.
         *
         ******************************************************/
        var updateCurrentPosition = function() {
            if(navigator.geolocation && navigator.geolocation.getCurrentPosition){
                // Отключение кнопки обновления местоположения на время получения данных
                updateLocationButton.set("disabled", true);

                // Службы геолокации доступны для запроса местоположения.
                // API геопозиционирования работает асинхронно, так что 
                // мы должны полагаться на функцию обратного вызова.
                navigator.geolocation.getCurrentPosition(
                    onGetPositionSuccess, //вызыватся при получении GPS-местоположения
                    onGetPositionError, //вызывается, если местоположение не определено
                    { enableHighAccuracy: true }); //специальное добавление для Android
            } else {
                // У нас нет служб геолокации, поэтому выдаем предупредительное сообщение
                alert("Geolocation services are not available!");
            }
        };

Обработчик кнопки обновления местоположения

Теперь мы можем довольно легко реагировать на нажатие кнопки Update Location. В следующем листинге (Листинг 10) мы регистрируем обработчик, который просто вызывает функцию updateCurrentPosition из предыдущего раздела.

Листинг 10. Обработка нажатия кнопки обновления местоположения
        /* ****************************************************
         * Обновление текущего положения при нажатии кнопки updateLocationButton
         * 
         ******************************************************/
        on(updateLocationButton, "click", function() {
            updateCurrentPosition();
        });

Теперь можно обновить app.js и выполнить предварительный просмотр приложения. Если нажать кнопку Update Location, то она станет серой, и браузер спросит, разрешаете ли вы приложению обращаться к информации о вашем местоположении.

Рисунок 13. Запрос браузера на обновление местоположения
Запрос браузера на обновление местоположения

Затем, если вы это разрешили, выполняется асинхронный вызов функции обратного вызова onGetPositionSuccess, что приводит к обновлению информации о местоположении на дисплее (см. Рисунок 14). Кроме того, снова включается кнопка Update Location.

Рисунок 14. Обновленное местоположение в Maqetta Preview
Обновленное местоположение в Maqetta Preview

Инициализация устройства

Мы реализовали большую часть функциональности прототипа. Единственная важная вещь, которую мы не сделали, ― это возможность инициализировать положение пользователя без необходимости нажатия кнопки Update Location. Чтобы получать текущее местоположение пользователя, нужно вызвать API PhoneGap. Но прежде чем мы сможем безопасно вызывать API PhoneGap, нужно знать, когда устройство будет «готово». Мы хотим запускать приложение на мобильных устройствах, но мы также хотим продолжать работать с функцией предварительного просмотра Maqetta для упрощения проектирования и прототипирования.

По опыту работы с Maqetta Previewer нам известно, что среда браузера готова по умолчанию, и мы сразу можем вызвать API Geolocation. Однако в среде PhoneGap нужно контролировать специальное событие, которое сигнализирует о готовности устройства. Поэтому в оставшейся части раздела мы напишем код JavaScript, который позволит нам делать следующее:

  1. Проверять, присутствует ли PhoneGap.
  2. Использовать этот результат для проверки готовности устройства.
  3. Как только устройство будет готово, инициализировать определение местоположения пользователя.

Готовность устройства и инициализация

Сначала определим две функции (Листинг 11), которые мы будем использовать для облегчения инициализации. Первая, beforeDeviceReady, устанавливает виджеты и отключает кнопку Update Location. Мы будем вызывать ее в качестве первого шага до того , как устройство будет готово. Вторую функцию, onDeviceReady, мы будем вызывать сразу же после того , как определим, что устройство готово. Затем функция onDeviceReady вызовет updateCurrentPosition для автоматического получения местоположения от имени пользователя. Добавьте эти функции в свой файл app.js, но никаких изменений в при предварительном просмотре пока не произойдет.

Листинг 11. Функции инициализации
        /* ****************************************************
         * Функция для выполнения некоторой инициализации до готовности устройства.
         * 
         ******************************************************/
        var beforeDeviceReady = function() {
            // Обновление виджетов значениями по умолчанию
            updateWidgets();

            // Отключение updateLocationButton до готовности устройства
            updateLocationButton.set("disabled", true);
        }

        /* ****************************************************
         * Функция, которая вызывается, когда устройство готово, и
         * мы смело можем попытаться получить местоположение и т.д.
         ******************************************************/
        var onDeviceReady = function() {
            // Получение начального положения
            updateCurrentPosition();
        }

Далее, определим функцию isPhoneGap, которая поможет нам проверить, находимся ли мы в среде PhoneGap (см. Листинг 12). Зная, в какой среде мы находимся, мы сможем определить, как лучше всего проверить готовность. К сожалению, общепринятого способа проверки среды не существует, а наиболее надежные подходы довольно сложны. Однако для нашего прототипа должна достаточно хорошо работать реализация, приведенная ниже. Добавьте следующую функцию в файл app.js, но отмечаем, что это не приведет ни к каким изменениям во время предварительного просмотра до тех пор, пока мы не добавим следующий фрагмент.

Листинг 12. Проверка на PhoneGap
        /* ****************************************************
         * Функция, используемая для определения, находимся ли мы в среде PhoneGap.
         * К сожалению, общепринятого лучшего способа сделать это не существует
         * 
         * (см., например, следующее обсуждение на StackOverflow: 
         * http://bit.ly/Wvmmcw ). И наша ситуация осложняется желанием работать 
         * в Ripple на настольном ПК.
         * Поэтому я думаю, что разумным компромиссом для прототипа будет следующий код.
         * 
         * 
         ******************************************************/
        var isPhoneGap = function() {
            var result = 
              // Проверка работы PhoneGap/Cordova в Ripple, 
              // но не надежная до события deviceready
              window.PhoneGap || 
              window.Cordova || 
              // Проверка браузера надежнее, но если перейти по URL предварительного 
              // просмотра из мобильного браузера, то этот тест пройдет, и будет  
              // казаться, что вы хотите использовать PhoneGap.
              navigator.userAgent.match(/(iPhone|iPod|iPad|Android|BlackBerry)/);

              return result;
        }

Функции из последних листингов приводятся в действие в следующем листинге (Листинг 13). Сначала мы вызываем beforeDeviceReady в качестве шага инициализации. Затем, если используется PhoneGap, регистрируем прослушиватель события deviceready PhoneGap. Когда событие произойдет, это покажет, что можно безопасно вызвать API PhoneGap, и будет вызвана функция onDeviceReady. Если мы не находимся в PhoneGap (например, когда мы в Maqetta Previewer), устройство «готово» по умолчанию, поэтому мы сразу вызываем onDeviceReady.

Листинг 13. Инициализация страницы
        /* ****************************************************
         * Начальная загрузка - вызов beforeDeviceReady с 
         * последующим вызовом onDeviceReady 
         * в зависимости от среды, в которой мы находимся.
         ******************************************************/
        beforeDeviceReady();
        if (isPhoneGap()) {
            // Мы работаем в PhoneGap, поэтому регистрируемся на deviceready. 
            document.addEventListener("deviceready", onDeviceReady, false);
        } else {
            // Мы находимся в настольном браузере, поэтому наше устройство 
            // «готово» автоматически.
            onDeviceReady(); 
        }

После добавления в app.js кода Листинг 13 приложение должно показывать ваше текущее местоположение. Однако вы можете спросить, как протестировать ветвь PhoneGap и функциональность Update Location? Ответом на оба эти вопроса станет Ripple Emulator, описанный в следующем разделе.


Ripple Emulator

Ripple Emulator (на момент написания статьи существует в бета-версии) — это расширение для Google Chrome, которое создает среду браузера, эмулирующую подмножество API PhoneGap. Мы будем использовать Ripple Emulator для тестирования вызов API Geolocation, а также при использовании приложением события deviceready.

Настройте Ripple Emulator следующим образом:

  1. В Google Chrome перейдите по адресу http://emulate.phonegap.com/.
  2. Если вы еще не работали с Ripple, вам будет предложено установить Ripple Emulator (см. Рисунок 14). Нажмите кнопку Get Ripple Emulator.
    Рисунок 15. Установка Ripple Emulator
    Установка Ripple Emulator
  3. Вы перейдете в интернет-магазин Chrome, где нужно нажать кнопку Add to Chrome.
  4. Перезагрузите http://emulate.phonegap.com/, и вы увидите форму для ввода эмулируемого URL-адреса (Рисунок 16).
    Рисунок 16. Ripple установлен (ввод URL-адреса)
    Онлайн форма для ввода эмулируемого URL-адреса

Проверка GPS-локатора

Когда Ripple Emulator запущен, мы можем протестировать приложение:

  1. Введите URL-адрес GPS-локатора и нажмите клавишу Return. Чтобы получить URL-адрес, скопируйте и вставьте ссылку рядом с надписью Previewing в области предварительного просмотра Maqetta. Он должен быть подобен следующему, но будет варьироваться в зависимости от вашего личного идентификатора: http://app.maqetta.org/maqetta/user/XYZ/ws/workspace/gpsLocator/index.html.
  2. После запуска Ripple вы должны увидеть GPS-локатор, работающий на имитируемом мобильном устройстве, и карту с центром в Ватерлоо (провинция Онтарио, Канада) (см. Рисунок 17). Поскольку Ватерлоо ― местоположение по умолчанию, предлагаемое Ripple Emulator, это означает, что функция deviceready сработала, и наш код из updateCurrentPosition успешно вызвал функцию PhoneGap navigator.geolocation.getCurrentPosition.
    Рисунок 17. Первый прогон GPS-локатора в Ripple
    Первый прогон GPS-локатора в Ripple
  3. Чтобы изменить устройство, используемое для предварительного просмотра, откройте раздел Devices в левом верхнем углу окна Ripple. По умолчанию установлено значение Generic - WVGA (480x800). Измените его на iPhone 4/4S (Рисунок 18).
    Рисунок 18. Изменение выбора устройства в Ripple
    Изменение выбора устройства в Ripple
  4. Теперь размер изображения устройства в Ripple должен быть точно таким, как в режиме предварительного просмотра Maqetta (см. Рисунок 19).
    Рисунок 19. GPS-локатор для iPhone в Ripple
    GPS-локатор для iPhone в Ripple
  5. Откройте раздел Geo Location в правой части окна Ripple. Вы увидите подробную информацию о местоположении устройства, предоставляемую API PhoneGap (Рисунок 20). Обратите внимание, что в центре карты на прототипе находится то же место, что и на вкладке Geo Location Ripple. Значения, которые приложение дает для широты и долготы, также совпадают.
    Рисунок 20. Вкладка Location в Ripple
    Вкладка Location в Ripple
  6. Поэкспериментируйте с параметрами настройки на вкладке Geo Location. Можно переместить карту или ввести явные значения широты и долготы. После обновления нажмите кнопку Update Location приложения, чтобы увидеть, что произойдет. Местоположение изменится на координаты штаб-квартиры IBM в Рочестере, штат Миннесота (широта = 44.058633, долгота =-92.507393) (Рисунок 21).
    Рисунок 21. Обновленное местоположение из Ripple Emulator
    Обновленное местоположение из Ripple Emulator

Экспорт приложения в PhoneGap

Мы увидели, что приложение работает (включая использование им API PhoneGap), и теперь можем подготовиться к его построению с помощью службы Adobe PhoneGap Build. Начнем с экспорта проекта из Maqetta, но перед этим, нужно проверить следующее (эти шаги могут служить контрольным списком для экспорта будущих проектов Maqetta в PhoneGap).

  1. Убедитесь, что главный HTML-файл имеет имя index.html и расположен в корневом каталоге вашего проекта. Это имя файла обязательно для любого проекта Maqetta, который вы хотите построить с помощью PhoneGap.
  2. Если вы используете API PhoneGap (как мы делаем для GPS-локатора), то должны загрузить phonegap.js из своего файла index.html. Он войдет в JavaScript, необходимый для API PhoneGap. Сначала просмотрите исходный HTML (вспомните о кнопке Source на панели инструментов Maqetta), затем добавьте строку в Листинг 14 прямо над строкой, загружающей app.js (это должна быть примерно строка 26). Затем сохраните HTML-файл.
    Листинг 14. Загрузка phonegap.js из index.html
    <script stype="text/javascript" src="phonegap.js"></script>
  3. Обратите внимание, что хотя мы загружаем phonegap.js, ресурсы PhoneGap JavaScript находятся не в проекте Maqetta (и не должны там находиться). Но они будут включены в набор ресурсов, укомплектованный с вашим "родным" приложением после его создания с помощью PhoneGap.
  4. Сохраните index.html и вернитесь в режим Design, нажав кнопку Design на панели инструментов Maqetta.

Экспорт архива проекта из Maqetta

Далее, мы загрузим проект из Maqetta в виде zip-файла, который затем передадим в службу построения PhoneGap:

  1. В палитре Maqetta Files щелкните на значке Download Entire Workspace на панели инструментов, чтобы открыть диалоговое окно загрузки (см. Рисунок 22). Имя zip-файла основывается на имени проекта.
  2. Откажитесь от библиотек gridx, clipart и shapes, так как мы их не используем. Это уменьшит размер zip-файла.
    Рисунок 22. Загрузка проекта из Maqetta
    Загрузка проекта из Maqetta
  3. Нажмите кнопку Download, что приведет к загрузке zip-файла с ресурсами проекта в файловую систему.

Построение прототипа с помощью PhoneGap

Теперь мы готовы приступить к созданию прототипа в качестве "родного" приложения с использованием службы Adobe PhoneGap Build. Начнем с перехода по адресу: https://build.phonegap.com/. В предположении, что вы еще никогда не заходили на PhoneGap:

  1. Нажмите кнопку Get started!.
  2. Если вы хотите только попробовать, без оплаты, нажмите Completely Free, что позволит вам построить и управлять не более чем одним собственным приложением за раз им.
  3. Войдите в систему с идентификатором Adobe ID или своими учетными данными GitHub.
  4. После входа в систему вы увидите экран, как на следующем рисунке (Рисунок 23). Убедитесь, что выбрана вкладка Private, и нажмите кнопку Upload a zip file.
    Рисунок 23. Построитель PhoneGap
    Построитель PhoneGap
  5. Используя появившееся диалоговое окно выбора файлов, выберите zip-файл, который вы загрузили из Maqetta (например, gpsLocator.zip).
  6. После отправки файла вы увидите страницу (Рисунок 24) с предложением ввести некоторую информацию о своем приложении и построить его. Введите имя приложения, например, Maqetta Locator. Можно также ввести описание, такое как "Образец прототипа GPS-локатора в Maqetta". Учитывая, что мы работаем в режиме прототипа, хорошая идея ― установить флажок Enable debugging (включить режим отладки) (работа с отладчиком выходит за рамки этой статьи).
    Рисунок 24. Панель приложения в PhoneGap
    Панель приложения в PhoneGap
  7. Нажмите кнопку Ready to build, и служба Adobe PhoneGap Build начнет процесс создания "родных" приложений для различных платформ.
  8. После того как процесс завершится, на панели вашего приложения появится ряд кнопок (по одной для каждого устройства), позволяющих загружать результаты (см. Рисунок 25). Красные кнопки указывают на то, что сборка для данного устройства не была завершена успешно. Можно нажать такую красную кнопку, чтобы получить дополнительную информацию. Обратите внимание, что неудачей закончилось построение как для iOS (потому что мы не представили ключ разработчика), так и для Blackberry (потому что слишком много файлов). Редактирование приложения для этих устройств выходит за рамки данной статьи.
    Рисунок 25. Сборка завершена
    Сборка завершена
  9. Если изменить приложение в Maqetta, можно загрузить новый zip-файл, а затем нажать кнопку Update code в PhoneGap, чтобы запустить новый процесс сборки.

Эмулятор Android

Одним из способов проверки результата работы PhoneGap без обращения к физическому устройству является использование эмулятора Android, который входит в SDK Android. Если вы хотите сделать это самостоятельно, выполните следующие действия для создания виртуального Android-устройства, на которое вы установите свое приложение:

  1. Следуйте инструкциям по загрузке и установке пакета Android Developer Tools (ADT) на свою систему. В него входят SDK Android, инструменты Android Platform и версия IDE Eclipse с плагином ADT.
  2. После установки пакета ADT запустите IDE Eclipse, входящую в пакет.
  3. В Eclipse выберите пункт меню Window > Android Virtual Device Manager.
  4. В появившемся диалоговом окне нажмите кнопку New... для создания нового устройства.
  5. В диалоговом окне Create (см. Рисунок 26) дайте устройству имя (например, myVirtualDevice), выберите устройство (я выбрал Nexus S), настройте целевую платформу на Android 4.2 и нажмите кнопку ОК.
    Рисунок 26. Создание виртуального устройства
    Диалоговое окно для создания нового виртуального устройства
  6. Диалоговое окно исчезнет, и в таблице виртуальных устройств Android появится новое устройство. Выберите его и нажмите кнопку Start.... В следующем диалоговом окне нажмите кнопку Launch. Запустится Android Emulator.

Установка приложения

При работающем Android Emulator можно установить и протестировать приложение:

  1. На странице сборки PhoneGap нажмите кнопку, чтобы загрузить приложение для Android. Это позволит сохранить в файловой системе файл.apk с именем типа MaqettaLocator-debug.apk.
  2. Имея файл .apk, можно установить его на своем виртуальном устройстве с помощью команды Android Debug Bridge (adb) из командной строки. Команда будет выглядеть примерно так:
    <sdk>/platform-tools/adb install <path_to_apk>/MaqettaLocator-debug.apk

    Если эта команда выполнена успешно, вы получите сообщение "Success" на консоли и увидите приложение в виде значка рядом со всеми остальными приложениями, установленными на эмуляторе (обратите внимание на значок приложения Maqetta Locator ― Рисунок 27).
  3. Чтобы запустить приложение, просто дважды щелкните на нем.
    Рисунок 27. Приложение на экране запуска PhoneGap Android Emulator
    Приложение на экране запуска PhoneGap Android Emulator

Задание местоположения в Android Emulator

При запуске приложения в Android Emulator появляется уменьшенная карта мира; это потому, что в эмуляторе не задано никакое первоначальное местоположение (Рисунок 28).

Рисунок 28. GPS-локатор в Android Emulator перед заданием местоположения
GPS-локатор в Android Emulator перед заданием местоположения

Чтобы изменить это, нужно задать географическое местоположение, известное Android Emulator:

  1. Из командной строки подключитесь к Emulator Console с помощью следующей команды:
    telnet localhost 5554

    Если вы работаете с Windows, помните, что telnet, скорее всего, по умолчанию не включен.
  2. Установите широту и долготу, используя команду geo. Например, чтобы задать координаты Бруклинского моста, потребуется команда:
    geo fix -73.99665 40.705921

После установки широты и долготы вы должны увидеть карту, в центре которой находится Бруклинский мост, в противном случае нужно будет нажать кнопку Update Location. Рисунок 29 демонстрирует приложение, работающее в эмуляторе, после обновления местоположения (и нажатия кнопки Hybrid для изменения типа карты).

Рисунок 29. Локатор после задания местоположения
Локатор после задания местоположения

Теперь вы убедились, что GPS-локатор работает как "родное" Android-приложение!


Пересмотр программы для отслеживания веса

Шаги, которые мы выполнили для экспорта GPS-локатора из Maqetta, его сборки с помощью PhoneGap и установки на эмулятор мобильного устройства, можно повторить с любым прототипом мобильного приложения Maqetta. Например, Рисунок 30 демонстрирует работу прототипа приложения для отслеживания веса, который мы разработали в первой и второй статьях, в эмуляторе Android после сборки с помощью PhoneGap.

Рисунок 30. Программа для отслеживания веса в Android Emulator
Программа для отслеживания веса в Android Emulator

Заключение

В этом цикле из трех статей мы использовали Maqetta для быстрого создания прототипов двух мобильных приложений. В процессе нам удалось охватить много тем, и мы надеемся, что это станет хорошим введением в Maqetta. Создание обоих приложений мы начали с разумного прототипа, который не требует написания кода, а затем расширили его интерактивные возможности, добавив некоторый специальный код JavaScript. В этой последней статье мы объединили Maqetta с PhoneGap и превратили прототип приложения GPS-локатора в "родное" приложение, которое затем испытали на эмуляторе Android.

Чтобы больше узнать о Maqetta, начните со ссылок в разделе Ресурсы, а также читайте блог Тони. Также имейте в виду, что в качестве руководства по разработке будущих мобильных приложений Maqetta и их установки с помощью PhoneGap можно использовать инструкции из этой статьи.


Загрузка

ОписаниеИмяРазмер
Окончательный исходный код специального приложенияmaqetta_part3.zip5 КБ

Ресурсы

Научиться

Получить продукты и технологии

  • Загрузить Maqetta: установите среду Maqetta на сервер в своей интрасети, загрузив ее со страницы загрузок, или запустите Maqetta в облаке.
  • Загрузить PhoneGap: бесплатная и открытая среда, которая облегчает создание мобильных приложений с помощью стандартизированных Web-API.
  • Получить Ripple Emulator: расширение для Google Chrome, предоставляющее среду браузера, в которой эмулируются подмножество API PhoneGap.
  • Загрузить Android ADT Bundle: в него входят SDK Android, инструменты Android Platform и версия IDE Eclipse с плагином ADT.

Обсудить

  • Присоединяйтесь к группе пользователей Maqetta: общайтесь с другими дизайнерами и разработчиками, использующими Maqetta для создания настольных и мобильных UI.

Комментарии

developerWorks: Войти

Обязательные поля отмечены звездочкой (*).


Нужен IBM ID?
Забыли Ваш IBM ID?


Забыли Ваш пароль?
Изменить пароль

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Профиль создается, когда вы первый раз заходите в developerWorks. Информация в вашем профиле (имя, страна / регион, название компании) отображается для всех пользователей и будет сопровождать любой опубликованный вами контент пока вы специально не укажите скрыть название вашей компании. Вы можете обновить ваш IBM аккаунт в любое время.

Вся введенная информация защищена.

Выберите имя, которое будет отображаться на экране



При первом входе в developerWorks для Вас будет создан профиль и Вам нужно будет выбрать Отображаемое имя. Оно будет выводиться рядом с контентом, опубликованным Вами в developerWorks.

Отображаемое имя должно иметь длину от 3 символов до 31 символа. Ваше Имя в системе должно быть уникальным. В качестве имени по соображениям приватности нельзя использовать контактный e-mail.

Обязательные поля отмечены звездочкой (*).

(Отображаемое имя должно иметь длину от 3 символов до 31 символа.)

Нажимая Отправить, Вы принимаете Условия использования developerWorks.

 


Вся введенная информация защищена.


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=40
Zone=Мобильные приложения, Open source, Web-архитектура
ArticleID=940536
ArticleTitle=Система макетирования Maqetta: Часть 3. Установка прототипа UI Maqetta с помощью PhoneGap
publish-date=08122013