Слайдшоу с эффектом Ken Burns

Автор: Андрей. Категория: Уроки Flash

Слайдшоу с эффектом Ken Burns
Наверняка каждый из пользователей знает, что такое слайдшоу и не раз сталкивался с этим явлением на просторах интернета. Сегодня будет творческий урок. Это будет красивое слайдшоу с модным эффектом Ken Burns. Используем Adobe Flash Pro + Action Script 3.0 + XML. Таким способом программируются большинство слайдшоу. В файле XML хранится информация о расположении файлов, их наименованиях, свойствах и т.д. Сами изображения находятся в отдельном каталоге и загружаются на этапе выполнения. А различные эффекты запрограммированы в исполняемом flash-файле. Используем эту схему для создания нашего слайдшоу.

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

Цель

  • научится применять Action Script 3.0 в Adobe Flash Professional
  • научится отделять код от графических объектов в Adobe Flash
  • ознакомится с библиотекой TweenLite и некоторыми плагинами

Техническое задание

Разработать флэш объект размером 500х280, который будет загружать изображения с сервера и последовательно их выводить на экран с заданной частотой. Имена файлов изображений и пути к ним хранятся в XML-файле, который загружается при запуске. К изображениям применяется эффект плавного масштабирования на случайную величину. Изображение должно быть привязано к одной из девяти точек: 4 угловые + 4 серединные + 1 центральная. Тип масштабирования (in / out) также выбирается случайно. Каждое следующее изображение плавно появляется за заданный промежуток времени. Переход можно осуществлять с помощью стрелок «next» и «prev». В XML файле содержатся следующие параметры слайдера: длительность показа каждого слайда, длительность эффекта появления слайда, минимальный коэффициент масштабирования, максимальный коэффициент масштабирования.

Решение

Начнём решение определения объектов. Я выделил 5 объектов:

  • Slide — отдельный слайд
  • Slider — объект, который управляет слайдами
  • Options — объект для хранения настроек
  • AlignPoint — объект для хранения массива точек привязки
  • Main — объект приложения

Я рекомендую сразу приучать себя код писать в отдельных от главного fla-файла. В самом fla-файле прописывать только операторы для управления внутренними объектами MovieClip, например gotoAndPlay, gotoAndStop и т.п. Так будет намного легче разобраться в структуре в дальнейшем.

Для начала создадим проект Flash. На панели Проект (Shift + F8) необходимо в выпадающем списке Проекты нажать пункт Создать проект... Назовём наш проект Slideshow, выберем корневую папку для него и нажмём кнопку Создать проект.

New project Flash

Теперь нужно привязать к нашему главному fla-файлу as-файл. Для этого на панели Свойства в поле Класс напишем имя нашего будущего класса Main и нажмём рядом в полем кнопку Редактировать определение класса. Откроется новое окно с нашим новым классом, который мы должны сохранить в той же папке, что и главный fla-файл. Немного отформатированный код класса пока выглядит так:

package 
{
    import flash.display.MovieClip;

    public class Main extends MovieClip
    {
        public function Main()
        {
            
        }
    }
}

Теперь отложим этот файл и приступим к созданию других объектов. Наши классы по правилам уникальности пространства имён мы будем создавать в папке ru\flexfactory. Начнём с простого класса Options.as. Ничего сложного в этом классе нет. Он нужен, чтобы хранить 4 параметра. Следовательно создадим 4 внутренние переменные, а также геттеры и сеттеры для них. В Adobe Flash в отличие от Adobe Flex нет автосоздания геттеров и сеттеров, но я настоятельно рекомендую не отказываться от них, даже если это небольшое приложение. Приучайте себя к «правильному» программированию. Вернёмся к нашему классу. Конструктор использовать необязательно. Создадим переменные и методы:

