Статьи

Построить спидометр GPS: пользовательский интерфейс и польский

В первой части, посвященной Mobiletuts + , мы рассмотрели основы разработки AIR для Android, а также заложили основы нашего приложения для спидометра, используя поддержку GPS для определения текущей скорости.

Теперь мы добавим последние штрихи к приложению, создав его пользовательский интерфейс, а также добавив код, чтобы приложение могло поддерживать свое состояние.

Ознакомьтесь с первой частью этого руководства. Постройте GPS-спидометр. Начало работы с AIR for Android на нашем дочернем сайте Mobiletuts +!


Запустите Flash Professional CS5 и откройте FLA, с которым вы работали, из первой части.

Иллюстрации , необходимые для этого урока, были представлены в части 1 в source / speedometer-artwork.fla , который использовался в качестве отправной точки. Поэтому ваш рабочий FLA должен содержать ту же иллюстрацию в своей библиотеке.

Подтвердите это, проверив, что библиотека вашего FLA выглядит следующим образом:

Если ваша библиотека не содержит обложки, скопируйте ее из source / speedometer-artwork.fla , который был предоставлен в части 1.


В настоящее время пользовательский интерфейс состоит из двух динамических текстовых полей: одно показывает текущую скорость в метрах в секунду, а другое — в милях в час.

Используя иллюстрацию из библиотеки, давайте теперь создадим циферблат спидометра, который мы можем использовать для замены этих текстовых полей.

Выберите Вставить | Новый символ … (Ctrl + F8).

На панели «Создать новый символ» назовите символ «Спидометр» и убедитесь, что для его типа установлено значение «Видеоклип»:

Нажмите ОК.

Из Flash IDE теперь вы можете напрямую редактировать созданный вами видеоклип.

Назовите слой временной шкалы «Meter Background»:

В вашей библиотеке разверните папку «Background» и перетащите «meter-background.png» на сцену.

На панели «Инспектор свойств» установите значения x и y клипа на «0»:


Оставаясь на временной шкале «Спидометр», добавьте новый слой и назовите его «Стрелка»:

В вашей библиотеке разверните папку «Стрелка» и перетащите клип «Стрелка» на сцену. Убедитесь, что вы перетащите его на слой с именем «Стрелка».

На панели «Инспектор свойств» установите значение x клипа равным 239, а значение y — 273.

Назовите экземпляр «стрелка» на панели инспектора свойств.

С выделенным клипом выберите Free Transform Tool (Q) на панели инструментов. Вокруг клипа появится ограничивающая рамка с точкой начала, показанной в виде белого круга в центре:

Переместите исходную точку клипа в нижний центр ограничительной рамки:

Вернитесь к корневой временной шкале.


Теперь мы можем удалить текстовые поля, которые ранее представляли пользовательский интерфейс.

Нажмите на инструмент «Выделение» (V) на панели инструментов и выберите и удалите каждое текстовое поле по отдельности.

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

Перетащите из библиотеки на сцену видеоклип «Спидометр», созданный на шаге 3. Назовите экземпляр спидометра клипа и поместите его в (0, 81).

Переименуйте слой временной шкалы, на котором находится ваш клип, в «Спидометр».

Стадия вашего корневого графика теперь должна выглядеть следующим образом:

Сохраните свой FLA.


Нам также необходимо отразить эти изменения в классе документа.

Откройте Application.as и удалите следующие два объявления переменных-членов:

1
2
public var metresPerSecond :TextField;
public var milesPerHour :TextField;

Вам также необходимо удалить ссылки на эти переменные из handleGeolocationUpdate() . Удалите следующие две строки:

1
2
3
4
5
private function handleGeolocationUpdate( e :GeolocationEvent ) :void
{
    metresPerSecond.text = String( Math.round( e.speed ) );
    milesPerHour.text = String( Math.round( convertToMilesPerHour( e.speed ) ) );
}

Вы также можете удалить оператор импорта, используемый для текстовых полей:

1
2
3
import flash.events.KeyboardEvent;
import flash.text.TextField;
import flash.ui.Keyboard;

Наконец добавьте новую переменную-член, которая ссылается на новый экземпляр видеоролика спидометра, который находится на сцене:

1
2
public var speedometer :Speedometer;
private var geolocation :IGeolocation;

Сохранить Application.as .

Мы вернемся к Application.as только мы напишем класс, который представляет спидометр.


Создайте новый класс и добавьте в него следующее:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package
{
    import flash.display.MovieClip;
    import flash.display.Sprite;
    import flash.events.Event;
     
    public class Speedometer extends Sprite
    {
        /*
            Constants
        */
         
        static private const SPEED_CONVERSION_BASE :Number = 2.2369362920544;
        static private const ROTATION_CONVERSION_BASE :Number = 1.671428571;
        static private const START_ROTATION :Number = -117
         
         
        /*
            Member Variables
        */
         
        public var arrow :MovieClip;
         
         
        /*
            Class Methods
        */
         
        public function Speedometer()
        {
            addEventListener( Event.ADDED_TO_STAGE, handleAddedToStage );
        }
         
        public function set speed( metresPerSecond :Number ) :void
        {
            var milesPerHour :Number = convertToMilesPerHour( metresPerSecond );
            arrow.rotation = convertToRotation( milesPerHour );
        }
         
        private function handleAddedToStage( e :Event ) :void
        {
            removeEventListener( Event.ADDED_TO_STAGE, handleAddedToStage );
            arrow.rotation = START_ROTATION;
        }
         
        private function convertToRotation( milesPerHour ) :Number
        {
            return START_ROTATION + ( milesPerHour * ROTATION_CONVERSION_BASE );
        }
         
        private function convertToMilesPerHour( metresPerSecond :Number ) :Number
        {
            return metresPerSecond * SPEED_CONVERSION_BASE;
        }
    }
}

