«Резиновый дизайн» на Flash

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

Резиновый дизайн на Flash

День добрый. Этот урок будет полезен разработчикам Flash-сайтов. Кто сталкивался с подобной задачей, точнее пытался реализовать её во Flash Builder, тот наверняка испытывал затруднения в работе с векторной графикой. Но тем не менее во Flash Builder есть много очень полезных вещей. Одним из таких удобств для меня является привязка к границам приложения через свойства left, top, horizontalCenter и т.д. В этом уроке мы рассмотрим, как можно реализовать подобные привязки на чистом AS3 во Flash Professional.

Цель

  • Научиться применять абстрактные классы в проектах
  • Научиться переопределять методы и свойства
  • Реализовать «резиновый» дизайн на Flash

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

Реализовать возможность управлять положением и размерами графических объектов внутри приложения, используя привязки к границам приложения. Создать абстрактный класс ScreenObject и простой класс Screen, на которых будут базироваться все объекты приложения. Для объекта ScreenObject реализовать свойства left, top, right, bottom, horizontalCenter, verticalCenter по аналогии с Flex. Для класса Screen реализовать методы init() и release().

Решение

Начнём пожалуй с формулировки понятия "абстрактный класс". Экземпляр такого класса не может быть создан, но сам класс можно расширить до другого конкретного класса. Отличается от интерфейса тем, что в нём можно реализовать некоторые методы, а другие оставить нереализованными (абстрактными). На самом деле в AS3 нет понятия «абстрактный класс», поэтому можно создать класс, который будет псевдоабстракным, т.е. можно создать объект этого класса и ошибок компиляции при этом не будет возникать. Ошибка будет возникать на этапе выполнения при попытке вызвать абстрактный метод данного класса. Новичкам скорее всего пока практически ничего непонятно ) Но не пугайтесь незнакомых слов. Всё на самом деле просто и легко для понимания.

Запустим Adobe Flash Professional и создадим проект Flash. Назовём его FlexDesign. Создадим главный файл FlexDesign.fla и его класс FlexDesign.as. Как это сделать описывалось тут. Теперь перейдём к созданию базового класса ScreenObject. Для уникальности пространства имён, поместим этот класс в пакет ru.flexfactory.display. Сам класс будет расширять класс Sprite.

Рассмотрим немного класс Sprite. Это подходящий класс для вывода графики, но всё же он обладает некоторыми особенностями. Во-первых, он меняет значения width и height после того, как на него добавлен другой объект, либо выполнен один из его методов graphics.draw..., в обратном случае свойства width и height будут равны 0 и изменить их нельзя. Во-вторых, если программно изменить свойства width и height у отрисованного объекта Sprite, то выполниться масштабирование. Это легко проверить например на прямоугольнике с закруглёнными углами. Нам не удастся изменить размеры уже готового прямоугольника не нарушив радиуса закругления угла (по аналогии кнопки во Flex). Т.е. объект просто сплющится, либо растянется вместо того, чтобы изменить размеры. Всё это мы учтём при создании нашего класса. Приведу полный листинг этого класса и после этого разберём.