package ru.flexfactory
{
    public class Options
    {
        private var _durationSlide:int = 0;
        private var _durationFade:int = 0;
        private var _minScale:Number = 1;
        private var _maxScale:Number = 1;
        
        public function set durationSlide(value:int):void
        {
            _durationSlide = value;
        }
        
        public function get durationSlide():int
        {
            return _durationSlide;
        }
        
        public function set durationFade(value:int):void
        {
            _durationFade = value;
        }
        
        public function get durationFade():int
        {
            return _durationFade;
        }
        
        public function set minScale(value:Number):void
        {
            _minScale = value;
        }
        
        public function get minScale():Number
        {
            return _minScale;
        }
        
        public function set maxScale(value:Number):void
        {
            _maxScale = value;
        }
        
        public function get maxScale():Number
        {
            return _maxScale;
        }
    }
}

Надеюсь, тут всё понятно. Следующий класс — AlignPoint. Этот класс мы будем использовать, чтобы получить случайным образом одну из девяти точек привязки. В конструкторе мы передаём размеры нашего слайдера и заполняем массив точек. Свойство randomPoint() будет возвращать одну из точек массива. Этот небольшой класс выглядит вот так:

package ru.flexfactory
{
    import flash.geom.Point;

    public class AlignPoint
    {
        private var points:Array;
        
        public function AlignPoint(slideWidth:int, slideHeight:int)
        {
            points = [new Point(0, 0),
                      new Point(slideWidth / 2, 0),
                      new Point(slideWidth, 0),
                      new Point(0, slideHeight / 2),
                      new Point(slideWidth / 2, slideHeight / 2),
                      new Point(slideWidth, slideHeight / 2),
                      new Point(0, slideHeight),
                      new Point(slideWidth / 2, slideHeight),
                      new Point(slideWidth, slideHeight)];
        }
        
        public function get randomPoint():Point
        {
            var index:int = Math.round(Math.random() * 8);
            var point:Point = points[index];
            return point;
        }
    }
}

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

Есть 2 способа распространения сторонних библиотек. Закрытые библиотеки скомпилированы в swc файл. Открытые распространяются как as-классы. TweenLite — это открытая библиотека, которую можно скачать на официальном сайте. Чтобы использовать её, необходимо скопировать папку com со всем содержимым в папку проекта. Теперь уделите хотя бы полчаса на знакомство с документацией этой библиотеки на сайте. Так будет проще понять дальнейшие действия.

Теперь создадим новый класс Slide, расширяющий класс MovieClip. Для чего это сделано? Дело в том, что одновременно к одному объекту MovieClip можно применить несколько плагинов TweenLite, но используя только один раз метод to или from. Немного запутано… Т.е. мы можем применить несколько действий (перемещение, изменение alpha-канала, трансформацию) к одному ролику одновременно только через один метод. А значит и время выполнения будет одинаковое. Но нам нужно, чтобы картинка появлялась, скажем, 2 секунды, а слайды менялись через каждые 10 секунд. Поэтому нам нужны 2 объекта MovieClip, один в другом, чтобы к одному применить один эффект, а к другому — второй. За появление будет отвечать метод fadeIn() класса Slide, а за перебор картинок будет отвечать класс Slider. Приведу код всего класса, чтобы разобрать его поподробнее

package ru.flexfactory
{
    import flash.display.*;
    import flash.net.URLRequest;
    import flash.events.*;
    
    import com.greensock.*; 
    import com.greensock.plugins.*;
    import flash.geom.Point;

    public class Slide extends MovieClip
    {
        private var sourceMC:MovieClip;
        private var durationFade:int;
        
        public function Slide(imagePath:String, durationFade:int = 1)
        {
            TweenPlugin.activate([AutoAlphaPlugin]);
            
            this.durationFade = durationFade;
            loadImage(imagePath);
        }
        
        private function loadImage(imagePath:String):void
        {
            var loader:Loader = new Loader();
            var request:URLRequest = new URLRequest(imagePath);
            loader.contentLoaderInfo.addEventListener(Event.COMPLETE, completeHandler);
            loader.contentLoaderInfo.addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler);
            loader.load(request);
        }
        
        private function completeHandler(event:Event):void
        {
            event.target.removeEventListener(Event.COMPLETE, completeHandler);
            event.target.removeEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler);
            
            var bitmap:Bitmap = event.target.content;
            bitmap.smoothing = true;
            sourceMC = new MovieClip();
            sourceMC.addChild(bitmap);    
            addChild(sourceMC);
            
            dispatchEvent(new Event(Event.COMPLETE));
        }
        
        private function ioErrorHandler(event:IOErrorEvent):void
        {
            trace(event.text);
        }
        
        public function fadeIn():void
        {
            if (sourceMC == null) return;
            TweenLite.from(sourceMC, durationFade, {autoAlpha:0});
        }
    }
}