Сохраните класс как Speedometer.as .

Перейдите в свою библиотеку, щелкните правой кнопкой мыши клип спидометра и выберите « Свойства …» . На панели «Свойства символа» разверните раздел «Дополнительно». Установите флажок «Экспорт для ActionScript» и установите для поля «Класс» значение «Спидометр»:

Нажмите OK, чтобы зафиксировать изменения. Класс Speedometer теперь будет связан с видеоклипом Спидометр в вашей библиотеке.

В классе есть одна переменная-член с именем arrow , которая содержит ссылку на экземпляр фрагмента ролика, представляющий стрелку спидометра.

Метод handleAddedToStage() вызывается при срабатывании события ADDED_TO_STAGE на спидометре. На данный момент метод просто поворачивает экземпляр фрагмента ролика со arrow так, что он изначально указывает на 0 миль в час на спидометре.

Открытый API класса Speedometer состоит из одного свойства с именем speed , которое принимает скорость, измеряемую в метрах в секунду. Вы можете увидеть метод, который обрабатывает это здесь:

1
2
3
4
5
public function set speed( metresPerSecond :Number ) :void
{
    var milesPerHour :Number = convertToMilesPerHour( metresPerSecond );
    arrow.rotation = convertToRotation( milesPerHour );
}

По сути, этот метод преобразует скорость из метров в секунду в мили в час перед тем, как рассчитать угол поворота, который будет применен к игле, чтобы она указывала на правильное положение на спидометре. Класс также имеет два закрытых метода, которые помогают ему сделать это — convertToMilesPerHour() и convertToRotation() .


Вернемся к Application.as .

Добавьте следующую строку кода в метод handleGeolocationUpdate() :

1
2
3
4
private function handleGeolocationUpdate( e :GeolocationEvent ) :void
{
    speedometer.speed = e.speed;
}

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

Сохранить Application.as .


Хорошо, теперь мы можем проверить, что у нас есть.

Если вы проводите тестирование непосредственно на устройстве Android и полагаетесь на его устройство GPS, выберите «Реальный» профиль публикации, который мы настроили в части 1, а также не забудьте активировать датчик GPS. Если вы проводите тестирование на настольном компьютере или на устройстве Android без использования GPS-модуля, выберите профиль публикации «Simulate».

Если вы тестируете в Flash Professional, выберите Control | Тестовый фильм | во Flash Professional. Если вы тестируете на устройстве Android, выберите Файл | Опубликовать (Alt + Shift + F12).

Кроме того, если вы создаете приложение AIR for Android впервые сегодня, вам будет представлена ​​панель «Настройки приложения и установщика» при попытке опубликовать:

Вам нужно будет повторно ввести пароль, связанный с вашим сертификатом из части 1. Также установите флажок «Запомнить пароль для этого сеанса», чтобы эта панель не появлялась при каждой попытке публикации и развертывания на телефоне Android.

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

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


При разработке для мобильных устройств, оптимизация и производительность должны быть основным фактором. Есть несколько вещей, которые мы можем сделать с классом Speedometer чтобы повысить производительность на мобильных устройствах.

Добавьте следующие две строки кода в метод handleAddedToStage() в handleAddedToStage() :

1
2
3
4
5
6
7
8
9
private function handleAddedToStage( e :Event ) :void
{
    removeEventListener( Event.ADDED_TO_STAGE, handleAddedToStage );
 
    mouseEnabled = false;
    mouseChildren = false;
             
    arrow.rotation = START_ROTATION;
}

Поскольку это не требуется, мы отключаем взаимодействие с мышью на спидометре, задав для mouseEnabled и mouseChildren значение false .

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

Оставаясь внутри handleAddedToStage() добавьте следующие две строки кода:

01
02
03
04
05
06
07
08
09
10
11
12
private function handleAddedToStage( e :Event ) :void
{
    removeEventListener( Event.ADDED_TO_STAGE, handleAddedToStage );
 
    mouseEnabled = false;
    mouseChildren = false;
     
    arrow.cacheAsBitmap = true;
    arrow.cacheAsBitmapMatrix = new Matrix();
     
    arrow.rotation = START_ROTATION;
}

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

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

cacheAsBitmap свойства cacheAsBitmap объекта значение true вы указываете среде выполнения Flash, что хотите, чтобы растровое представление этого объекта было сохранено в графическом процессоре. Растровое изображение будет оставаться в графическом процессоре до тех пор, пока экранный объект, который он представляет, не изменится, после чего будет создано новое растровое изображение для экранного объекта и отправлено в графический процессор.

Чтобы кэширование на GPU было эффективным, вам нужно минимизировать количество изменений экранного объекта. Смена положения объекта x или y — это нормально, поскольку кэшированные данные растрового изображения все еще могут быть использованы, но перемещение точки воспроизведения в пределах вашего экранного объекта или вращение его приведет к аннулированию кэшированной версии, что заставит новое растровое изображение отображаться и загружаться в графический процессор. Этот процесс является дорогостоящим, и наличие экранных объектов, которые необходимо постоянно кэшировать, будет фактически снижать производительность, а не улучшать ее.