package ru.flexfactory.display
{
    import flash.display.Sprite;

    public class ScreenObject extends Sprite 
    {
        protected const ERROR_MESSAGE_BASE_CLASS:String = 
		    "Вызван метод базового класса, который должен быть переопределён в подклассах";
        
        protected var _left:Object;
        protected var _right:Object;
        protected var _top:Object;
        protected var _bottom:Object;
        protected var _horizontalCenter:Object;
        protected var _verticalCenter:Object;
        protected var _width:Number = 1;
        protected var _height:Number = 1;
        
        public function get left():Object
        {
            return _left;
        }

        public function set left(value:Object):void
        {
            _left = value;
        }

        public function get right():Object
        {
            return _right;
        }

        public function set right(value:Object):void
        {
            _right = value;
        }

        public function get top():Object
        {
            return _top;
        }

        public function set top(value:Object):void
        {
            _top = value;
        }

        public function get bottom():Object
        {
            return _bottom;
        }

        public function set bottom(value:Object):void
        {
            _bottom = value;
        }
        
        public function get horizontalCenter():Object
        {
            return _horizontalCenter;
        }

        public function set horizontalCenter(value:Object):void
        {
            _horizontalCenter = value;
        }

        public function get verticalCenter():Object
        {
            return _verticalCenter;
        }

        public function set verticalCenter(value:Object):void
        {
            _verticalCenter = value;
        }
        
        //Переопределение свойств "ширина" и "длина"
        override public function set width(value:Number):void
        {
            _width = value;
            redrawBaseRect();
        }
        
        override public function set height(value:Number):void
        {
            _height = value;
            redrawBaseRect();
        }
        
        public function ScreenObject()
        {
            super();
            
            redrawBaseRect();
        }
        
        protected function redrawBaseRect():void
        {
            graphics.clear();
            drawBaseRect();
        }
        
        protected function drawBaseRect():void
        {
            throw new Error(ERROR_MESSAGE_BASE_CLASS);
        }
        
        public function setPosition():void
        {
            if (!parent) 
                return;            
            
            positioning(left, horizontalCenter, right, "_width", "width", "x");
            positioning(top, verticalCenter, bottom, "_height", "height", "y");
            
            redrawBaseRect();
        }
        
        protected function positioning(lowValue:Object, middleValue:Object, 
                                       highValue:Object, sizeProp:String, 
                                       parentProp:String, posProp:String):void
        {
            if (valueIsNumeric(lowValue) && valueIsNumeric(highValue))
            {
                var newValue:int = parent[parentProp] - Number(lowValue) - Number(highValue);
                this[sizeProp] = Math.max(newValue, 0);
            }
            
            if (valueIsNumeric(middleValue))
            {
                this[posProp] = parent[parentProp] / 2 - this[sizeProp] / 2 + Number(middleValue);
            }
            else if (valueIsNumeric(lowValue))
            {
                this[posProp] = Number(lowValue);
            }
            else if (valueIsNumeric(highValue))
            {
                this[posProp] = parent[parentProp] - this[sizeProp] - Number(highValue);
            }            
        }
        
        protected function valueIsNumeric(value:Object):Boolean
        {
            var result:Boolean = false;
            
            if (value != null)
                result = (!isNaN(Number(value))) ? true : false; 

            return result; 
        }
    }
}

Что можно сказать. Большую половину этого класса занимают геттеры и сеттеры. Я придерживаюсь позиции их использовать в своих проектах, чтобы соблюдать принцип ООП. Но на самом деле в отличие от языка Java, в AS3 это не критично, т.к. public переменную очень легко превратить в protected и создать ей геттер и сеттер. Во Flash Builder 4.5 и выше это можно сделать с помощью комбинации Ctrl+1. После геттеров и сеттеров находятся 2 переопределённых метода width и height. Это нужно для того, чтобы запомнить необходимые размеры и перерисовать объект вместо того, чтобы его масштабировать.

Далее следуют конструктор и 2 внутренних метода для отображения графики: redrawBaseRect():void и drawBaseRect():void. Они нужны для перерисовки графики после изменения размеров объекта, если это необходимо. Причём второй метод применяется в том случае, если изображение формируется программным путём. Но если Вы обратите внимание на тело этого метода, то увидите, что кроме строки throw new Error(ERROR_MESSAGE_BASE_CLASS) там ничего нет. Для чего я это сделал? Для того, чтобы сделать данный класс псевдоабстрактным. Т.е. при вызове данного метода в экземпляре данного класса возникнет ошибка. А значит мы не сможем использовать этот класс для создания объектов, а только лишь для создания подклассов, в которых один из этих методов должен быть переопределён.

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

Есть один ньюанс. Для того, чтобы выравнивание было корректно, графика в объекте должна быть в начале координат. Если же у вас есть готовый MovieClip, начало которого находится не в центре, можно использовать такой приём:

var rect:Rectangle = movieClip.getBounds(movieClip);
movieClip.x -= rect.x;
movieClip.y -= rect.y;

Перейдём к классу Screen. Тут мы впервые применим тип Vector. Что это за тип. Это просто типизированный массив, т.е. массив, который может хранить только объекты указанного типа, в нашем случае ScreenObject. В этот массив мы будем добавлять ссылки на объекты. Само добавление мы внедрим в метод addChild. Аналогично в методе removeChild пропишем удаление ссылок на эти объекты из вектора. Также, учитывая перечисленные выше особенности класса Sprite, будем хранить значение width и height в отдельных переменных. В методах init() и release() будем просто добавлять либо удалять объекты с данного спрайта. Также добавим метод resize, для вызова метода setPosition у всех дочерних объектов. Ниже приведён полностью класс Screen.as.