Чтоб мы видим. В классе содержится 2 внутренние переменные. sourceMC — это тот объект MovieClip, к которому мы будем применять эффект появления. durationFade — длительность появления. В конструктор мы передаём путь к изображению и значение длительности появления. Активируем плагин AutoAlphaPlugin и вызываем метод загрузки изображения. Для загрузки мультимедийных файлов будем использовать класс Loader. Объекту этого класса нужно повесить слушателей для успешной и неудачной загрузки и вызвать метод load(). Файл загружается как объект класса Bitmap, который мы добавляем во внутренний sourceMC. Обязательно нужно свойству smoothing присвоить значение true, иначе не будет плавности при масштабировании изображения. Чтобы попусту не занимать память, нужно удалить слушателей загрузки изображения методом removeEventListener. Последняя строчка — это вызов события, которое будет сигнализировать о том, что файл загружен и обработан. Это событие мы перехватим в классе Slider, что поможет нам избежать ошибок от преждевременного добавления объекта Slide. Последний метод отвечает за плавное появление изображения.

И вот мы подошли к главному классу Slider. В этом классе всего три метода public, но достаточно много методов private. Изображения мы загрузим только один раз и будет хранить из в массиве slides, чтобы потом применять к ним эффект масштабирования. Ниже я привожу полный класс с подробными комментариями к коду:

