Статьи

Введение в Flash Camo: часть 1

Добро пожаловать на презентацию Flash Camouflage Framework . Flash Camo (для краткости) — это графическая структура, которая разбита на 3 основных области: «Декали», «CSS Parser» и «CamoDisplay». Эти системы могут быть использованы индивидуально или в сочетании с вашими потребностями. При совместном использовании они образуют мощный набор инструментов, помогающих создавать обложки и стилизовать любые приложения Flash Благодаря модульному подходу Camo, вы можете использовать как можно меньше или столько фреймворка, сколько захотите.

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




Мы собираемся воссоздать «Пусковую площадку Bobble Person Site» с http://jessefreeman.com .

1_bobblehead_preview

В этом уроке мы будем использовать Flex Builder. Если вы не работали во Flex Builder или вам нужна помощь в настройке проекта библиотеки Flex, ознакомьтесь с моим «Учебником по песочнице разработки Flash» ( часть 1 и часть 2 ). Из этого туториала вы узнаете, как загрузить и настроить Flash Camo из SVN. Для этого проекта мы будем использовать версию 2.2 фреймворка, вы можете проверить это здесь:

http://flash-camouflage.googlecode.com/svn/tags/FlashCamo_2.2.0_beta

2_flash_camo_svn

Создайте новый проект с именем BobbleHeadApp и убедитесь, что вы связали его с Flash Camo SWC, который мы проверили на шаге 1.

3_new_project

Вам также понадобятся следующие папки ресурсов, чтобы начать. Загрузите их отсюда и поместите в папку html-template. Я включил обновленный файл html-шаблона, связанный с swfObject, вместе с макетом наших папок с внешними ресурсами.

4_bobble_project