package ru.flexfactory.display
{
    import flash.display.*;

    public class Screen extends Sprite 
    {
        protected var _objects:Vector.<ScreenObject>;
        protected var _width:Number;
        protected var _height:Number;
        
        public function Screen()
        {
            super();
            
            _objects = new Vector.<ScreenObject>;
        }
        
        override public function addChild(child:DisplayObject):DisplayObject
        {
            if (child is ScreenObject)
                _objects.push(child as ScreenObject);
                
            return super.addChild(child);
        }
        
        override public function removeChild(child:DisplayObject):DisplayObject
        {
            if (child is ScreenObject)
            {
                var index:int = _objects.indexOf(child as ScreenObject);
                
                if (index > -1) 
                    _objects.splice(index, 1);
            }
            
            return super.removeChild(child);
        }
        
        public function init():void
        {
            for each (var object:ScreenObject in _objects)
            {
                if (!contains(object))
                    super.addChild(object);
            }
        }
        
        public function release():void
        {
            for each (var object:ScreenObject in _objects)
            {
                super.removeChild(object);
            }
        }
        
        public function resize(width:Number, height:Number):void
        {
            _width = width;
            _height = height;
                
            for each (var object:ScreenObject in _objects)
            {
                object.setPosition();
            }
        }
        
        override public function get width():Number
        {
            return _width;
        }
        
        override public function get height():Number
        {
            return _height;
        }
    }
}

Чтобы протестировать нашу конструкцию нужно создать несколько конкретных подклассов ScreenObject. Например, это будут некоторые прямоугольники разного цвета со скруглёнными углами. В подклассах нам нужно всего лишь переопределить метод drawBaseRect(), чтобы нарисовать графику. Всё остальное уже имеется. Ниже представлены 3 класса для красного, зелёного и синего прямоугольника.

RedObject.as

package ru.flexfactory.display
{
    public class RedObject extends ScreenObject 
    {
        override protected function drawBaseRect():void
        {
            graphics.beginFill(0xFF0000);
            graphics.drawRoundRect(0, 0, _width, _height, 10, 10);
            graphics.endFill();
        }
    }
}

GreenObject.as

package ru.flexfactory.display
{
    public class GreenObject extends ScreenObject 
    {
        override protected function drawBaseRect():void
        {
            graphics.beginFill(0x00FF00);
            graphics.drawRoundRect(0, 0, _width, _height, 10, 10);
            graphics.endFill();
        }
    }
}

BlueObject.as

package ru.flexfactory.display
{
    public class BlueObject extends ScreenObject 
    {
        override protected function drawBaseRect():void
        {
            graphics.beginFill(0x0000FF);
            graphics.drawRoundRect(0, 0, _width, _height, 10, 10);
            graphics.endFill();
        }
    }
}

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

package 
{
    import flash.display.*;
    import ru.flexfactory.display.*;
    import flash.events.Event;
    
    public class FlexDesign extends MovieClip
    {
        protected var screen:Screen;
        
        public function FlexDesign()
        {
            stage.scaleMode = StageScaleMode.NO_SCALE;    //Выключаем масштабирование при изменении размера
            stage.align = StageAlign.TOP_LEFT;            //Выравниваем по левому верхнему углу
            
            screen = new Screen();
            addChild(screen);
            
            stage.addEventListener(Event.RESIZE, resizeHandler);      //Вешаем слушателя для события изменения размера клипа
            
            drawScreen();
            
            resizeHandler(null);    //После создания и добавления всех объектов - выравниваем
        }
        
        protected function resizeHandler(event:Event):void
        {
            screen.resize(stage.stageWidth, stage.stageHeight);
        }
        
        private function drawScreen():void
        {
            var red:ScreenObject = new RedObject();
            red.width = 400;
            red.height = 100;
            red.right = 20;
            red.top = 20;
            
            var green:ScreenObject = new GreenObject();
            green.left = 20;
            green.right = 20;
            green.top = 150;
            green.bottom = 150;
            
            var blue:ScreenObject = new BlueObject();
            blue.width = 500;
            blue.height = 100;
            blue.bottom = 20;
            blue.horizontalCenter = 0;
            
            screen.addChild(red);
            screen.addChild(green);
            screen.addChild(blue);
        }
    }
}

Готово! Далее можно расширить функционал. Можно добавить к примеру использование процентных значений. Но это уже Вам домашнее задание )

Результат

Запустите готовый файл и измените размеры окна

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

Тэги: , , , ,

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



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

  • Александр

    |

    Спасибо за урок! Очень круто!

    Ответить

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

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