package ru.flexfactory
{
    import flash.display.*;
    import flash.net.URLRequest;
    import flash.events.*;
    import flash.utils.Timer;

    import com.greensock.*;
    import com.greensock.easing.*;
    import com.greensock.plugins.*;
    import flash.geom.Point;

    public class Slider extends MovieClip
    {
        private var xmlData:XML;            //загруженный XML-файл
        private var countSlide:int = 0;     //число изобажений
        private var slide:Slide;            //переменная для ссылки на слайды
        private var slides:Array;           //массив слайдов
        private var timer:Timer;            //таймер для перебора слайдов
        private var i:int;                  
        private var options:Options;        //объект класса Options для хранения опций
        private var points:AlignPoint;      //объект класса AlignPoint для получения случайной точки

        //конструктор, в который мы передаём загруженный извне файл XML с данными
        public function Slider(xml:XML)
        {
            super();
            xmlData = xml;
            countSlide = xmlData.slide.length();
            slides = new Array(countSlide);
            options = new Options();
        }

        //метод для подготовки объекта и запуска таймера
        public function init():void
        {
            if (countSlide == 0) return;

            TweenPlugin.activate([TransformAroundPointPlugin]); 
            setOption();

            timer = new Timer(options.durationSlide * 1000);
            timer.addEventListener(TimerEvent.TIMER, timerHandler);
            
            points = new AlignPoint(stage.stageWidth,stage.stageHeight);

            i = 0;
            
            start();
        }

        //метод для сохранения входных параметров в объекте options
        //на самом деле можно было обойтись и без этого класса и просто обращаться к объекту xml, но
        //используя данный класс, нам не нужно будет вспоминать всю структуру xml файла каждый раз 
        //при необходимости, следовательно меньше вероятность допустить ошибку
        private function setOption():void
        {
            options.durationSlide = int(xmlData.options.duration_slide);
            options.durationFade = int(xmlData.options.duration_fade);
            options.minScale = Number(xmlData.options.min_scale);
            options.maxScale = Number(xmlData.options.max_scale);
        }

        //запускаем механизм
        private function start():void
        {
            timer.start();
            timerHandler(null);
        }

        //функция, которая выполняется через определённый в таймере промежуток времени
        private function timerHandler(event:TimerEvent):void
        {
            //берём объект из массива
            slide = slides[i];
            
            //если он не загружен, то загружаем и переходим к обработке
            if (slide == null)  
            {
                slide = new Slide(xmlData.*[i].path, options.durationFade);
                slides[i] = slide;
                slide.addEventListener(Event.COMPLETE, slideCompleteHandler);
            }
            //если загружен, то просто обрабатываем
            else 
            {
                slideCompleteHandler(null);
            }
        }

        //применяем эффект масштабирования
        private function slideCompleteHandler(event:Event):void
        {
            //если мы пришли в эту функцию через слушателя, то удаляем его
            if (event != null) event.target.removeEventListener(Event.COMPLETE, slideCompleteHandler);
            
            //масштаб по-умолчанию
            slide.scaleX = 1;
            slide.scaleY = 1;
            
            //если слайд ещё не добавлен в слайдер, то добавляем
            addIfOut();
            
            //выносим текущий слайд поверх остальных
            setChildIndex(slide, numChildren - 1);
            
            //запускаем эффект появления
            slide.fadeIn();
            
            //берём произвольную точку для выравнивания
            var point:Point = points.randomPoint;
            
            //выравниваем мувик относительно этой точки
            alignByPoint(slide, point);

            //этот кусок кода можно написать через if(), но тогда параметры метода нужно будет писать 2 раза
            //"правильное" программирование исключает копипаст, поэтому сделаем следующим образом
            //присваиваем переменной action метод, который выбираем случайно 50/50. 
            var action:Function = (Math.random() * 2 < 1) ? TweenLite.from : TweenLite.to;
            
            //выбираем произвольное значение масштабирования в пределах от minScale до maxScale
            var scale:Number = (Math.random() * (options.maxScale - options.minScale)) + options.minScale;
            
            //выполняем выбранный метод TweenLite
            action(slide, 
                   options.durationSlide + options.durationFade, 
                   {transformAroundPoint:{point:point, scaleX:scale, scaleY:scale}, 
                   ease:Linear.easeNone});

            //увеличиваем либо сбрасываем значение счётчика
            if (++i == countSlide) i = 0;
        }
        
        //если слайд ещё не добавлен в слайдер, то добавляем
        private function addIfOut():void
        {
            for (var j:int = 0; j < numChildren; j++)
            {
                if (slide == getChildAt(j)) return;
            }
            addChildAt(slide, 0);
        }
    
        //метод для выравнивания изображения относительно точки
        private function alignByPoint(slide:Slide, point:Point):void
        {
            if (point.x == 0) slide.x = 0;
            if (point.x == stage.stageWidth / 2) slide.x = -(slide.width - stage.stageWidth) / 2;
            if (point.x == stage.stageWidth) slide.x = -(slide.width - stage.stageWidth);

            if (point.y == 0) slide.y = 0;
            if (point.y == stage.stageHeight / 2) slide.y = -(slide.height - stage.stageHeight) / 2;
            if (point.y == stage.stageHeight) slide.y = -(slide.height - stage.stageHeight);
        }

        private function ioErrorHandler(event:IOErrorEvent):void
        {
            trace(event.text);
        }
        
        //метод для перехода на следующий слайд
        public function showNext():void
        {
            if (timer == null) return;   
            
            timer.reset();
            timer.start();
            timerHandler(null);
        }
        
        //метод для перехода на предыдущий слайд
        public function showPrev():void
        {
            if (timer == null) return;
            
            i -= 2;
            if (i < 0) i += countSlide;
            
            showNext();
        }
    }
}

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

Следующим шагом подготовим наш fla файл. В свойствах изменим значение FPS на 30 для более плавного движения. Также изменим размеры файла на 500х280, а цвет фона изменим на чёрный.

Свойства главного файла

В библиотеке создадим новый символ типа Кнопка с именем Arrow и нарисуем какую-нибудь стрелку. Добавим на монтажный стол два экземпляра этого символа слева и справа и назовём их arrowLeft и arrowRight соответственно. Чтобы присвоить имя экземпляру, необходимо выделить его и в свойствах вписать имя в поле Имя. Должно получиться что-то вроде этого

Вид монтажног стола

Нам осталось добавить нашу конструкцию в приложение. Это мы сделаем в самом классе Main, который мы создали в самом начале. При запуске приложения мы загружаем файл data.xml, в котором хранятся все данные и создаём объект Slider, в который передаём загруженные данные. Собственно и всё. Вот этот класс

