Статьи

Обратный отсчет в стиле с таймером терминала аэропорта

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

Переизданный учебник

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


Создайте новый файл Flash (Actionscript 3) со следующими настройками: 500×300, черный фон и 30 кадров в секунду.

Настройка файла Flash

Создайте новый мувиклип с именем digit_bottom и нарисуйте внутри него скругленный прямоугольник шириной около 36 пикселей и высотой 50 пикселей. (Быстрый способ нарисовать прямоугольник с точными размерами — выбрать инструмент «Прямоугольник» и щелкнуть «Alt» на сцене.)

Дайте прямоугольнику градиентную заливку от # 111111 (вверху) до # 333333 (внизу) и цветной контур 2 пикселя # 333333.

Фоновое изображение или анимация

Расположите прямоугольник так, чтобы точка регистрации мувиклипа (маленький знак «+») находилась точно посередине между верхом и низом и левым краем. Если вы сделали прямоугольник высотой 50px, то значение y должно быть -25.

Свойства прямоугольника

Создайте новый слой и добавьте динамическое текстовое поле с именем «t_num». Выберите шрифт, который имеет отношение к аэропорту или вокзалу (например, Helvetica , DIN или Interstate ). Я использую Helvetica Bold.

Установите формат абзаца по центру и не забудьте встроить шрифты для чисел 0–9.

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

Числовое текстовое поле

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


Создайте новый слой на временной шкале MovieClip digit_bottom и назовите его «mask». Скопируйте прямоугольник со скругленными углами и вставьте их на слой маски («Правка»> «Вставить на месте» или «Command-Shift-V»).

Выберите верхнюю половину маски прямоугольника и удалите ее.

Щелкните правой кнопкой мыши слой маски , выберите «Маска» и убедитесь, что он маскирует все слои под ним.

Создать маску

Зайдите в библиотеку, продублируйте мувиклип digit_bottom и назовите новую копию digit_top.

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

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

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

зажимы digit_top и digit_bottom

Создайте новый мувиклип с именем «Digit». Перетащите MovieClips digit_top и digit_bottom и поместите их в 0,0. Дайте им имена экземпляров «top1» и «bottom1».

Теперь скопируйте оба фрагмента ролика ( digit_top и digit_bottom ), создайте новый слой и вставьте копию каждого из них. Назовите новые копии «top2» и «bottom2».

Теперь у вас должно быть 4 MovieClips внутри вашего Digit MovieClip: 2 копии digit_top и 2 копии digit_bottom . Я объясню, почему мы настраиваем это следующим образом.


Нам нужно немного поработать над анимацией, чтобы получить эффект переворачивания чисел, который мы хотим. Взгляните на диаграмму ниже нашего Digit MovieClip (я рендерил его в 3D только для того, чтобы вы могли легче видеть слои):

Цифровая анимационная диаграмма

Мы начинаем с клипа bottom2, перевернутого вверх дном (используя свойство scaleY ) и расположенного за клипом top2 . На данный момент 2 видимых клипа это top2 и bottom1 . Числа на этих двух клипах соответствуют друг другу, поэтому они образуют полную цифру.

Теперь мы перевернем клип top2 к центру цифры. В этой точке шкала Y будет равна нулю, поэтому клип не будет виден. В то же время, мы также переворачиваем клип bottom2 , но этот перевернем до самого конца. Так как он находится за top2 , он не будет отображаться, пока не переместится за полпути. Теперь 2 видимых клипа — это top1 и bottom1 . Числа на этих двух клипах не совпадают, но это нормально, потому что этот шаг длится недолго.

Зажим top2 остается в центре, а bottom2 продолжает падать до самого дна. Как только это на месте, числа на видимых клипах ( top1 и bottom2 ) снова совпадают, чтобы сформировать полную цифру.

На этом этапе мы будем ретранслировать и сбрасывать позиции 2 скрытых клипов, чтобы подготовиться к следующему броску. Обратите внимание, что клипы находятся в тех же позициях, что и Шаг 1, только в обратном порядке.


Теперь, когда мы настроили отдельный Digit MovieClip, давайте создадим часы.

Создайте новый мувиклип на сцене «Часы» с именем экземпляра «часы». Внутри нового MovieClip поместите 9 копий вашего Digit MovieClip; 2 для секунд, 2 для минут, 2 для часов и 3 для дней. Дайте каждой цифре имя экземпляра. Слева направо назовите их «digit0», «digit1», «digit2» и так далее.

Добавьте несколько двоеточий для разделения фрагментов ролика и надписей для каждого раздела. Дизайн зависит от вас. Я добавил темный скругленный прямоугольник в качестве фона для моих часов.

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

Часы MovieClip