Учитывая, что стрелка спидометра постоянно вращается, вы можете спросить, почему мы кешируем ее. В конце концов, разве это не заставляет клип со стрелкой постоянно кешироваться? Короткий ответ — да, если мы только установили свойство cacheAsBitmap в true . Однако было также установлено дополнительное свойство — cacheAsBitmapMatrix . Это свойство позволяет выполнять дополнительные графические операции на графическом процессоре, например вращение.

Другими словами, устанавливая и cacheAsBitmap и cacheAsBitmapMatrix вы можете принудительно cacheAsBitmapMatrix растровое представление стрелки нашего спидометра в графический процессор, а также заставлять графический процессор вращать кэшированное растровое изображение каждый раз, когда стрелка поворачивается нашим кодом.

Наконец, добавьте следующий оператор импорта в ваш класс:

1
2
import flash.events.Event;
import flash.geom.Matrix;

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

Повышение производительности вряд ли будет видно в этом приложении, но это небольшое введение в кэширование на GPU должно пригодиться во многих ваших будущих мобильных проектах.

Сохранить Speedometer.as .


Adobe AIR для Android предоставляет два режима рендеринга, которые могут использоваться средой выполнения. До настоящего времени мы использовали режим рендеринга ЦП, который заставляет весь рендеринг графики происходить на ЦП. Чтобы воспользоваться преимуществами графического процессора на устройстве, необходимо специально настроить приложение на использование режима визуализации графического процессора.

Давайте сделаем это сейчас.

В среде Flash IDE выберите «Файл» | Настройки AIR Android … для вызова панели настроек приложения и установщика:

Убедитесь, что выбрана вкладка Общие. Используя раскрывающийся список, установите для поля «Режим рендеринга» значение «GPU».

Теперь нажмите ОК.

Используя профиль «Simulate», опубликуйте и разверните эту последнюю версию на вашем Android-устройстве.


Вы можете помнить из части 1, что GPS-приемники не всегда точны, а это означает, что данные, которые вы получаете от датчика вашего телефона, не всегда могут быть правильными. Даже данные теста, которые я предоставил для класса GeolocationSimulate содержали ошибки.

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

Вот значения для первых пятнадцати секунд вывода данных GeolocationSimulate :

2, 128 , 5, 6, 128 , 7, 128 , 7, 128 , 8, 6, 5, 128 , 7, 9

Как вы можете видеть, есть несколько странных сбоев, которые заставляют приемник сообщать, что вы путешествуете со скоростью 128 метров в секунду. Мы хотим поймать эти шипы и предотвратить их появление в приложении спидометра.

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

Сначала в Speedometer.as добавьте следующую константу:

1
2
static private const START_ROTATION :Number = -117
static private const MAX_SPEED_INCREASE :Number = 20;

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

1
2
public var arrow :MovieClip;
private var currMetresPerSecond :Number;

Давайте инициализируем эту переменную-член в конструкторе:

1
2
3
4
5
public function Speedometer()
{
    currMetresPerSecond = 0;
    addEventListener( Event.ADDED_TO_STAGE, handleAddedToStage );
}

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

01
02
03
04
05
06
07
08
09
10
11
public function set speed( metresPerSecond :Number ) :void
{
    // Prevent geolocation errors from being used.
    if( Math.abs( currMetresPerSecond — metresPerSecond ) < MAX_SPEED_INCREASE )
    {
        currMetresPerSecond = metresPerSecond;
    }
             
    var milesPerHour :Number = convertToMilesPerHour( currMetresPerSecond );
    arrow.rotation = convertToRotation( milesPerHour );
}

Также обратите внимание, что мы изменили следующую строку в методе с:

1
var milesPerHour :Number = convertToMilesPerHour( metresPerSecond );

чтобы:

1
var milesPerHour :Number = convertToMilesPerHour( currMetresPerSecond );

Сохранить Speedometer.as .

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


Как отмечалось ранее, стрелка спидометра переходит к значениям, а не плавно перемещается между ними.

Давайте исправим это с помощью движка анимации движения GreenSock TweenLite .

Добавьте следующий класс импорта в класс Speedometer :

1
2
import flash.geom.Matrix;
import com.greensock.TweenLite;

Теперь в методе set speed() замените следующую строку:

1
arrow.rotation = convertToRotation( milesPerHour );

с этим:

1
TweenLite.to( arrow, 1, { rotation: convertToRotation( milesPerHour ) } );

Эта новая строка кода инициирует анимацию в экземпляре фрагмента ролика со стрелкой, которая происходит в течение одной секунды. В течение этого периода времени свойство rotation экземпляра стрелки плавно переходит от его текущего значения к новому значению, вычисленному convertToRotation() .

Сохранить Speedometer.as .

Конечно, нам нужно загрузить TweenLite SDK и настроить Flash Professional CS5 для его использования. Давайте сделаем это на следующем шаге.


Откройте веб-браузер и перейдите на страницу TweenLite на сайте GreenSock: http://www.greensock.com/tweenlite/

В правой части страницы находится кнопка «Скачать AS3». Нажмите на него и загрузите ZIP-файл. После загрузки скопируйте zip-файл на рабочий стол.

Для рабочего стола вот куда скопировать zip-файл:

  • Windows Vista и Windows 7: C: \ Users \ имя_пользователя \ Desktop
  • Windows XP: C: \ Documents and Settings \ ваше_имя_пользователя \ рабочий стол
  • Mac OS X: Macintosh HD / Пользователи / Ваше_пользовательское имя / Рабочий стол