package 
{
    import flash.display.MovieClip;
    import flash.net.*;
    import flash.events.*;
    import ru.flexfactory.*;

    public class Main extends MovieClip
    {
        //константа для хранения имени xml файла
        private static const XML_PATH:String = "data.xml";
        
        private var slider:Slider;
        
        public function Main()
        {
            addEventListener(Event.ADDED_TO_STAGE, init);
            
            //добавляем слушателей кнопкам
            arrowRight.addEventListener(MouseEvent.CLICK, nextHandler);
            arrowLeft.addEventListener(MouseEvent.CLICK, prevHandler);
        }
        
        private function init(event:Event):void
        {
            removeEventListener(Event.ADDED_TO_STAGE, init);
            
            var request:URLRequest = new URLRequest(XML_PATH);
            var loader:URLLoader = new URLLoader();
            loader.addEventListener(Event.COMPLETE, completeHandler);
            loader.addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler);
            loader.load(request);
        }
        
        private function completeHandler(event:Event):void
        {
            event.target.removeEventListener(Event.COMPLETE, completeHandler);
            event.target.removeEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler);
            
            var xml:XML = new XML(event.target.data);
            
            slider = new Slider(xml);
            
            //добавляем на экран с индексом 0, чтобы при отображении слайдов стрелки не оказались за изображением
            addChildAt(slider, 0);
            slider.init();
        }
        
        private function ioErrorHandler(event:IOErrorEvent):void
        {
            trace(event.text);
        }
        
        private function nextHandler(event:MouseEvent):void
        {
            slider.showNext();
        }
        
        private function prevHandler(event:MouseEvent):void
        {
            slider.showPrev();
        }
    }
}

Файл XML будет выглядеть так:

<?xml version="1.0" encoding="UTF-8"?>
<gallery>
    <slide>
        <id>1</id>
        <path>assets/pict01.jpg</path>
    </slide>
    <slide>
        <id>2</id>
        <path>assets/pict02.jpg</path>
    </slide>
    <slide>
        <id>3</id>
        <path>assets/pict03.jpg</path>
    </slide>
    <slide>
        <id>4</id>
        <path>assets/pict04.jpg</path>
    </slide>
    <options>
        <duration_slide>10</duration_slide>
        <duration_fade>2</duration_fade>
        <min_scale>0.8</min_scale>
        <max_scale>1.5</max_scale>
    </options>
</gallery>

Результат

Архив с готовым проектом

Увидимся...


Тэги: , , , , ,

Ссылка для вашего сайта.