Создайте новый файл Actionscript с именем «Digit.as» и добавьте этот код, чтобы создать пустую оболочку для класса:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
package {
 
    import flash.display.MovieClip;
 
    public class Digit extends MovieClip {
        private const TOP:int = 0;
        private const BOTTOM:int = 1;
 
        private var _currentDigit:Array;
        private var _nextDigit:Array;
        private var _number:String = «0»;
         
        // CONSTRUCTOR
        public function Digit() {
            _currentDigit = new Array( top1, bottom1 );
            _nextDigit = new Array( top2, bottom2 );
 
        }
 
    }
 
}

Это пока мало что делает. У нас есть пара массивов для хранения двух наборов мувиклипов digit_top и digit_bottom . Я установил 2 константы, TOP и BOTTOM, чтобы отслеживать верхний и нижний клипы в этих массивах. Переменная _number будет содержать цифру, отображаемую в любой момент времени.

(Примечание: я использую подчеркивание в именах переменных для обозначения приватных переменных.)

Найдите свой Digit MovieClip в библиотеке и назначьте ему этот класс в настройках Linkage.

Настройки класса Digit Linkage

Мы собираемся использовать библиотеку TweenLite для анимации нашего Digit MovieClip.

Загрузите версию библиотеки TweenLite для AS3 здесь .

Поместите папку ‘com’ в тот же каталог, что и ваш основной файл Flash (или в исходный путь, если вы установили другой путь к классу ).

Добавьте эти две строки вверху вашего класса Digit , чуть ниже импорта MovieClip:

1
2
import com.greensock.*
import com.greensock.easing.*

Мы едва ли расскажем о том, что TweenLite может сделать в этом уроке. Для получения дополнительной информации ознакомьтесь с документацией TweenLite .


Добавьте эту функцию в ваш класс Digit :

01
02
03
04
05
06
07
08
09
10
public function flipTo(num:String):void {
    _number = num;
    _nextDigit[TOP].t_num.text = num;
    _nextDigit[BOTTOM].t_num.text = num;
     
    // flip down the top of the digit to the halfway point
    TweenLite.to(_currentDigit[TOP], .15, {scaleY: 0, ease: Linear.easeNone});
    // flip the next digit bottom down
    TweenLite.to(_nextDigit[BOTTOM], .3, {scaleY:1, onComplete: flipComplete, ease: Bounce.easeOut});
}

Вот что происходит, строка за строкой:

  • Эта функция принимает строку, которая будет содержать цифру, к которой мы будем перелистывать. Первая строка просто устанавливает переменную _number для хранения этой цифры.
  • Затем мы устанавливаем текстовые поля в TOP и BOTTOM MovieClips в нашем массиве _nextDigit для отображения той же цифры.
  • Затем мы используем TweenLite для анимации свойства scaleY TOP MovieClip _currentDigit до 0. Это дает эффект, что он «падает» к центру цифры.
  • Последняя строка — это другая анимация, на этот раз анимирующая нижний клип _nextDigit от верха цифры вниз до низа. Опять же, мы используем свойство scaleY для имитации этого эффекта, но на этот раз от -1 до 1. Поскольку его анимация удваивается вдвое больше, чем клип TOP, мы даем ему удвоенное количество времени (0,3 секунды вместо 0,15). , Когда эта анимация заканчивается, она вызывает функцию flipComplete. Мы напишем эту функцию на следующем шаге.

Посмотрите еще раз на диаграмму в шаге 8, если вы не уверены в анимации здесь.


Добавьте эту функцию в класс Digit чуть ниже функции flipTo :

1
2
3
4
5
6
7
8
9
private function flipComplete():void {
    // swap digits
    var next:Array = _currentDigit;
    _currentDigit = _nextDigit;
    _nextDigit = next;
     
    // reset layering
    reset();
}

После завершения анимации мы запустим эту функцию. Он заменяет массивы _currentDigit и _nextDigit . После того, как это сделано, он вызывает функцию с именем «reset», чтобы сбросить слои клипа и позиции для следующего броска. Давайте напишем эту функцию сейчас.


Добавьте эту функцию в класс Digit :

1
2
3
4
5
6
7
8
private function reset():void {
    addChild(_nextDigit[BOTTOM]);
    addChild(_currentDigit[TOP]);
     
    // flip up the next bottom to be behind the current top
    _nextDigit[BOTTOM].scaleY = -1;
    _nextDigit[TOP].scaleY = 1;
}

Первые две строки в этой функции выводят нижнюю _nextDigit, а затем верхнюю часть _currentDigit в верхнюю часть списка отображения. Я обычно просто использую addChild (), чтобы сделать это, потому что он требует меньше набирать, чем setChildIndex () .