Распакуйте все файлы и удалите оригинальный zip-файл.

В Flash Professional CS5 выберите Файл | Настройки публикации ….

На панели «Параметры публикации» выберите профиль публикации «Simulate» и перейдите на вкладку «Flash»:

Нажмите кнопку «Настройки …» в правой части поля «Сценарий». Откроется панель «Дополнительные параметры ActionScript 3.0». Убедитесь, что выбрана вкладка «Исходный путь»:

Нажмите кнопку «Добавить новый путь» на панели «Исходный путь» (кнопка «Плюс»). Теперь нажмите кнопку «Обзор пути».

Перейдите к папке greensock-as3, которая содержит подпапку с именем «com». Если вы распаковали zip-файл на рабочий стол, путь должен быть следующим:

  • Windows Vista и Windows 7: C: \ Users \ имя_пользователя \ Desktop \ greensock-as3 \ greensock-as3 \
  • Windows XP: C: \ Documents and Settings \ ваше_пользовательское имя \ рабочий стол \ greensock-as3 \ greensock-as3 \
  • Mac OS X: Macintosh HD / Пользователи / имя_пользователя / рабочий стол / greensock-as3 / greensock-as3 /

В случае успеха панель расширенных настроек ActionScript 3.0 должна выглядеть примерно так:

Нажмите OK, чтобы подтвердить ваши изменения.

Теперь перейдите в «Реальный» профиль публикации и добавьте путь к библиотеке в его настройки ActionScript 3.0.

Убедитесь, что оба профиля успешно построены.

Выберите соответствующий профиль и протестируйте последнюю версию кода на рабочем столе или на устройстве.

Теперь стрелка спидометра должна плавно перемещаться до заданной скорости.

Документацию по API для TweenLite и других движков анимации движения GreenSock можно найти по адресу http://www.greensock.com/as/docs/tween/ . Вы также можете найти интерактивные примеры и примеры кода для TweenLite по адресу http://www.greensock.com/timelinelite/ .


Теперь мы можем начать работу со счетчиком пробега спидометра. Наше приложение будет отслеживать общее пройденное вами расстояние и обновит счетчик пробега, чтобы отразить это. Вот как будет выглядеть счетчик пробега:

Мы хотим, чтобы цифры внутри счетчика плавно перемещались между значениями. Чтобы увидеть это в действии, взгляните на предварительный просмотр SWF, показанный в начале этого урока. Каждый раз, когда добавляется дополнительная миля, вы увидите цифры на счетчике.

Наш счетчик состоит из восьми цифр, и мы начнем с написания класса, который представляет одну цифру.


Чтобы сэкономить ваши усилия, вы найдете клип в вашей библиотеке с именем «Цифра». Вы можете найти его в папке «Счетчик». Дважды щелкните «Цифра», чтобы перейти к его временной шкале.

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

Сначала это может показаться странным, но столбец содержит каждое число дважды — от 0 до 9 и от 0 до 9 снова. Такое дублирование позволяет нам эффективно моделировать вращение цифр, например, от 9 до 0.

Диаграмма ниже должна проиллюстрировать это вместе с несколькими другими сценариями:

На приведенной выше диаграмме красный квадрат представляет область фрагмента ролика «Цифра», которая фактически будет видна пользователю. Чтобы показать пользователю другое число, экземпляр «столбца» будет прокручиваться вверх — область маски всегда будет оставаться на том же месте.

В первом примере выше показано движение от числа «0» до числа «4». Как видите, для этого мы используем только верхнюю половину столбца — мы никогда не переходим к повторным числам в нижней половине. То же самое относится и ко второму примеру, где мы переходим от числа «5» к числу «9».

Третий пример, однако, отличается. Показывает перемещение цифры от цифры 5 к цифре 3. На этот раз мы переходим от чисел в верхней половине столбца к дублирующимся числам в нижней половине столбца. То же самое относится и к последнему примеру, где мы переходим от числа «9» к числу «0».

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


Давайте напишем класс, который связан с клипом Digit и управляет им.

Создайте новый класс и добавьте в него следующее:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
package
{
    import flash.display.MovieClip;
    import flash.events.Event;
    import flash.geom.Rectangle;
    import com.greensock.TweenLite;
    import com.greensock.easing.Linear;
     
    public class Digit extends MovieClip
    {
        static private const DIGIT_HEIGHT :uint = 25;
        static private const DIGIT_WIDTH :uint = 23;
         
        public var column :MovieClip;
        private var currVal :int;
        private var prevVal :int;
         
        public function Digit()
        {
            currVal = -1;
            prevVal = -1;
            scrollRect = new Rectangle( 0, 0, DIGIT_WIDTH, DIGIT_HEIGHT );
        }
         
        public function set value( value :int ) :void
        {
            // Set the digit’s value.
            prevVal = currVal;
            currVal = value % 10;
             
            // If it’s the first time then snap to the value.
            // Otherwise move to the value.
            if( prevVal == -1 )
            {
                snapColumn();
            }
            else
            {
                moveColumn();
            }
        }
         
        public function get value() :int
        {
            return currVal;
        }
         
        private function snapColumn() :void
        {
            column.y = -currVal * DIGIT_HEIGHT;
        }
         
        private function moveColumn() :void
        {
            // Stop the column from animating.
            TweenLite.killTweensOf( column );
             
            // Ensure that we start from the top-half of the column.
            if( column.y < -( DIGIT_HEIGHT * 10 ) )
            {
                column.y += ( DIGIT_HEIGHT * 10 );
            }
             
            // Find out the number we are currently stopped at.
            var valStoppedAt :int = Math.abs( column.y / DIGIT_HEIGHT );
             
            // Calculate the y-position the column needs to be moved to to
            // show the new current number.
            var targY :Number = -currVal * DIGIT_HEIGHT;
             
            // If the new current value is less than the value we stopped at
            // then we need to simulate the digit wrapping back round.
            // this, we’ll scroll into the bottom-half of the column.
            if( currVal < valStoppedAt )
            {
                targY -= ( DIGIT_HEIGHT * 10 );
            }
             
            // Perform the actual scroll animation on the column.
            var duration :uint = Math.abs( column.y — targY ) / DIGIT_HEIGHT;
            TweenLite.to( column, duration, { y: targY, ease:Linear.easeNone } );
        }
    }
}