Комментарии (31)

  • Julia Rietveld

    |

    Красота неописуемая. Спасибо за такое подробное описание. Добавьте, пожалуйста, кнопочку для печати страницы, чтобы можно было Вас читать с бумаги.
    Продолжайте в том же духе!

    Ответить

  • 1

    |

    9Reports of a sentimental return to Everton for Wayne Rooney will not go away.

    Ответить

  • luxury handbags

    |

    luxury handbags

    Flex Factory — блог, уроки, статьи flash flex для начинающих » Слайдшоу с эффектом Ken Burns

    Ответить

  • click through the up coming post

    |

    click through the up coming post

    Flex Factory — блог, уроки, статьи flash flex для начинающих » Слайдшоу с эффектом Ken Burns

    Ответить

  • mitutoyo digital caliper 12 Inch

    |

    mitutoyo digital caliper 12 Inch

    Flex Factory — блог, уроки, статьи flash flex для начинающих » Слайдшоу с эффектом Ken Burns

    Ответить

  • videogiochi storia

    |

    videogiochi storia

    Flex Factory — блог, уроки, статьи flash flex для начинающих » Слайдшоу с эффектом Ken Burns

    Ответить

  • original site

    |

    original site

    Flex Factory — блог, уроки, статьи flash flex для начинающих » Слайдшоу с эффектом Ken Burns

    Ответить

  • frye handbags

    |

    frye handbags

    Flex Factory — блог, уроки, статьи flash flex для начинающих » Слайдшоу с эффектом Ken Burns

    Ответить

  • hatsan air rifles for Raccoon

    |

    hatsan air rifles for Raccoon

    Flex Factory — блог, уроки, статьи flash flex для начинающих » Слайдшоу с эффектом Ken Burns

    Ответить

  • Artego Shampoo

    |

    Artego Shampoo

    Flex Factory — блог, уроки, статьи flash flex для начинающих » Слайдшоу с эффектом Ken Burns

    Ответить

  • liverwurst nutrition facts

    |

    liverwurst nutrition facts

    Flex Factory — блог, уроки, статьи flash flex для начинающих » Слайдшоу с эффектом Ken Burns

    Ответить

  • betsey johnson wallet macys

    |

    betsey johnson wallet macys

    Flex Factory — блог, уроки, статьи flash flex для начинающих » Слайдшоу с эффектом Ken Burns

    Ответить

  • More Bonuses

    |

    More Bonuses

    Flex Factory — блог, уроки, статьи flash flex для начинающих » Слайдшоу с эффектом Ken Burns

    Ответить

  • makita angle grinder ga4530

    |

    makita angle grinder ga4530

    Flex Factory — блог, уроки, статьи flash flex для начинающих » Слайдшоу с эффектом Ken Burns

    Ответить

  • marshmallows

    |

    marshmallows

    Flex Factory — блог, уроки, статьи flash flex для начинающих » Слайдшоу с эффектом Ken Burns

    Ответить

  • he said

    |

    he said

    Flex Factory — блог, уроки, статьи flash flex для начинающих » Слайдшоу с эффектом Ken Burns

    Ответить

  • foldable tote bag for travel

    |

    foldable tote bag for travel

    Flex Factory — блог, уроки, статьи flash flex для начинающих » Слайдшоу с эффектом Ken Burns

    Ответить

  • talking to

    |

    talking to

    Flex Factory — блог, уроки, статьи flash flex для начинающих » Слайдшоу с эффектом Ken Burns

    Ответить

  • lucky charms marshmallows

    |

    lucky charms marshmallows

    Flex Factory — блог, уроки, статьи flash flex для начинающих » Слайдшоу с эффектом Ken Burns

    Ответить

  • electriq android tv

    |

    electriq android tv

    Flex Factory — блог, уроки, статьи flash flex для начинающих » Слайдшоу с эффектом Ken Burns

    Ответить

  • Ilobeau`s blog

    |

    Ilobeau`s blog

    Flex Factory — блог, уроки, статьи flash flex для начинающих » Слайдшоу с эффектом Ken Burns

    Ответить

  • Come creare videogiochi

    |

    Come creare videogiochi

    Flex Factory — блог, уроки, статьи flash flex для начинающих » Слайдшоу с эффектом Ken Burns

    Ответить

  • best erotic asian movies

    |

    best erotic asian movies

    Flex Factory — блог, уроки, статьи flash flex для начинающих » Слайдшоу с эффектом Ken Burns

    Ответить

  • Asian action movies 2017

    |

    Asian action movies 2017

    Flex Factory — блог, уроки, статьи flash flex для начинающих » Слайдшоу с эффектом Ken Burns

    Ответить

  • asian shemale sex movies

    |

    asian shemale sex movies

    Flex Factory — блог, уроки, статьи flash flex для начинающих » Слайдшоу с эффектом Ken Burns

    Ответить

  • cheap luggage sets

    |

    cheap luggage sets

    Flex Factory — блог, уроки, статьи flash flex для начинающих » Слайдшоу с эффектом Ken Burns

    Ответить

  • Suggested Webpage

    |

    Suggested Webpage

    Flex Factory — блог, уроки, статьи flash flex для начинающих » Слайдшоу с эффектом Ken Burns

    Ответить

  • discount tobacco pipes

    |

    discount tobacco pipes

    Flex Factory — блог, уроки, статьи flash flex для начинающих » Слайдшоу с эффектом Ken Burns

    Ответить

  • ultrasonic cleaner for guns

    |

    ultrasonic cleaner for guns

    Flex Factory — блог, уроки, статьи flash flex для начинающих » Слайдшоу с эффектом Ken Burns

    Ответить

Добавить комментарий

Дополнительно