Теперь, когда у нас есть настроенный проект и ресурсы, давайте начнем с нашего класса документов (BobbleHeadApp.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
package
{
    import flash.display.Loader;
    import flash.display.Sprite;
    import flash.display.StageAlign;
    import flash.display.StageScaleMode;
    import flash.events.Event;
    import flash.net.URLRequest;
 
    [SWF( width=»600″, height=»400″, backgroundColor=»#3A3E4A», framerate=»31″ )]
 
    public class BobbleHeadApp extends Sprite
    {
 
        private var fontSWF : Loader;
 
        public function BobbleHeadApp()
        {
            configureStage( );
            loadFonts( );
        }
 
        private function configureStage() : void
        {
            stage.align = StageAlign.TOP_LEFT;
            stage.scaleMode = StageScaleMode.NO_SCALE;
        }
 
        private function loadFonts() : void
        {
            fontSWF = new Loader( );
            fontSWF.contentLoaderInfo.addEventListener( Event.COMPLETE, onFontsLoaded );
            fontSWF.load( new URLRequest( «swfs/fonts/FontLibrary.swf» ) );
        }
 
        private function onFontsLoaded(event : Event) : void
        {
            fontSWF.contentLoaderInfo.removeEventListener( Event.COMPLETE, onFontsLoaded );
            init( );
        }
 
        protected function init() : void
        {
            trace( «Hello World» );
        }
    }
}

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


Ошибка № 2044: необработанное SecurityErrorEvent :. text = Ошибка # 2140: Нарушение изолированной программной среды безопасности: … SWF-файлы локальной с файловой системой и локальной с сетью не могут загружать друг друга.

Если это произойдет с вами, добавьте следующее «-use-network = false» в аргументы компилятора вашего проекта. Это позволит обойти любые проблемы безопасности Sandbox, которые вы можете получить при локальном тестировании.

Теперь вы должны быть готовы скомпилировать SWF и взглянуть на окно вывода. Вы увидите «Hello World»:

5_hello_world

Если вы проверяете соединения вашего браузера, шрифт swf должен быть загружен:

6_connections

Нам понадобится центральное место для хранения и доступа к нашим свойствам CSS. Прежде чем мы сделаем это, давайте поговорим о CamoPropertySheet. Возможно, вы использовали CSS с Flash в прошлом, но это нечто совершенно новое и уникальное для Flash Camo. Пользовательский анализатор CSS Camo, «CamoPropertySheet» (находится в пакете «camo.core.property»), выходит далеко за рамки собственного класса StyleSheet, поддерживая наследование стилей, псевдо-селекторы и объединяя стили на лету. Цель CamoPropertySheet — создать стили, которые можно применять к любому из ваших классов, а не только к TextFields. CSS — отличный способ определить свойства вашего класса во внешнем файле, и Camo помогает преобразовать эти стили CSS в пары свойство / значение, которые можно применить к любому объекту. Camo также имеет класс CamoPropertySheetManager (находится внутри пакета «camo.core.managers») для обработки нескольких CamoPropertySheets в одном приложении.

Поскольку нам нужны все наши классы для доступа к CamoPropertySheetManager, мы собираемся создать Singleton . Этот синглтон будет принудительно использовать один экземпляр нашего CamoPropertySheetManager. Я не большой поклонник синглетонов, но в подобных ситуациях это неизбежное зло. Нам понадобится один экземпляр класса, отвечающий за управление и возврат запрошенных PropertySelectors. Позже я объясню, что такое ProperySelector и как он будет функционировать в нашем приложении.

Давайте создадим новый класс с именем «GlobalPropertySheetManager» и поместим его в пакет «com.jessefreeman.managers».

7_create_global_pms

Вот код класса:

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
package com.jessefreeman.managers
{
    import camo.core.managers.CamoPropertySheetManager;
 
    public class GlobalPropertySheetManager
    {
 
        public static const INIT : String = «init»;
        private static var __instance : CamoPropertySheetManager;
     
        public function GlobalPropertySheetManager(enforcer : SingletonEnforcer)
        {
            if (enforcer == null)
            {
                throw new Error( «Error: Instantiation failed: Use GlobalDecalSheetManager.instance instead.» );
            }
        }
     
        public static function get instance() : CamoPropertySheetManager
        {
            if(GlobalPropertySheetManager.__instance == null)
            {
                GlobalPropertySheetManager.__instance = new CamoPropertySheetManager( );
            }
            return GlobalPropertySheetManager.__instance;
        }
    }
}
 
internal class SingletonEnforcer
{
}

Существует много способов создания Singleton, и обсуждение немного выходит за рамки данного руководства. Давайте быстро поговорим о том, что здесь происходит. Поскольку классы ActionScript 3 не могут иметь частные конструкторы, нет способа скрыть конструктор класса. Вместо того, чтобы разрешать создание класса с помощью обычной «новой» конструкции, мы будем принудительно вызывать GlobalPropertySheetManager.instance, чтобы получить ссылку на класс. Получатель экземпляра проверяет, был ли класс уже создан, если класс не существует, он создает новый CamoPropertySheetManager. Если экземпляр существует, он вернет ссылку на этот экземпляр. Это гарантирует, что у нас всего 1 экземпляр CamoPropertySheetManager во всем нашем приложении. Теперь мы готовы начать загрузку в нашем CSS.

CamoPropertySheetManager не имеет возможности загружать CSS, вместо этого мы предоставим механизм загрузки и скажем ему проанализировать загруженные данные. Мы начнем с создания нашего метода загрузки, используя URLLoader. Добавьте следующие методы в наш класс документов после функции «onFontsLoaded»:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private function loadPropertySheet() : void
{
    trace( «Loading CSS» );
 
    propSheet = GlobalPropertySheetManager.instance;
 
    var loader : URLLoader = new URLLoader( );
    loader.addEventListener( Event.COMPLETE, onPropertySheetLoad );
    loader.load( new URLRequest( «css/main.properties.css» ) );
}
 
private function onPropertySheetLoad(event : Event) : void
{
 
    var target : URLLoader = event.target as URLLoader;
    target.removeEventListener( Event.COMPLETE, onPropertySheetLoad );
 
    propSheet.parseCSS( «global.properties», target.data );
 
    trace( «PropertySheetSelectors», propSheet.getPropertySheet( «global.properties» ).selectorNames );
 
    init( );
}

Вам также необходимо добавить следующие свойства в класс документа:

1
private var propSheet : CamoPropertySheetManager;

Затем импортируйте следующие классы:

1
2
3
import flash.net.URLLoader;
import camo.core.managers.CamoPropertySheetManager;
import com.jessefreeman.managers.GlobalPropertySheetManager;

Наконец, мы просто изменим наш метод onFontsLoaded для вызова loadPropertySheet, заменив:

1
init( );

с:

1
loadPropertySheet();

Поскольку URLLoader может загружаться в любой текстовый файл, мы просто загружаем наш собственный пользовательский CSS, а затем передаем его экземпляру CamoPropertySheetManager, который мы получили из GlobalPropertySheetManager. Обратите внимание, как мы вызвали parseCSS и передали имя для нашего PropertySheet вместе с только что загруженными строковыми данными. Это все, что вам нужно сделать, чтобы Camo проанализировал ваш CSS. Позже мы поговорим о том, как мы выводим наши стили, но здесь немного больше информации о системе PropertySheet в камуфляже.

После анализа CSS вы можете извлечь массив имен селекторов, вызвав метод selectedNames. Селектор представляет имя определенного стиля CSS и его коллекцию значений. Метод «getSelector» возвращает селектор и его свойства как «PropertySelector». Новые селекторы могут быть добавлены в CamoPropertySheet путем вызова «newSelector» и передачи имени селектора и экземпляра PropertySelector. Наконец, вы можете продублировать CamoPropertySheet, вызвав «клон». Мы рассмотрим это подробнее, когда начнем создавать наши компоненты.

Сейчас самое время проверить, работает ли наш CSS-парсер. Давайте откроем файл css в «html-tempalte / css / main.properties.css» и добавим следующий стиль:

1
2
3
4
5
6
7
8
9
.TestStyle
{
    x: 100px;
    y: 100px;
    width: 100px;
    height: 100px;
    background-color: #ff0000;
    border: 1px solid #000000;
}

Если вы работаете в Flex Builder и изменяете файлы в папке html-template, вам необходимо очистить проект. Вы можете сделать это, перейдя в Project> Clean … и выбрав проекты, которые вы хотите очистить. Когда вы очищаете проект, он делает новую копию папки html-template в папку bin. Допустим, вы изменили что-то в html-шаблоне, а затем добавили новую строку кода. Flex Builder автоматически преформует чистку. Такая ситуация возникает только тогда, когда вы редактируете файл в html-шаблоне и не делаете очистку. Поверьте мне, этот шаг сэкономит вам бесчисленные часы разочарований, пытаясь выяснить, почему ваши изменения не отображаются в браузере. Я обычно сопоставляю чистое действие проекта с параметром + command + c, так как использую его очень часто.

8_clean_menu

Теперь, если вы перекомпилируете swf и посмотрите на вывод, вы должны увидеть имя селектора «.TestStyle» рядом с трассировкой «PropertySheetSelectors».

9_teststyle_trace

Давайте попробуем восстановить наш стиль и отследить его. Заменить:

1
trace( «PropertySheetSelectors», propSheet.getPropertySheet( «global.properties» ).selectorNames );

внутри onCSSLoad из нашего класса Doc с этим:

1
trace( «Test Selector», propSheet.getSelector(«.TestStyle»));

Теперь, когда вы компилируете, вы должны увидеть следующее:

1
Test Selector .TestStyle{x:100;height:100background-color:#ff0000;border:1 solid #000000;selectorName:.TestStyle;y:100;width:100;}

Это строковое значение нашего PropertySelector. Выглядит как объект, верно? Ну, это потому, что это так! CSS-парсер Camo преобразует селекторы CSS в нативные объекты. Важно отметить, что каждое значение по-прежнему является строкой. Позже мы поговорим о том, как преобразовать эти строки в типизированные значения, чтобы вы могли применить их к любому объекту или классу в вашем приложении. Давайте начнем строить фундамент для нашего проекта.

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

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

Абстрактные классы, как и наш синглтон, не должны напрямую создаваться. Думайте об абстрактном классе как об интерфейсах, но с кодом в них. Мы просто устанавливаем несколько функций, которые понадобятся всем компонентам, и каждый компонент добавляет дополнительную логику, которая им необходима. Мы собираемся назвать наш абстрактный класс «AbstractComponent» и поместить его в пакет «com.jessefreeman.components».

10_create_abstract_comp

Давайте посмотрим на код:

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
package com.jessefreeman.components
{
    import camo.core.display.CamoDisplay;
    import camo.core.property.PropertySelector;
 
    import com.jessefreeman.managers.GlobalPropertySheetManager;
 
    import flash.errors.IllegalOperationError;
 
    public class AbstractComponent extends CamoDisplay
    {
 
        protected static const ID_DELIMITER : String = » «;
        protected var defaultSelectorNames : Array;
 
        public function AbstractComponent(self : AbstractComponent, id : String)
        {
            if(self != this)
            {
                //only a subclass can pass a valid reference to self
                throw new IllegalOperationError( «Abstract class did not receive reference to self. » + className + » cannot be instantiated directly.» );
            }
            else
            {
                parseStyleNames( id );
                init( );
            }
        }
     
        protected function parseStyleNames(id : String) : void
        {
            defaultSelectorNames = id.split( ID_DELIMITER );
            this.id = defaultSelectorNames.pop( );
            // clean up selectors
            defaultSelectorNames.unshift( «.» + className );
            defaultSelectorNames.push( «#» + this.id );
        }
 
        protected function init() : void
        {
            var prop : PropertySelector = GlobalPropertySheetManager.instance.getSelector.apply( null, defaultSelectorNames );
 
            applyProperties( prop );
        }
    }
}

Несмотря на то, что это небольшой класс, здесь много чего происходит. Начнем с того, что он расширяет CamoDisplay. Обычно мы расширяем Sprite или MovieClip для наших классов отображения, но, поскольку мы хотим использовать BoxModel во Flash Camo, нам нужно создать из CamoDisplay. Мы поговорим о BoxModel и CamoDisplay чуть позже.

Если вы проверите конструктор, вы заметите, что мы запрашиваем два элемента: self и id. Проходя через конструктор, мы проверяем, что экземпляр класса можно создать только путем передачи его ссылки обратно в конструктор. Вы увидите, как это работает с нашими «конкретными» компонентами (конкретный класс — это любой полностью функциональный класс, расширяющий абстрактный класс). Другое свойство id важно, потому что мы будем использовать его для поиска идентификатора компонента из css. Как только проверка Abstract прошла, мы вызываем метод parseStyleName.

Метод parseStyleNames делает что-то особенное для нас. Если вы работали в html и css, вы можете знать, что вы можете перегрузить идентификатор или тег класса, указав несколько имен селекторов CSS, разделенных пробелом. В этом методе мы позволяем вам добавить несколько идентификаторов, разделенных пробелом, и мы разбиваем их на отдельные идентификаторы. Последний идентификатор становится основным идентификатором класса, и мы автоматически добавляем «#» к нему. Мы также добавляем имя класса из получателя CamoDisplay, называемого «className», в наш список имен селекторов. Давайте посмотрим на функцию init, чтобы увидеть, как мы ее используем.

В функции init мы делаем две вещи; получить наши селекторы из GlobalPropertySheetManager и затем применить свойства css к самому компоненту с помощью метода applyProperties. Посмотрите, как мы вызываем метод getSelector в GlobalPropertySheetManager. Эта часть может заставить вашу голову взорваться, но не волнуйтесь, я гарантирую, что в конце следующих нескольких шагов ваш код скомпилируется и будет работать нормально, независимо от того, полностью ли вы понимаете, что происходит.

Прежде всего, мы хотим получить экземпляр CamoPropertySheetManager, поэтому мы вызываем свойство экземпляра GlobalPropertySheetManager. Помните, что это Singleton, и у нас есть только один экземпляр CamoPropertySheetManager. Затем мы вызываем его функцию «getSelector», но вместо того, чтобы передавать имена каждого селектора, мы вызываем функцию функции «getSelector». Я знаю, что это безумие, но это что-то встроенное в язык. Смотрите «getSelector» — это функция отдыха во Flash. Эта функция не имеет заданного количества параметров, поэтому вы можете передать столько элементов, сколько захотите. Вот как на самом деле выглядит функция в исходном коде:

1
public function getSelector( … selectorNames) : PropertySelector

Видите «… selectorNames»? Это говорит функции, что вы получите любое количество имен селекторов, так что будьте готовы. Поскольку мы не можем просто передать массив в метод «getSelector» (потому что он просто увидит его как переменную массива 1), мы должны вызвать специальную функцию «apply» для передачи массива аргументов. Мораль этой истории заключается в том, что вы можете передать столько имен селекторов, сколько захотите, в функцию «getSelector», и анализатор CSS объединит их все в один PropertySelector. Если вы хотите больше узнать о выражениях rest во Flash, проверьте это .

Теперь это не так, давайте посмотрим на пример, чтобы проиллюстрировать, что будет делать этот класс.

Мы собираемся выполнить быстрый тест, чтобы показать, как работают AbstractComponent и BoxModel внутри CamoDisplay. Создайте класс с именем «SimpleDisplay» в пакете «com.jessefreeman.components».

11_create_simple_display

Вот код класса:

01
02
03
04
05
06
07
08
09
10
11
12
package com.jessefreeman.components
{
 
    public class SimpleDisplay extends AbstractComponent
    {
 
        public function SimpleDisplay(id : String = «BoxModelDisplay»)
        {
            super( this, id );
        }
    }
}

Это очень простой класс, он просто расширяет AbstractComponent и передает ссылку на себя в super (для того, чтобы пройти проверку AbstractComponent), а также передает свой идентификатор. Давайте проверим это, вернувшись к нашему классу Doc и добавив следующее после трассировки Hello World внутри функции init:

1
2
var component:SimpleDisplay = new SimpleDisplay(«BoxModelDisplay»);
addChild(component);

и импорт:

1
import com.jessefreeman.components.SimpleDisplay

Теперь, если вы скомпилируете swf, вы ничего не увидите, но есть экземпляр SimpleDisplay. Зайдите в свой CSS-файл и измените имя «.TestStyle» на «.SimpleDisplay». Теперь перекомпилируйте, и вы должны увидеть свой SimpleDisplay.

12_boxmodel_test

Удивительно, правда? SimpleDisplay автоматически нашел имя класса CSS и применил его значения. Любой экземпляр SimpleDisplay теперь автоматически применяет CSS-стиль «.SimpleDisplay» к себе. Давайте добавим следующий стиль в наш лист CSS:

1
2
3
4
5
#BoxModelDisplay
{
    background-color: #00ff00;
    border-top: 10px;
}

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

13_boxmodel_test_2

Это очень мощный способ настроить внешний вид вашего приложения. Теперь вы можете использовать CSS для стилизации любого класса, который расширяет AbstractComponent. Все это стало возможным благодаря BoxModelDisplay Камо. «BoxModelDisplay» позволяет нам применять Margin, Padding, Border и Background (Color / Image). BoxModel Camo работает точно так же, как CSS. Вот схема:

14_box_model

Как вы помните, наш AbstractComponent расширил CamoDisplay, который, в свою очередь, расширил BoxModel. Что ж, CamoDisplay не только наследует все макеты BoxModel, но также добавляет несколько дополнительных функций. Вы можете прочитать обо всех свойствах BoxModel и CamoDisplay в папке docs вашей проверки Flash Camo.

Мы собираемся создать простой компонент, который будет обрабатывать все текстовые метки, которые нам нужны в нашем приложении. Это также хороший компонент, помогающий проиллюстрировать, как работает ProperySelector. Для начала создайте новый класс с именем «Label» в пакете «com.jessefreeman.components».

15_create_label

Вот код:

001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
package com.jessefreeman.components
{
    import camo.core.enum.CSSProperties;
 
    import flash.text.StyleSheet;
    import flash.text.TextField;
    import flash.text.TextFormat;
 
    public class Label extends AbstractComponent
    {
 
        protected var _styleSheet : StyleSheet;
        protected var textField : TextField = new TextField( );
        protected var proxyTextFormat : TextFormat = new TextFormat( );
        protected var _textAlign : String = CSSProperties.LEFT;
 
        public function set autoSize(value : String) : void
        {
            textField.autoSize = validateAutoSize( value );
            invalidate( );
        }
 
        public function set antiAliasType(value : String) : void
        {
            textField.antiAliasType = validateAntiAliasType( value );
            invalidate( );
        }
 
        public function set embedFonts(value : Boolean) : void
        {
            textField.embedFonts = value;
            invalidate( );
        }
 
        public function set sharpness(value : Number) : void
        {
            textField.sharpness = value;
            invalidate( );
        }
 
        public function get text() : String
        {
            return textField.text;
        }
 
        public function set text(value : String) : void
        {
            textField.text = value;
            invalidate( );
        }
 
        public function set textAlign(value : String) : void
        {
            _textAlign = value;
            invalidate( );
        }
 
        public function set textFieldWidth(value : Number) : void
        {
            textField.width = value;
            invalidate( );
        }
 
        public function set textFieldHeight(value : Number) : void
        {
            textField.height = value;
            invalidate( );
        }
 
        public function set textFieldAlign(value : String) : void
        {
            proxyTextFormat.align = value;
            invalidate( );
        }
 
        public function set fontFace(value : String) : void
        {
            font = value;
            invalidate( );
        }
 
        public function set fontSize(value : Number) : void
        {
            size = value;
            invalidate( );
        }
 
        public function set font(value : String) : void
        {
            proxyTextFormat.font = value;
            invalidate( );
        }
 
        public function set color(value : uint) : void
        {
            proxyTextFormat.color = value;
            invalidate( );
        }
 
        public function set size(value : Number) : void
        {
            proxyTextFormat.size = value;
            invalidate( );
        }
 
        public function set letterSpacing(value : Number) : void
        {
            proxyTextFormat.letterSpacing = value;
            invalidate( );
        }
 
        public function Label( id : String = «label» )
        {
            super( this, id );
        }
 
        override protected function init() : void
        {
            textField.selectable = false;
            textField.autoSize = «left»;
            addChild( textField );
            super.init( );
        }
 
        override protected function draw() : void
        {
            proxyTextFormat.align = _textAlign;
            textField.defaultTextFormat = proxyTextFormat;
            textField.setTextFormat( proxyTextFormat );
            super.draw( );
        }
 
        public function setTextFormat(format : TextFormat, beginIndex : int = — 1, endIndex : int = — 1) : void
        {
            textField.setTextFormat( format, beginIndex, endIndex );
        }
 
        public static function validateAntiAliasType(value : String) : String
        {
            switch (value)
            {
                case CSSProperties.ADVANCED:
                    return value;
                    break;
                default:
                    return CSSProperties.NORMAL;
                    break;
            }
        }
 
        public static function validateAutoSize(value : String) : String
        {
            switch (value)
            {
                case CSSProperties.LEFT:
                case CSSProperties.RIGHT:
                case CSSProperties.CENTER:
                    return value;
                    break;
                default:
                    return CSSProperties.NONE;
                    break;
            }
        }
    }
}

Как видите, этот компонент метки функционирует как TextField с помощью композиции . Я делаю это, создавая экземпляр TextField и используя сеттеры для обновления его значений. Это может показаться чрезмерным убийством, но, поступая таким образом, я использую две возможности Flash Camo. Прежде всего, я расширяю набор классов AbstractDisplay> CamoDisplay> BoxModelDisplay, чтобы я получил стили CSS и BoxModel «бесплатно». Кроме того, мне больше не нужно создавать отдельный TextFormat для стилизации TextField и применять его каждый раз, когда происходят изменения. Этот компонент позволяет мне просто применять свойства TextFormat и автоматически обновляет свойства экземпляров TextField. Это важно, когда мы смотрим на то, как применяются PropertySelectors.

Я уже показал вам, как наш AbstractComponent автоматически применяет стили css к нашим компонентам, но давайте посмотрим, что происходит за кулисами в CamoDisplay, когда мы вызываем «applyProperties». Вот как выглядит код:

1
2
3
4
5
6
7
8
public function applyProperties(style: PropertySelector):void
{
    clearProperties();
     
    PropertyApplierUtil.applyProperties(this, style);
 
    invalidate();
}

Здесь происходит несколько вещей. Во-первых, все свойства очищены от CamoDisplay. Затем мы используем служебный класс PropertyApplierUtil, чтобы применить PropertySelector к экземпляру CamoDisplay. Ранее я упоминал, что PropertySelectors — это объекты, созданные из селекторов CSS. Значения объекта — это строки, но нам понадобится способ преобразовать размер шрифта в число. Эта утилита автоматизирует процесс, анализируя типы значений открытых переменных и получателей класса, а затем преобразует соответствующие значения PropertySelector в правильный тип. Вот пример.

Допустим, вы хотите установить размер шрифта для нашего компонента Label. У вас может быть стиль, который выглядит следующим образом:

1
2
3
4
.Label
{
    size: 30;
}

Когда вы применяете PropertyLelector «.Label» к экземпляру Label (помните, что это делается автоматически для нас), PropertyApplierUtil ищет свойство с именем «size» в экземпляре Label. Как только это находит, это проверяет значение свойств. Поскольку у нас есть метод get с именем size, который принимает числовое значение, утилита автоматически преобразует значение размера PropertySelector в число и устанавливает его в нашем компоненте. Вам не нужно делать ничего особенного, утилита обрабатывает строки, числа, массивы, объекты, цвета (единицы измерения) и некоторые другие типы. Пока ваша общедоступная переменная имеет тип, который утилита может преобразовать, любые подходящие свойства будут установлены автоматически. Мы увидим это в действии на следующем этапе.

Давайте перейдем к нашему Doc Class и заменим нашу демонстрацию SimpleDisplay, заменив функцию init следующим:

1
2
3
4
5
protected function init() : void
{
    trace( «Hello World» );
    createLabel();
}

Затем создайте новую функцию под нашей функцией init:

1
2
3
4
5
private function createLabel() : void
{
    label = new Label( «siteLabel» );
    addChild( label );
}

Не забудьте добавить следующий параметр:

1
private var label : Label;

А также наш импорт:

1
import com.jessefreeman.components.Label;

Наконец, нам нужно добавить следующие стили в наш CSS-файл:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
.Label
{
    font: Arial Black;
    size: 20px;
    embedFonts: true;
    color: #ffffff;
    anti-alias-type: advanced;
}
 
#siteLabel
{
    text: ANATOMY OF JESSE FREEMAN;
    x: 155;
    y: 60;
    size: 18px;
    rotation: 24px;
    letter-spacing: -2;
    text-height: 30px;
    alpha: .5;
}

Теперь, если вы скомпилируете SWF-файл, вы увидите ярлык с текстом «АНАТОМИЯ ДЖЕССИ ФРИМАНА».

16_label_preview

Этикетка автоматически использовала «.Label» вместе с «#siteLabel» и стилизовала себя. Видишь, как легко это было? Несколько строк кода, немного CSS, и мы сразу же приступим к работе.

Теперь у вас есть повторно используемый компонент Label, который можно стилизовать с помощью CSS. Этот класс ни в коем случае не закончен. Вы можете продолжать добавлять столько геттеров или сеттеров, сколько вам нужно, чтобы сделать эту метку полностью настраиваемой. Я просто предоставил вам базу для начала. Когда вы начнете расширять рамки, важно понять некоторые основные принципы рисования.

Вы, возможно, заметили из нашего компонента Label, что метод invalidate () вызывается после того, как мы изменили значение в любом из установщиков. Перерисовка BoxModel и дисплея очень дорога, поэтому, чтобы облегчить бремя постоянного обновления дисплея, CamoDisplay ждет до следующего кадра, чтобы перерисовать себя. Этот метод используется в собственных компонентах Adobe и очень эффективен для сокращения процессов с талией. Это то, что вы должны иметь в виду при расширении CamoDisplay и использовании аннулирования на ваших собственных установках.

Другой важный метод, о котором вы должны знать — это draw (). После того, как дисплей был признан недействительным, а Flash Player воспроизводит новый кадр, CamoDisplay автоматически вызывает функцию рисования. Если у вас есть пользовательская логика отображения, это лучшее место для ее размещения. После вызова «draw» флаг недействительности сбрасывается, и CamoDisplay не будет перерисовываться, пока не будет снова признан недействительным.

Если вы выполняете сложные анимации, например, меняете ширину и высоту или требует постоянных обновлений, вы можете переопределить аннулирование, вызвав «refresh ()» в любое время. Это немедленно заставит CamoDisplay перерисоваться. Используйте это, когда вам нужно иметь постоянные обновления дисплея, поскольку это может стать очень интенсивным для рендеринга.

Мы собираемся создать класс с именем «BobbleContainer» в пакете «com.jessefreeman.components»

17_create_bobble_container

Вот код:

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
package com.jessefreeman.components
{
    import com.jessefreeman.components.AbstractComponent;
 
    import flash.events.IEventDispatcher;
    import flash.events.MouseEvent;
 
    public class BobbleContainer extends AbstractComponent
    {
 
        protected var _active : Boolean;
        public var rollOverForce : Number = 1;
        public var noddingForce : Number = 0;
        public var noddingAngle : Number = 0;
        public var noddingRange : Number = 30;
        public var noddingHit : Number = .7;
        public var noddingDamp : Number = .985;
 
        public function BobbleContainer(id : String = «bobbleContainer»)
        {
            super( this, id );
        }
 
        public function set active(value : Boolean) : void
        {
            _active = value;
            if(_active)
                addEventListeners( this );
            else
                removeEventListeners( this );
        }
 
        public function get active() : Boolean
        {
            return _active;
        }
 
        override protected function init() : void
        {
            super.init( );
        }
 
        protected function addEventListeners(target : IEventDispatcher) : void
        {
            target.addEventListener( MouseEvent.ROLL_OVER, onRollOver );
            target.addEventListener( MouseEvent.ROLL_OUT, onRollOut );
        }
 
        protected function removeEventListeners(target : IEventDispatcher) : void
        {
            target.removeEventListener( MouseEvent.ROLL_OVER, onRollOver );
            target.removeEventListener( MouseEvent.ROLL_OUT, onRollOut );
        }
 
        public function calculateBobble() : void
        {
            if (noddingForce)
            {
                if(noddingForce < .05)
                    noddingForce = 0;
                rotation = Math.sin( noddingAngle ) * noddingForce * (noddingRange * .5);
                 
                noddingAngle += noddingHit * noddingForce;
                noddingForce *= noddingDamp;
            }
        }
 
        protected function onRollOver(event : MouseEvent) : void
        {
            if(noddingForce < (rollOverForce * .5))
            {
                noddingAngle = 0;
                noddingForce = rollOverForce;
            }
        }
 
        protected function onRollOut(event : MouseEvent) : void
        {
        }
    }
}

В нашем классе Doc давайте создадим экземпляр BobbleContainer для тестирования нашего кода. Давайте добавим следующую функцию ниже createLabel:

1
2
3
4
5
protected function createPerson() : void
{
    bobbleTest = new BobbleContainer(«TestContainer»);
    addChild(bobbleTest);
}

Вам понадобится следующее свойство:

1
private var bobbleTest : BobbleContainer;

затем импортировать:

1
import com.jessefreeman.components.BobbleContainer;

Чтобы заставить это работать, нам нужно добавить еще несколько вещей. В нашей функции init добавьте следующее:

1
2
createPerson();
addEventListener( Event.ENTER_FRAME, onEnterFrame );

Ниже функции инициализации добавьте:

1
2
3
4
private function onEnterFrame(event : Event) : void
{
    bobbleTest.calculateBobble();
}

Мы почти закончили. Добавьте следующее в ваш CSS-файл и нажмите compile:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
.BobbleContainer
{
    active: true;
    rollOverForce: 1;
    roll-over-force: 1;
    nodding-force: 0;
    nodding-angle: 0;
    nodding-range: 10;
    nodding-hit: .7;
    nodding-damp: .985;
}
 
.SimpleDisplay #TestContainer
{
    border: 4 solid white;
}

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

18_bobble_test

Теперь, когда я полностью представил CSS-парсер Flash Camo, с этого момента я буду называть его «Свойства / Свойство» вместо CSS. Вам рекомендуется думать о PropertySheets Camo как о естественном расширении настройки значений свойств ваших классов. Идите дальше и измените значения «nodding-range» или «nodding-hit», чтобы увидеть, как это работает. Представьте себе, что вы настраиваете все приложение без перекомпиляции! Не только это, но вы можете просто изменить стиль приложения, указав его на новый PropertySheet.

Прежде чем мы пойдем, давайте посмотрим на последнюю вещь. Обратите внимание на нашу таблицу свойств селектор «.SimpleDisplay #TestContainer». Flash Camo поддерживает селекторное наследование. Мы ссылаемся на «#TestContainer» как на предмет, а «.SimpleDisplay» будет элементом-предком. Если бы вы вызвали getSelector для #TestContainer, парсер автоматически вернет PropertySelector со свойствами из .SimpleDisplay и #TestContainer, объединенных вместе. Любые конфликтующие свойства будут переопределены предметным стилем. Довольно круто, верно?

Теперь мы готовы создать класс, который будет управлять нашими частями тела BobbleContainer. Давайте создадим класс с именем «BobblePerson» в пакете «com.jesseFreeman.components».

19_create_bobble_person

Вот код:

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
package com.jessefreeman.components
{
    import com.jessefreeman.components.AbstractComponent;
 
    public class BobblePerson extends AbstractComponent
    {
 
        protected var _partIds : Array = new Array( );
        protected var partInstances : Array = new Array( );
        public var partDisplayId : String;
 
        public function set partIds(value : Array) : void
        {
            _partIds = value;
            createParts( );
        }
 
        public function BobblePerson(id : String = «BobblePerson»)
        {
            super( this, id );
        }
 
        public function get partDisplay() : BobbleContainer
        {
            var index : int = _partIds.indexOf( partDisplayId );
             
            return (index != — 1) ?
        }
 
        override protected function init() : void
        {
            super.init( );
        }
 
        public function createParts() : void
        {
            var total : int = _partIds.length;
            var i : int;
            var part : BobbleContainer;
            var partID : String;
             
            for (i = 0; i < total ; i ++)
            {
                partID = _partIds[i];
                part = new BobbleContainer( partID );
                addChild( part );
                partInstances.push( part );
            }
        }
 
        public function calculateBobble() : void
        {
            var total : int = partInstances.length;
            for (var i : int = 0; i < total ; i ++)
            {
                BobbleContainer( partInstances[i] ).calculateBobble( );
            }
        }
    }
}

Как вы можете видеть здесь, у нас есть несколько методов, вращающихся вокруг настройки, создания и обновления частей тела. Этот класс создает детали после того, как установщику partIds присвоено значение. Мы делаем это через установщик, потому что мы будем использовать наш PropertySheet для передачи массива частей. Когда у нас есть список частей, вызывается метод createParts. Он перебирает массив partIds и создает новые экземпляры BobbleContainer. Каждому контейнеру присваивается идентификатор, который затем помещается в массив с именем partInstances, чтобы мы могли отслеживать все части.

Наша последняя функция для обновления деталей. Помните цикл ввода фрейма, который мы установили в классе Doc, который обновил демо BobbleContainer? Мы подключим этот цикл к методу CalcubleBobble от BobblePerson, и он будет управлять вызовом calcBobble для каждой из наших частей. Таким образом, в наше время в нашем приложении работает только один цикл ввода кадра.

Теперь мы готовы настроить этот класс. Давайте заменим несколько вещей в нашем классе документов, чтобы наш BobblePerson заработал. Начните с замены метода «createPerson» следующим:

1
2
3
4
5
protected function createPerson() : void
{
    bobblePerson = new BobblePerson( «BobblePerson» );
    addChild( bobblePerson );
}

Затем выполните поиск и замените «bobbleTest» на «bobblePerson».

20_find_and_replace

Затем замените:

1
private var bobblePerson : BobbleContainer;

с:

1
private var bobblePerson : BobblePerson;

Убедитесь, что вы также импортируете:

1
import com.jessefreeman.components.BobblePerson;

Наконец, вам нужно заменить весь файл PropertySheet следующим:

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
85
86
87
88
89
90
91
92
93
94
95
96
97
98
/* CSS file */
 
.BobblePerson
{
    part-ids: head body leftArm rightArm leftLeg leftFoot rightLeg;
    part-display-id: body;
}
 
.Label
{
    font: Arial Black;
    size: 20px;
    embedFonts: true;
    color: #ffffff;
    anti-alias-type: advanced;
}
 
#siteLabel
{
    text: ANATOMY OF JESSE FREEMAN;
    x: 155;
    y: 60;
    size: 18px;
    rotation: 24px;
    letter-spacing: -2;
    text-height: 30px;
    alpha: .5;
}
 
.BobbleContainer
{
    active: true;
    rollOverForce: 1;
    roll-over-force: 1;
    nodding-force: 0;
    nodding-angle: 0;
    nodding-range: 10;
    nodding-hit: .7;
    nodding-damp: .985;
    background-color: silver;
    background-color-alpha: .5;
}
 
#body
{
    x: 355;
    y: 229;
    width: 181;
    height: 144;
}
 
#head
{
    x: 449;
    y: 250;
    width: 139;
    height: 143;
}
 
#leftArm
{
    x: 381;
    y: 313;
    width: 195;
    height: 80;
}
 
#leftFoot
{
    x: 145;
    y: 213;
    width: 127;
    height: 80;
}
 
#leftLeg
{
    x: 263;
    y: 235;
    width: 125;
    height: 80;
}
 
#rightArm
{
    x: 419;
    y: 176;
    width: 184;
    height: 125;
}
 
#rightLeg
{
    x: 275;
    y: 176;
    width: 218;
    height: 171;
}

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

21_body_demo

У нас есть начало нашего сайта. Далее мы добавим метки к каждой части, чтобы мы могли отслеживать их.

Давайте перейдем к нашему классу BobbleContainer и добавим следующую функцию после init:

1
2
3
4
5
protected function createLabel() : void
{
    label = new Label( id + «Label» );
    addChild( label );
}

Затем измените функцию init, добавив следующее:

1
createLabel( );

над :

1
super.init( );

Вам также необходимо создать следующее свойство:

1
public var label : Label;

И импортируем наш класс Label:

1
import com.jessefreeman.components.Label;

Как вы можете видеть, мы создаем нашу новую метку и используем для идентификатора родительского класса «id +« Label »». Это позволяет нам дать уникальный идентификатор каждому созданному ярлыку, но все же позволяет нам стилизовать каждый из них через его Имя класса. Давайте добавим следующее в наш PropertySheet:

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
/* BobbleContainer Labels */
 
#bodyLabel
{
    alpha: 1;
    rotation: 20;
    x: -35;
    y: -30;
    text-align: center;
    auto-size: center;
     
}
 
#leftArmLabel
{
    text: bFreeDesign;
    x: -90;
    y: 25;
    rotation: -9;
    size: 18;
    letter-spacing: -3;
}
 
#leftFootLabel
{
    text: eMail;
    x: -78;
    y: -13;
    rotation: 6;
}
 
#leftLegLabel
{
    text: FlashBum;
    x: -95;
    y: -40;
    rotation: 9;
}
 
#rightArmLabel
{
    text: FlashArtOfWar;
    x: -80;
    y: -75;
    rotation: 32;
    font-size: 16;
    letter-spacing: -1;
}
 
#rightLegLabel
{
    text: iLikeToDream;
    x: -157;
    y: -83;
    rotation: 27;
}

Теперь, если вы скомпилируете, вы увидите, что все наши ярлыки работают и настраиваются с помощью CSS!

22_body_demo_2

Мы прошли много вещей в этом первом уроке. Лучше всего, если мы сделаем перерыв и позволим всему этому впитаться. Далее мы рассмотрим, как создать оболочку для нашего BobbleContainer с использованием декалей, добавить интерактивность, оптимизировать / очистить код и поговорить о развертывании. Будьте на связи!