Сохраните класс как Digit.as .

Открытый API класса Digit состоит из одного свойства с именем value . При установке этого свойства цифра будет прокручиваться до этого значения. Однако есть одно исключение. Если свойство value устанавливается в первый раз, класс просто разместит цифру в этом значении, а не прокрутит до нее. Это позволяет установить цифру в исходное положение по умолчанию при первом value свойства value .

Посмотрите на операторы импорта в классе. Вы увидите, что этот класс, как и класс Speedometer , использует API-интерфейс TweenLite:

1
2
import com.greensock.TweenLite;
import com.greensock.easing.Linear;

Однако, наряду с использованием класса TweenLite , мы также импортируем один из классов замедления TweenLite с именем Linear , который будет использоваться для определения того, как происходит анимация движения столбца чисел в мувиклипе «Цифра».

В Digit.as используются три переменные- Digit.as :

1
2
3
public var column :MovieClip;
private var currVal :int;
private var prevVal :int;

Первый — column — просто содержит ссылку на экземпляр столбца, который находится на временной шкале фрагмента ролика «Цифра». Две другие переменные-члены — currVal и prevVal — используются для хранения текущего значения, которое представляет цифра, и предыдущего значения, которое она представляла.

Класс имеет две объявленные константы, которые определяют ширину и высоту каждого из числовых значений в мувиклипе «Цифра»:

1
2
static private const DIGIT_HEIGHT :uint = 25;
static private const DIGIT_WIDTH :uint = 23;

Эти константы используются в разных местах класса, первое из которых находится в конструкторе:

1
2
3
4
5
6
public function Digit()
{
    currVal = -1;
    prevVal = -1;
    scrollRect = new Rectangle( 0, 0, DIGIT_WIDTH, DIGIT_HEIGHT );
}

Как упоминалось ранее, только одно значение будет отображаться в любой момент в клипе «Цифра». Это может быть достигнуто путем рисования маски на временной шкале фрагмента ролика «Цифра», однако маски загружают процессор и не рекомендуются для мобильных устройств. Альтернативный подход к созданию маски — установить свойство scrollRect объекта. Константы DIGIT_WIDTH и DIGIT_HEIGHT используются здесь для определения прямоугольной области, которая расположена над первой цифрой в DIGIT_WIDTH DIGIT_HEIGHT ролика.

Кроме того, в конструкторе значения currVal и prevVal по умолчанию равны -1 , что указывает на то, что значение еще не было установлено для цифры через его свойство value .

Теперь давайте посмотрим на метод, который позволяет установить свойство value :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
public function set value( value :int ) :void
{
    // Set the digit’s value.
    prevVal = currVal;
    currVal = value % 10;
             
    // If it’s the first time then snap to the value.
    // Otherwise move to the value.
    if( prevVal == -1 )
    {
        snapColumn();
    }
    else
    {
        moveColumn();
    }
}

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

Наиболее сложным методом в этом классе является moveColumn() , который отвечает за скольжение экземпляра столбца, чтобы показать правильное значение пользователю. Метод хорошо прокомментирован и показан ниже:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
private function moveColumn() :void
{
    // Stop the column from animating.
    TweenLite.killTweensOf( column );
             
    // Ensure that we start from the top-half of the column.
    if( column.y < -( DIGIT_HEIGHT * 10 ) )
    {
        column.y += ( DIGIT_HEIGHT * 10 );
    }
             
    // Find out the number we are currently stopped at.
    var valStoppedAt :int = Math.abs( column.y / DIGIT_HEIGHT );
     
    // Calculate the y-position the column needs to be moved to to
    // show the new current number.
    var targY :Number = -currVal * DIGIT_HEIGHT;
             
    // If the new current value is less than the value we stopped at
    // then we need to simulate the digit wrapping back round.
    // this, we’ll scroll into the bottom-half of the column.
    if( currVal < valStoppedAt )
    {
        targY -= ( DIGIT_HEIGHT * 10 );
    }
             
    // Perform the actual scroll animation on the column.
    var duration :uint = Math.abs( column.y — targY ) / DIGIT_HEIGHT;
    TweenLite.to( column, duration, { y: targY, ease:Linear.easeNone } );
}

Для тех, кто требует дальнейшего объяснения, вот некоторый псевдокод, который должен прояснить ситуацию:

  1. Если столбец прокручивается до позиции, остановите ее.
  2. Если в значении в нижней половине «столбца», то перейти к тому же значению в верхней половине.
  3. Рассчитайте целевую y-позицию, в которую нужно переместить «столбец», чтобы показать текущее значение в верхней половине столбца.
  4. Если текущее значение меньше, чем предыдущее значение, отрегулируйте целевую y-позицию, чтобы мы прокрутили до того же значения в нижней половине.
  5. Используя TweenLite, запустите анимацию прокрутки в целевую y-позицию.