После того, как клипы перенастроены, мы устанавливаем свойства scaleY, чтобы они были готовы к следующему флипу. Это означает изменение _nextDigit [BOTTOM] с 1 на -1 и _nextDigit [TOP] с 0 на 1.

Опять же, проверьте схему в шаге 8, если вы заблудились.


Одна вещь, которую мы забыли сделать, это правильно расположить клипы для первой анимации сальто. Это легко сделать, добавив вызов функции reset прямо в конструкторе класса Digit:

1
2
3
4
5
6
7
// CONSTRUCTOR
public function Digit() {
    _currentDigit = new Array( top1, bottom1 );
    _nextDigit = new Array ( top2, bottom2 );
     
    reset();
}

Последнее, что нам понадобится в нашем классе Digit, — это способ доступа к закрытой переменной _number извне класса. Мы добавим простую функцию доступа:

1
2
3
public function get number():String {
    return _number;
}

Создайте новый файл ActionScript с именем «Clock.as». Вставьте в этот код:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
package {
 
    import flash.display.MovieClip;
    import flash.events.TimerEvent;
    import flash.media.Sound;
    import flash.utils.Timer;
 
    public class Clock extends MovieClip {
        private var _clockTimer:Timer;
        private var _targetDate:Date;
         
        // CONSTRUCTOR
        public function Clock() {
 
        }
 
    }
 
}

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


Добавьте эту функцию в класс Clock чуть ниже конструктора:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
// set the target date and start the countdown timer
public function set(date:Date):void {
    _targetDate = date;
     
    _clockTimer = new Timer(1000) // tick every second (1000 milliseconds)
    _clockTimer.addEventListener(TimerEvent.TIMER, update);
    _clockTimer.start();
     
    // display the target date above the clock
    t_date.text = _targetDate.toLocaleString().toUpperCase();
     
    // update the clock once here so it starts with the correct time
    update();
}

Это функция, которую мы будем использовать для установки целевой даты для часов. Он принимает дату (конечно) и назначает ее переменной _targetDate . Затем он создает наш _clockTimer . _ClockTimer будет вызывать функцию обновления один раз в секунду для обновления цифр.

После запуска таймера функция устанавливает текст t_date с целевой датой. Функция toLocaleString () обеспечивает отображение даты в местном часовом поясе пользователя.

Последняя строка этой функции вызывает update один раз, чтобы установить часы на нужное время. В противном случае он будет отображать «000 00:00:00» в течение одной секунды до первого события таймера.


Эта функция немного длинная, потому что именно там выполняется большая часть работы. Добавьте его в свой класс часов:

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
private function update(e:TimerEvent = null):void {
    var now:Date = new Date();
     
    // find the difference (in ms) between the target and now
    var diff:Number = _targetDate.valueOf() — now.valueOf();
    if(diff <=0){
        // TIME’S UP!
        // do something cool here
        _clockTimer.stop();
        _clockTimer.removeEventListener(TimerEvent.TIMER, update);
        diff = 0;
    }
     
    // convert to seconds
    diff = Math.round(diff/1000);
     
    // number of days
    var days:int = Math.floor(diff/ (24 * 60 * 60));
    diff -= days*(24 * 60 * 60 );
     
    // number of hours
    var hours:int = Math.floor(diff / (60 * 60))
    diff -= hours*60 * 60;
     
    // number of minutes
    var min:int = Math.floor(diff/ 60);
    diff -= min*60;
     
    // seconds are all that remain
    var sec:int = diff;
     
    // create an array of strings to hold the number for each value
    var diffArr:Array = new Array(String(days), String(hours), String(min), String(sec));
    var diffString:String = «»
    var len:int = 3;
    for each(var s:String in diffArr){
        // pad the string with a leading zero if needed
        while(s.length < len){
            s = «0»+s;
        }
         
        len = 2;
        diffString += s;
    }
     
    // go through each character in the diffString and set the corresponding digit
    for(var i:int = 0; i< diffString.length; i++){
        if(diffString.substr(i, 1) != this[«digit»+i].number){
            this[«digit»+i].flipTo(diffString.substr(i, 1));
        }
    }
 
}

Эта функция принимает TimerEvent в качестве своего параметра. Значением по умолчанию для этого параметра является ноль . Это позволяет нам вызывать функцию без отправки параметра, как мы делаем в функции set .

Первая строка этой функции получает текущую дату и время как объект Date. Далее мы находим разницу между текущей датой и целевой датой (строка 37). Если разница равна 0 или меньше, то она превышает целевую дату, поэтому мы останавливаем _clockTimer (строки 38-44).

Поскольку разница во времени между текущим и целевым значением рассчитывается в миллисекундах, нам необходимо преобразовать ее в удобное для чтения отображение дней, часов, минут и секунд (строки 46–62). Математика здесь довольно проста, если вы знаете, что в секунду 1000 миллисекунд, 60 секунд в минуту, 60 минут в час и 24 часа в день.