Все, что осталось сделать, — это связать класс с клипом Digit в библиотеке.

Щелкните правой кнопкой мыши фрагмент ролика «Цифра» в библиотеке и выберите «Свойства …», чтобы открыть диалоговое окно «Свойства символа».

В диалоговом окне установите флажок «Экспорт для ActionScript» и введите «Цифра» в поле «Класс»:

Нажмите OK и сохраните свой FLA.

Теперь пришло время протестировать класс Digit и убедиться, что он работает должным образом.


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

Мы будем использовать класс Application для этого быстрого теста.

Сначала перетащите экземпляр фрагмента ролика «Цифра» из библиотеки на корневую временную шкалу. Назовите экземпляр «цифра» и поместите его на сцене в (158, 449):

В Application.as добавьте следующую переменную-член для ссылки на экземпляр фрагмента фильма «digit»:

1
2
3
public var digit :Digit;
public var speedometer :Speedometer;
private var geolocation :IGeolocation;

Теперь давайте присвоим цифре значение по умолчанию, добавив следующую строку в конец конструктора класса Application :

1
2
3
4
geolocation.setRequestedUpdateInterval( 1000 );
geolocation.addEventListener( GeolocationEvent.UPDATE, handleGeolocationUpdate );
             
digit.value = 6;

Сохранить класс.

Опубликуйте и проверьте это на рабочем столе, выбрав Control | Тестовый фильм | во Flash Professional. Вы должны увидеть цифру, расположенную внутри спидометра и по умолчанию равную цифре «6».

Теперь давайте проверим, что мы можем перемещаться между значениями. Давайте попробуем перейти от «6» к «9». Добавьте следующую строку в ваш конструктор:

1
2
digit.value = 6;
digit.value = 9;

Сохраните класс и повторите тестирование во Flash Professional. На этот раз вы увидите прокрутку цифр от «6» до «9».

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

1
2
digit.value = 8;
digit.value = 4;

Сохраните класс и протестируйте в Flash Professional. Если все идет по плану, вы должны увидеть цифру, начинающуюся с «8» и вращающуюся до упора в цифру «4».

Удалите строки кода, добавленные на этом шаге, включая переменную-член digit . Также удалите экземпляр фильма со «цифрой» со сцены.

Сохраните и Application.as и свой FLA.


Хорошо, пришло время создать клип, который фактически представляет счетчик пробега. К счастью, сложная часть была сделана для вас.

Посмотрите в своей библиотеке. Внутри папки «Counter» вы найдете видеоклип «Counter». Дважды щелкните клип «Counter», чтобы перейти к его временной шкале.

Сидя на сцене, вы найдете восемь экземпляров видеоклипа «Цифра». Каждому из экземпляров было присвоено уникальное имя экземпляра от «d7» в крайнем левом углу до «d0» в крайнем правом.

Хотя вы можете видеть все числа в каждом столбце, помните, что класс Digit связанный с каждым экземпляром, будет применять маску, заставляющую отображать только одно значение во время выполнения.


Теперь мы можем написать класс для представления символа клипа «Счетчик».

Создайте новый класс и добавьте в него следующее:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
package
{
    import flash.display.MovieClip;
     
    public class Counter extends MovieClip
    {
        public var d0 :Digit;
        public var d1 :Digit;
        public var d2 :Digit;
        public var d3 :Digit;
        public var d4 :Digit;
        public var d5 :Digit;
        public var d6 :Digit;
        public var d7: Digit;
         
        private var digits :Vector.<Digit>;
        private var dist :Number;
         
        public function Counter()
        {
            digits = new <Digit>[ d7, d6, d5, d4, d3, d2, d1, d0 ];
            dist = 0;
        }
         
        public function set distance( value :Number ) :void
        {
            dist = value;
            updateDigits();
        }
         
        public function get distance() :Number
        {
            return dist;
        }
         
        private function updateDigits() :void
        {
            // Convert the distance to a string.
            var distString :String = String( dist );
             
            // If the string is too long then reduce it.
            // If the string is too short then pad the beginning out with some zeros.
            if( distString.length > digits.length )
            {
                distString = distString.substr( ( distString.length — digits.length ), digits.length );
            }
            else if( distString.length < digits.length )
            {
                distString = createZerosString( digits.length — distString.length ) + distString;
            }
             
            // Walk the string and assign each digit a value from the string.
            var digit :Digit;
            for( var i :int = 0; i < digits.length; i++ )
            {
                digit = digits[ i ];
                digit.value = parseInt( distString.substr( i, 1 ) );
            }
        }
         
        private function createZerosString( count :uint ) :String
        {
            var s :String = «»;
            for( var i :uint = 0; i < count; i++ )
            {
                s += «0»;
            }
            return s;
        }
    }
}

Сохраните класс как Counter.as .

Этот класс просто управляет экземплярами ‘Digit’, принимая пробег, а затем присваивая значение каждой из цифр, чтобы отобразить этот пробег.

Например, расстояние в 16 500 250 миль будет разделено на следующие отдельные значения: «1», «6», «5», «0», «0», «2», «5», «0». Каждое из этих значений затем будет присвоено экземпляру Digit . Например, «d7» будет установлен на «1», «d6» будет установлен на «6», и так далее.

API класса Counter предоставляет одно открытое свойство с именем distance которое используется для установки пробега счетчика. Вот код снова:

1
2
3
4
5
public function set distance( value :Number ) :void
{
    dist = value;
    updateDigits();
}

Как видите, расстояние хранится в переменной-члене с именем dist . Затем вызывается закрытый updateDigits() , который отвечает за разбиение расстояния на отдельные цифры и их отображение.

Вот код:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
private function updateDigits() :void
{
    // Convert the distance to a string.
    var distString :String = String( dist );
             
    // If the string is too long then reduce it.
    // If the string is too short then pad the beginning out with some zeros.
    if( distString.length > digits.length )
    {
        distString = distString.substr( ( distString.length — digits.length ), digits.length );
    }
    else if( distString.length < digits.length )
    {
        distString = createZerosString( digits.length — distString.length ) + distString;
    }
             
    // Walk the string and assign each digit a value from the string.
    var digit :Digit;
    for( var i :int = 0; i < digits.length; i++ )
    {
        digit = digits[ i ];
        digit.value = parseInt( distString.substr( i, 1 ) );
    }
}

По сути, метод берет расстояние и преобразует его в строку. Затем он проходит через строку по одному символу за раз; преобразует каждый символ в целое число; и затем назначает это целое число соответствующему экземпляру Digit .

Также есть дополнительный код для обработки слишком длинных или слишком коротких строк. Если строка слишком короткая, она заполняется дополнительными нулями. Если это слишком долго, то некоторые персонажи рубятся.

На экземпляры Digit ссылаются следующие открытые переменные-члены:

1
2
3
4
5
6
7
public var d0 :Digit;
public var d1 :Digit;
public var d2 :Digit;
public var d3 :Digit;
public var d4 :Digit;
public var d5 :Digit;
public var d6 :Digit;

Кроме того, вектор используется для хранения этих ссылок в том порядке, в котором они должны быть установлены:

1
digits = new <Digit>[ d7, d6, d5, d4, d3, d2, d1, d0 ];

Если вы посмотрите на цикл for в конце updateDigits()метода, вы увидите digitsвектор, к которому осуществляется доступ, чтобы получить ссылку на следующий Digitэкземпляр, значение которого должно быть установлено.

Вы можете задаться вопросом, почему Vectorдля хранения Digitссылок использовался знак «а» Array. Векторы, как правило, более эффективны в использовании памяти, имеют более высокую производительность по сравнению с массивами и поэтому идеально подходят для написания мобильных приложений. Опять же, с этим приложением выигрыши в производительности не будут заметны, но вы должны попытаться выработать привычку к экономии циклов ЦП и памяти при разработке для мобильных устройств.

Для получения дополнительной информации о Vectorклассе посетите сайт Adobe LiveDocs .

Теперь давайте свяжем этот класс с символом фрагмента ролика «Counter» в библиотеке. Щелкните правой кнопкой мыши фрагмент ролика и выберите «Свойства …». В диалоговом окне «Свойства символа» установите флажок «Экспорт для ActionScript» и введите «Счетчик» в поле «Класс»:

Нажмите OK и сохраните свой FLA.

Теперь пришло время подключить счетчик к остальной части приложения.


Дважды щелкните значок клипа «Спидометр» в вашей библиотеке. Добавьте новый слой к его временной шкале, назовите его «Counter» и убедитесь, что он расположен поверх других слоев:

Убедитесь, что выбран новый слой, и перетащите символ клипа «Счетчик» из библиотеки на сцену. Назовите экземпляр «counter» и поместите его в (160, 369):

Сохраните свой FLA.

Теперь давайте добавим код в Speedometerкласс для обработки счетчика.

SpeedometerКласс будет на самом деле отслеживать общее расстояние и передать его на прилавок. Класс будет хранить общее расстояние в метрах и преобразовывать его в мили, прежде чем передать его на счетчик.

Добавьте к классу следующую константу, которая будет использоваться для преобразования расстояния:

1
2
static private const MAX_SPEED_INCREASE :Number = 20;
static private const DISTANCE_CONVERSION_BASE :Number = 0.000621371192237334;

Кроме того, добавьте две новые переменные-члены — одну для хранения ссылки на экземпляр «counter», а другую для хранения пройденного расстояния:

1
2
3
4
public var arrow :MovieClip;
public var counter :Counter;
private var currMetresPerSecond :Number;
private var dist :Number;

В конструкторе по умолчанию distпеременная-член:

1
2
3
4
5
6
public function Speedometer()
{
    currMetresPerSecond = 0;
    dist = 0;
    addEventListener( Event.ADDED_TO_STAGE, handleAddedToStage );
}

В конце урока добавьте новый приватный метод, который преобразует метры в мили:

1
2
3
4
private function convertToMiles( metres :Number ) :Number
{
    return metres * DISTANCE_CONVERSION_BASE;
}

Теперь, наконец, добавьте некоторый код в set speed()метод, чтобы обновить пройденное расстояние и передать расстояние счетчику для отображения:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
public function set speed( metresPerSecond :Number ) :void
{
    // Prevent geolocation errors from being used.
    if( metresPerSecond < MAX_METRES_PER_SECOND )
    {
        currMetresPerSecond = metresPerSecond;
    }
             
    var milesPerHour :Number = convertToMilesPerHour( currMetresPerSecond );
    TweenLite.to( arrow, 1, { rotation: convertToRotation( milesPerHour ) } );
             
    dist += currMetresPerSecond;
    counter.distance = Math.floor( convertToMiles( dist ) );
}