В строке 65 мы храним все эти значения как элементы в массиве. Начиная со строки 68, мы перебираем каждый элемент и добавляем его в строку символов с именем diffString. При этом мы также добавляем ведущие нули, где это необходимо (строка 71). Поэтому, если бы наши значения для часов были 30 дней, 5 часов, 56 минут и 6 секунд, diffString выглядела бы так: «030055606».

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


Найдите (или создайте) хороший тикающий звук, который будет звучать при каждом обновлении часов. Импортируйте его в библиотеку вашего Flash-файла и установите имя класса «TickSound» в настройках Linkage.

Tick ​​Sound Linkage Settings

Добавьте переменную _tickSound в начало класса Clock чуть ниже двух других переменных:

1
2
3
private var _clockTimer:Timer;
private var _targetDate:Date;
private var _tickSound:Sound = new TickSound();

И воспроизвести звук внутри функции обновления :

1
_tickSound.play();

Наш таймер обратного отсчета завершен, нам просто нужен какой-то способ установить целевую дату. Создайте новый файл Actionscript с именем «Main.as» с этим кодом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
package {
 
    import flash.display.MovieClip;
 
    public class Main extends MovieClip {
 
        public function Main() {
 
            // set the target date for the clock
            var targetDate:Date = new Date();
            targetDate.setTime( Date.UTC(2010, 4, 28, 20, 00) );
            clock.set(targetDate);
        }
    }
 
}

Все, что это делает, это устанавливает целевую дату для экземпляра Clock на сцене. Я использую setTime () и Date.UTC () для преобразования даты в универсальный тайм-код. Таким образом, дата будет правильной, когда она будет преобразована обратно в местное время на компьютере пользователя. Также помните, что месяцы начинаются с нуля. Итак, месяц 4 на самом деле май, а не апрель.

В вашем Flash-файле установите класс Document в значение «Main».

Установить класс документа

Если вам нужно освежить в использовании класса документов, ознакомьтесь с этим кратким советом .


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

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

Давайте посмотрим, что мы можем сделать с этим …


Создайте новый XML-файл в той же папке, что и ваш Flash-файл, с именем «targetDate.xml» (XML-файл — это просто текстовый файл). Добавьте это в файл XML:

1
2
3
4
5
6
7
<targetDate>
    <year>2011</year>
    <month>3</month>
    <day>25</day>
    <hour>20</hour>
    <minute>21</minute>
</targetDate>

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


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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
package {
 
    import flash.display.MovieClip;
    import flash.net.URLLoader;
    import flash.net.URLRequest;
    import flash.events.Event;
 
    public class Main extends MovieClip {
 
        // CONSTRUCTOR
        public function Main() {
            // load the XML
            var xmlLoader:URLLoader = new URLLoader();
            xmlLoader.addEventListener(Event.COMPLETE, onDataLoaded);
            xmlLoader.load( new URLRequest(«targetDate.xml») );
 
        }
 
    }
 
}

Вы заметите, что мы импортировали некоторые дополнительные классы, чтобы помочь нам загрузить файл XML. В функции конструктора мы создаем новый экземпляр URLLoader для загрузки файла для нас. Мы прикрепляем прослушиватель событий, который будет вызывать функцию с именем onDataLoaded после завершения загрузки файла.


Добавьте эту функцию в основной класс:

1
2
3
4
5
6
7
8
private function onDataLoaded(e:Event):void {
    var xml:XML = new XML(e.target.data);
     
    var targetDate:Date = new Date();
    targetDate.setTime(Date.UTC(int(xml.year), int(xml.month), int(xml.day), int(xml.hour), int(xml.minute) ));
 
    clock.set(targetDate);
}

Эта функция создает новый объект XML из файла, который мы загрузили. Затем мы создаем новый объект Date из значений в XML. Мы снова используем setTime () и Date.UTC () для преобразования даты в универсальный тайм-код. Последняя строка такая же, как и раньше, она просто отправляет целевую дату нашему экземпляру Clock.


Это довольно много для этого. Есть несколько улучшений, которые вы могли бы сделать, хотя:

  1. В зависимости от того, для чего вы используете обратный отсчет, вы можете захотеть сделать что-то особенное для пользователя, когда обратный отсчет достигнет нуля. Вы добавили бы это к классу Clock в части функции обновления, которая проверяет, находится ли таймер на нуле.
  2. Как я уже упоминал, формат нашего XML довольно расточительный. Вы можете просто передать дату в виде строки через FlashVars , использовать другой формат данных (например, JSON ) или просто переформатировать XML, чтобы сделать его более компактным.

Удачи! Как всегда, оставьте комментарий и дайте мне знать, что вы думаете.