Сохранить Speedometer.as.

Выберите профиль публикации «Simulate» и протестируйте его из Flash, выбрав Control | Тестовый фильм | во Flash Professional.

Как только пройдена миля (подождите чуть более двух минут), вы должны увидеть приращение счетчика. Он будет продолжать увеличиваться для каждой пройденной мили.


У вас почти есть полное приложение. Последнее, что мы добавим, — это некоторое управление состоянием, которое позволит приложению сохранять пройденное расстояние при выходе пользователя и получать пройденное расстояние при следующем запуске приложения.

Мы будем использовать SharedObjectкласс, предоставляемый API-интерфейсом ActionScript. SharedObjectиспользуется для чтения и записи ограниченного объема данных на настольный компьютер пользователя или устройство Android.

Мы сохраним расстояние, пройденное до того момента, SharedObjectкогда наше приложение получит Event.DEACTIVATEсобытие, которое срабатывает непосредственно перед закрытием приложения. Если вы помните из первой части, мы добавили handleDeactivate()прослушиватель событий в наш Applicationкласс для этого самого события.

Однако прежде чем мы добавим код для работы с ним, SharedObjectнам сначала нужно добавить Speedometerоткрытый интерфейс класса, чтобы можно было установить и получить расстояние.

Откройте Speedometer.asи добавьте к нему следующие два метода:

01
02
03
04
05
06
07
08
09
10
public function set distance( metres :Number ) :void
{
    dist = metres;
    counter.distance = Math.floor( convertToMiles( dist ) );
}
         
public function get distance() :Number
{
    return dist;
}

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

Сохранить Speedometer.as.

Теперь давайте добавим код управления состоянием в Application.as.

Сначала добавьте оператор импорта для SharedObjectкласса:

1
2
3
import flash.events.KeyboardEvent;
import flash.net.SharedObject;
import flash.ui.Keyboard;

Добавьте новую переменную-член для представления общего объекта:

1
2
3
public var speedometer :Speedometer;
private var geolocation :IGeolocation;
private var sharedObject :SharedObject;

Теперь инициализируйте общий объект, добавив следующий код в конец конструктора класса:

1
2
3
4
5
6
sharedObject = SharedObject.getLocal( "speedometer" );
if( sharedObject.size == 0 )
{
    sharedObject.data.distance = 0;
}
speedometer.distance = sharedObject.data.distance;

Вы не создаете экземпляр общего объекта напрямую, вместо этого вы делаете запрос на получение ссылки на локально общий объект через getLocal(). Вы переходите к getLocal()уникальной строке, которая используется для доступа к общему объекту для вашего приложения. Если общий объект не существует, среда выполнения Flash создаст его и передаст вам ссылку. В нашем коде выше мы дали нашему общему объекту уникальное имя «спидометр».

Получив ссылку на наш общий объект, мы должны проверить, содержит ли он данные. Приведенный выше код проверяет sizeсвойство общего объекта, чтобы увидеть, есть ли какие-либо данные. Если 0возвращается размер, то нам нужно добавить данные к объекту. Расстояние — это единственные данные, которые нам нужно сохранить, поэтому мы добавляем свойство с именем distanceк нашему общему объекту и устанавливаем для него значение по умолчанию — 0.

Последняя строка кода просто берет расстояние, сохраненное в общем объекте, и использует его для установки speedometerначального расстояния переменной-члена.

Теперь осталось только сохранить текущее расстояние при выходе из приложения. Мы можем сделать это, добавив следующий код в handleDeactivate()метод:

1
2
3
4
5
6
7
private function handleDeactivate( e :Event ) :void
{
    sharedObject.data.distance = speedometer.distance;
    sharedObject.flush();
             
    NativeApplication.nativeApplication.exit();
}

Первая строка получает текущее расстояние от спидометра и устанавливает его в рамках общего объекта. Затем выполняется вызов flush()метода общего объекта . Это заставляет данные быть сохраненными локально.

Сохранить Application.as.

Ваше приложение спидометра теперь может поддерживать состояние, когда оно было закрыто.

Опубликуйте и запустите ваше приложение. Если вы владелец Android, разверните его на своем устройстве. Если вы действительно хотите использовать GPS-модуль устройства, не забудьте переключиться на «Реальный» профиль перед публикацией. Да, и не забудьте включить датчик GPS вашего телефона перед запуском приложения.

Проверьте правильность управления состоянием вашего спидометра, выйдя из приложения, а затем снова запустив его (подождите, пока вы не начислили немного пробега). Вы должны заметить, что счетчик спидометра запоминает пройденное вами расстояние.

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


Теперь вы должны чувствовать себя довольно комфортно с основами разработки Adobe AIR для Android. Вы не только создали визуально насыщенное приложение, которое поддерживает состояние и использует GPS-датчик вашего Android, вы также затронули более сложные темы, такие как аппаратное ускорение и оптимизация кода.

Спасибо за чтение и удачи в будущих проектах Adobe AIR для Android!


Иллюстрации, использованные в этом учебном пособии, были созданы с использованием шагов, описанных в разделе «Как создать иконку спидометра в Photoshop» — учебном пособии испанского гуру графического дизайна Роберто Абриль Идальго .

Особая благодарность моей невестке Хелен Калеб, которая всегда приходит мне на помощь благодаря своему опыту в Photoshop.