Статьи

Увеличить свой собственный холст контента

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

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

Примечание. Этот учебник не имеет ничего общего с элементом HTML5 canvas!


Демо-версия — это приложение для просмотра, которое загружает свои данные из XML. Контент помещается на «холст», и пользователь может увеличить масштаб холста или щелкнуть любое изображение, чтобы расположить его по центру экрана. Такие параметры, как размер страницы, цвет / текстура фона и другие параметры, загружаются динамически. В качестве бонуса пользователь может создавать несколько страниц.

В этом уроке мы рассмотрим основные концепции создания такого приложения. Желательно хранить исходный код рядом с вами во время чтения учебника, потому что не каждая часть объясняется. Исходный код доступен для использования с FlashDevelop или с Flash CS3 Professional и выше.


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

Панель глобальных настроек безопасности

Если вы не знакомы с XML, ознакомьтесь с AS3 101: XML от Dru Kepple. Ниже показаны данные XML, соответствующие одной странице, заполненной одним изображением и одним текстовым полем. Указанные здесь настройки применяются ко всем страницам. Как видите, создание нескольких страниц и добавление к ним контента стало простым. К изображениям вы можете добавить заголовок и описание, которое видно при наведении мышью. Многострочный текст может быть добавлен и размер и стиль могут быть настроены. Каждое текстовое поле использует собственный шрифт, найденный в файле .fla.

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
<?xml version=»1.0″ encoding=»utf-8″ ?>
<data>
    <settings>
        <bg_color>0x000000</bg_color>
        <image_path>../images/</image_path>
         
        <viewer_width>580</viewer_width>
        <viewer_height>380</viewer_height>
        <content_width>2000</content_width>
        <content_height>1300</content_height>
         
        <viewer_bg_color>0xEEDEC0</viewer_bg_color>
        <viewer_line_color>0xC1C59C</viewer_line_color>
        <viewer_offset_x>10</viewer_offset_x>
        <viewer_offset_y>10</viewer_offset_y>
    </settings>
    <page id=»0″ name=»Holiday in Copenhagen»>
         
        <text>
            <content>
Hello there and welcome to the demonstration of the canvas application.
    Used as a subject here is my holiday to Copenhagen.
        Feel free to read the texts and sniff up the culture.
            </content>
            <size>23</size>
            <x>470</x>
            <y>50</y>
        </text>
         
        <image>
            <title>Church and lake</title>
            <description>The beautiful enviroment in Kastelskirken.</description>
            <url>Church_And_Lake.jpg</url>
            <scale>1</scale>
            <x>750</x>
            <y>500</y>
        </image>
         
    </page>
</data>

Используйте следующий код для настройки нашего приложения. Он почти полностью генерируется FlashDevelop с использованием шаблона «AS3 Project». Main — это класс документа, который является первым классом, загружаемым при запуске flash, и внутри Canvas мы создаем наше приложение.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
public class Main extends Sprite
{
    public function Main():void
    {
        if (stage) init();
        else addEventListener(Event.ADDED_TO_STAGE, init);
    }
         
    private function init(e:Event = null):void
    {
        removeEventListener(Event.ADDED_TO_STAGE, init);
        // entry point
             
        var canvas:Canvas = new Canvas(this);
    }
}

И упрощенная версия Canvas. Обратите внимание, что мы используем контейнер экранного объекта в качестве параметра, чтобы добавить этот дополнительный бит управления для добавления / удаления Canvas.

01
02
03
04
05
06
07
08
09
10
11
public class Canvas extends Sprite
{
    public function Canvas(container:DisplayObjectContainer)
    {
        // Save parameter locally
        this.container = container;
         
        // Add to container
        container.addChild(this);
    }
}

В этом приложении мы используем так называемый BulkLoader : библиотеку AS3, цель которой — облегчить и ускорить загрузку и управление сложными требованиями к загрузке. При первой загрузке мы используем его для получения файла XML и фонового изображения. Следующий код показывает, как загрузить BulkLoader и добавить два элемента в очередь загрузки. Функция Complete вызывается, когда все элементы загружены.

01
02
03
04
05
06
07
08
09
10
11
12
// Init bulk loader
loader = new BulkLoader(«Canvas»);
             
loader.add(«../background.jpg», { id:»background» } );
loader.add(«../settings.xml», { id:»xmldata» } );
             
// Loader events
loader.addEventListener(BulkLoader.COMPLETE, Complete, false , 0, true);
loader.addEventListener(BulkLoader.ERROR, HandleError, false, 0, true);
             
// Start loader
loader.start();

Мы сохраняем загруженные активы внутри класса для дальнейшего использования. BulkLoader предоставляет простой способ получить нужные нам элементы. Просто вызовите функцию, соответствующую типу объекта, и используйте строку идентификатора объекта. Статические переменные используются, чтобы сделать данные XML глобально доступными (например, доступ к статической переменной с помощью Canvas.viewerWidth ). Переменная path указывает, где будут находиться загруженные извне изображения. Изображения могут быть загружены только с собственного веб-сервера из-за ограничений безопасности.

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
// Get background image
g_background = (loader.getBitmap(«background»)).bitmapData;
             
// Get XML data
xmldata = loader.getXML(«xmldata»);
             
// Set static vars for easy access
viewerWidth = int(xmldata.settings.viewer_width);
viewerHeight = int(xmldata.settings.viewer_height);
viewerBgColor = uint(xmldata.settings.viewer_bg_color);
path = String(xmldata.settings.image_path);
customFont = new customFontClass();
contentWidth = int(xmldata.settings.content_width);
contentHeight = int(xmldata.settings.content_height);
             
// Remove complete listener
loader.removeEventListener(BulkLoader.COMPLETE, Complete);
             
// Remove all data references stored in the loader
loader.clear();
             
// Remove loader from memory
loader = null;
             
// Set up viewer
InitialiseViewer();

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

Исходный код этой демонстрации также включен в архив. Давайте объясним этот пример, начав с настройки параметров. Мы бы хотели, чтобы область просмотра была 305 x 230, а область содержимого, которая является размерами цветочного изображения, должна пытаться сохранить соотношение сторон области просмотра, чтобы она не деформировалась слишком сильно. Наш цветок 1000 х 750, что достаточно близко. Чтобы показать весь цветок в области просмотра, камера должна быть того же размера, что и цветок, который составляет 1000 x 750.

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

1
2
3
4
5
// Settings
viewerWidth = 305;
viewerHeight = 230;
contentWidth = 1000;
contentHeight = 750;

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

1
2
3
4
// Viewer container
viewerContainer = new Sprite();
viewerContainer.scrollRect = new Rectangle(0,0, viewerWidth, viewerHeight);
addChild(viewerContainer);

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

1
2
3
// Page scroller
var viewerScroller:Sprite = new Sprite();
viewerContainer.addChild(viewerScroller);

Добавить контент в viewerScroller . В этом случае изображение цветка.

1
2
3
4
5
6
// Content
content = new Sprite();
viewerScroller.addChild(content);
             
var bmp:Bitmap = new image();
content.addChild(bmp);

Теперь добавьте класс Camera . Хотя нам еще предстоит создать класс Camera , его конструктор будет принимать измерения средства просмотра в качестве параметра, а функция SetTarget имеет содержимое Sprite качестве параметра. Мы помещаем Camera в центр содержимого и даем ему первое обновление.

1
2
3
4
5
6
// Add virtual camera
cam = new Camera(viewerWidth,viewerHeight);
cam.SetTarget(viewerScroller);
cam.x = contentWidth / 2;
cam.y = contentHeight / 2;
cam.Update();

Имея весь этот код, мы можем визуализировать и управлять камерой. Внутри цикла Event.ENTER_FRAME вы можете запустить cam.Update() чтобы обновить вид с камеры. Однако более эффективно обновлять информацию только после внесения изменений. Изменяя cam.x и cam.y вы можете перемещать камеру, а с помощью cam.scaleX и cam.scaleY вы можете изменять масштаб. Это именно то, что делают клавиши клавиатуры в примере.


Здесь мы посмотрим на внутренности Camera . В функцию конструктора мы добавляем графику, которая необходима для манипулирования масштабом камеры. Мы также храним измерения содержимого средства просмотра локально внутри класса.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
public function Camera(width:int,height:int,initialZoom:Number = 1)
{
    // Sprite used to give the camera a width and height.
    g = new Shape();
    g.graphics.drawRect(0, 0, 10, 10);
    g.graphics.endFill();
    addChild(g);
             
    // Set dimensions of viewer rectangle
    tw = width;
    th = height;
             
    // Initial zoom
    this.scaleX = this.scaleY = initialZoom;
}

Затем прикрепите камеру к объекту. Камера становится такого же масштаба, как объект.

1
2
3
4
5
6
7
8
public function SetTarget(target:Sprite):void
{
    this.target = target;
             
    // adjust camera dimensions
    g.width = target.width;
    g.height = target.height;
}

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
public function Update():void
{
    cw = this.width;
    ch = this.height;
             
    tscaleX = tw / cw;
    tscaleY = th / ch;
             
    // put new scaling values
    mat.a = tscaleX;
    mat.d = tscaleY;
             
    // put new position (translation) values.
    // the camera position is made negative, because eg when the camera moves right, the page has to move left to accomodate.
    // cw and ch are added to move the page to the center of the viewing area
    // all positions are scaled accordingly
    mat.tx = (-mat.tx + cw / 2) * tscaleX;
    mat.ty = (-mat.ty + ch / 2) * tscaleY;
             
    target.transform.matrix = mat;
}

Мы возвращаемся к нашему заявлению. На этом этапе мы закладываем основу для так называемых объектов Page которые содержат холст с изображениями и текстом. Сначала создайте фон для всего приложения. Цвет указывается из XML.

1
2
3
4
5
6
// Background
var bg:Sprite = new Sprite();
bg.graphics.beginFill(xmldata.settings.bgcolor);
bg.graphics.drawRect(0, 0, stage.stageWidth, stage.stageHeight);
bg.graphics.endFill();
addChild(bg);

Как и в примере с Camera и цветком, мы используем pageContainer и pageScroller и Camera . Это должно выглядеть довольно знакомо.

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

Параметры, используемые Page являются

  • pageScroller : Sprite к которому будет добавлена Page
  • p : объект данных XML, содержащий всю информацию на определенной странице (все изображения и текст)
  • g_background : фоновая текстура
1
2
3
4
5
6
7
// Add pages
pages = new Array(xmldata.page.length());
for each(var p:XML in xmldata.page)
{
    var id:int = int(p.attributes()[0]);
    pages[id] = new Page(pageScroller, p, g_background);
}

Ниже показан конструктор Page . Мы просто сохраняем все параметры локально в классе для дальнейшего использования.

1
2
3
4
5
6
7
8
public function Page(container:DisplayObjectContainer,data:XML,background:BitmapData)
{
    this.id = id;
    this.container = container;
    this.data = data;
    this.background = background;
    this.title = String(data.attributes()[1]);
}

Теперь немного более интересных вещей. Функция Load Page начинается с того, что мы берем фоновую текстуру, которую мы загружали ранее, и оборачиваем ее поверх холста. Для этого нам нужно знать размер фонового изображения, которое мы зацикливаем, и размер всего холста. Создайте Sprite именем g_background который функционирует как графический контейнер. Добавьте сплошной фоновый цвет, чтобы предотвратить отображение линий между растровыми изображениями.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
var b:int = background.width;
var h:int = background.height;
var trueWidth:int = Canvas.contentWidth;
var trueHeight:int = Canvas.contentHeight;
             
// background layer
g_background = new Sprite();
addChild(g_background);
 
// A solid background color
var bg:Sprite = new Sprite();
bg.graphics.beginFill(Canvas.viewerBgColor);
bg.graphics.drawRect(0, 0, trueWidth, trueHeight);
bg.graphics.endFill();
g_background.addChild(bg);

Обтекание фоновой текстуры использует два цикла, только по вертикали и один по горизонтали. Это продолжает петлю, пока край не будет достигнут. Каждая плитка в четном положении — например, (2,2), (4,2), (4,6) и так далее — переворачивается с помощью scaleX или scaleY . Это делает текстуру плавно переходящей к следующей. Проверка четности числа выполняется с помощью оператора по модулю (%). Если остаток от числа после деления на 2 равен нулю, то это число должно быть четным. По краям мы trueWidth любую текстуру, которая выходит за пределы размеров содержимого, как указано в trueWidth и trueHeight . Важно, чтобы объект Page оставался в этом размере, так как увеличение его размера изменит соотношение сторон и приведет к деформации экрана.

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
// Add looped background image
var i:int, j:int;
while (i * b < trueWidth)
{
    j = 0;
    while (j * h < trueHeight)
    {
        // new bitmap
        var s:Bitmap = new Bitmap(background);
         
        // position
        sx = i * b;
        sy = j * h;
                 
        // alternate horizontal and vertical flip
        if (i % 2 != 0)
        {
            s.scaleX *= -1;
            sx += b;
        }
        if (j % 2 != 0)
        {
            s.scaleY *= -1;
            sy += h;
        }
                     
        // clip
        if (i * b + b > trueWidth || j * h + h > trueHeight)
        {
            var clipw:int = Math.min(trueWidth — i * b, b);
            var cliph:int = Math.min(trueHeight — j * h, h);
             
            var nbd:BitmapData = new BitmapData(clipw, cliph);
            nbd.copyPixels(background, new Rectangle(0, 0, clipw, cliph), new Point());
            s.bitmapData = nbd;
             
            if (s.scaleX == -1)
                sx -= b — clipw;
            if (s.scaleY == -1)
                sy -= h — cliph;
        }
                     
        // add bitmap to display list
        g_background.addChild(s);
        j++;
    }
    i++;
}

Результат повторяющегося фона должен быть примерно таким. Темные границы добавлены для ясности.

Повторение фона

Давайте начнем наше путешествие по заполнению страницы с добавления текста. AddText все записи XML с AddText «текст» и передайте их данные в функцию AddText .

1
2
3
4
5
texts = new Array();
for each(var text:XML in data.text)
{
    AddText(text);
}

Функция AddText выглядит следующим образом. Первая часть кода проверяет данные XML. Если некоторые поля не заполнены, к ним будет добавлено стандартное значение. QuickText — это класс, используемый для создания текстового поля с определенными параметрами. Наконец, текст должен быть исключен из взаимодействия с мышью с помощью mouseEnabled = false и mouseChildren = false .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
private function AddText(text:XML):void
{
    if (!text.font)
        text.font = null;
    if (!text.size)
        text.size = 12;
    if (!text.color)
        text.color = 0x000000;
    if (!text.bold)
        text.bold = 0;
    if (!text.italic)
        text.italic = 0;
                 
    var qt:QuickText = new QuickText(text.x, text.y, String(text.content), Canvas.customFont, int(text.size), uint(text.color), Boolean(text.bold), Boolean(text.italic));
    qt.blendMode = BlendMode.LAYER;
    texts.push(qt);
    addChild(qt);
     
    qt.mouseEnabled = false;
    qt.mouseChildren = false;
}

На следующем изображении показаны все параметры класса QuickText :

Опции класса QuickText.

И результат новой страницы, включая текст:

Страница с текстом

Первым шагом здесь является создание объектов Picture которые будут содержать данные XML, растровое изображение и некоторый описательный текст. Слушатели напрямую применяются для взаимодействия с мышью. Все эти объекты Picture затем сохраняются в массиве для быстрого доступа позже.

01
02
03
04
05
06
07
08
09
10
11
12
13
// grab all images
pictures = new Array();
for each(var image:XML in data.image)
{
    // new picture object with information in it
    var picture:Picture = new Picture(image);
    pictures.push(picture);
                 
    // add listeners to picture
    picture.addEventListener(MouseEvent.MOUSE_OVER, PictureMouseOver);
    picture.addEventListener(MouseEvent.MOUSE_OUT, PictureMouseOut);
    picture.addEventListener(MouseEvent.MOUSE_DOWN, PictureMouseDown);
}

А вот базовая версия класса Picture , просто содержащая данные XML. Picture расширяет Sprite , мы уже можем ее где-то позиционировать. Параметр масштаба проверяется перед использованием, потому что если пользователь пропустит его в XML, он вернет 0. Стандартное значение для масштаба — 1.

01
02
03
04
05
06
07
08
09
10
11
public function Picture(data:XML)
{
    title = data.title;
    description = data.description;
    url = data.url;
    page = data.page;
    x = data.x;
    y = data.y;
    if (Number(data.scale) != 0)
        imgscale = Number(data.scale);
}

Создайте новый BulkLoader как в первый раз, но теперь для загрузки пакетов изображений. Мы собираемся загружать 5 изображений одновременно и отображать их, когда все будет готово. Он будет продолжать получать новые изображения, пока все не будет сделано. Функция Complete вызывается при завершении каждой партии.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
// Create bulk loader.
loader = new BulkLoader(«page» + id);
loader.addEventListener(BulkLoader.COMPLETE, Complete, false, 0, true);<br /><br />
 
// set total amount of pictures
totalPictures = pictures.length;
             
// grab the first 5 pictures or the total amount of pictures if there are less.
i = 0;
while (i < Math.min(totalPictures,5))
{
    loader.add(String(Canvas.path + pictures[i].url), { id: «img» + i } );
    i++;
}
 
// Start loader
loader.start();

На этом этапе мы помещаем загруженные данные в объекты Picture . Свойство items загрузчика содержит все загруженные объекты. Обведите все эти объекты и проверьте, являются ли они изображением. Каждое Bitmap присваивается соответствующему объекту Picture и Picture добавляется на холст. Мы также удаляем загруженное изображение из списка элементов загрузчика, чтобы избежать конфликтов с более поздним пакетом загруженных изображений.

01
02
03
04
05
06
07
08
09
10
11
12
13
// Image batch loaded.
i = amountPicturesLoaded;
for each(var item:LoadingItem in loader.items)
{
    if (item.isImage())
    {
        pictures[i].SetImage(loader.getBitmap(item.id));
        loader.remove(item.id);
        AddPicture(pictures[i]);
        i++;
        amountPicturesLoaded++;
    }
}

И внимательнее посмотрим на функцию SetImage Picture .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
public function SetImage(ob:Bitmap):void
{
    // if no image data is loaded, show nothing
    if (ob == null)
        return;
             
    // save the data inside the class
    img = ob;
                 
    // show image
    addChild(img);
             
    // scale
    img.scaleX = img.scaleY = imgscale;
}

А добавить изображение на холст так же просто, как вызвать addChild .

1
2
3
4
private function AddPicture(pict:Picture):void
{
    addChild(pict);
}

Результат теперь становится:

Страница с текстом и изображениями

Добавление чего-либо в представление камеры выполняется путем добавления дочернего viewerScroller контейнер viewerScroller . Мы добавили viewerScroller к каждому объекту Page в качестве параметра, чтобы мы могли добавить его как дочерний элемент, вызвав функцию Show() объекта Page .

1
2
3
4
public function Show():void
{
    container.addChild(this);
}

Вернитесь к классу Canvas и вызовите функции Load () и Show (), когда мы хотим показать пользователю страницу. Если Page уже загружена, Load() вернется напрямую, поэтому никаких ненужных действий не будет сделано. Текущая Page мы показываем, сохраняется в классе как page . Эта ссылка будет важна для взаимодействия страниц.

01
02
03
04
05
06
07
08
09
10
11
private function ShowPage(nr:int):void
{
    // hide old page
    if (page)
        page.Hide();
                 
    // set new page
    page = pages[nr];
    page.Load();
    page.Show();
}

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

1
2
// Set magnify steplist
magnifyStepList[0] = Math.min(viewerWidth / contentWidth, viewerHeight / contentHeight);

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

1
page.hitField.addEventListener(MouseEvent.MOUSE_DOWN, MouseZoomIn);

После щелчка мышью мы хотели бы, чтобы камера уменьшила масштаб до положения увеличения в magnifyStepList и переместилась к точке на холсте, который мы щелкнули. Помните из примера, что по мере уменьшения камеры (уменьшения) масштаб на холсте становится больше. Вот почему мы уменьшаем целочисленный зум на значение один. Получить позицию мыши, которую мы нажали на холсте, легко с помощью page.mouseX и page.mouseY . Flash автоматически преобразует числа так, чтобы они отображались локально — это означает, что, если страница, например, уменьшена до 50 пикселей на 50% и вы нажмете наполовину, она вернет 1000 пикселей, даже если положение мыши в координатах шкалы намного меньше.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
private function MouseZoomIn(e:MouseEvent):void
{
    var pt:Point;
             
    if (!cam.bZooming && zoom > 0)
    {
        // Zoom in one step
        zoom—;
                 
        // New camera point.
        pt = new Point(page.mouseX, page.mouseY);
        CameraBounds(pt);
        cam.Zoom(pt.x, pt.y, magnifyStepList[zoom]);
    }
}

Чтобы держать область камеры внутри холста, нам нужно исправить положение в пределах камеры. Посмотрим на пример камеры еще раз для демонстрации этого. Камера центрирована вокруг себя, поэтому горизонтальное положение, например, должно оставаться в пределах 0 + camera half-width contentWidth - camera half-width а contentWidth - camera half-width . Надежный способ вычислить ширину камеры при увеличении — это contentWidth/2 * magnifyStepList[zoom] , потому что камера в исходном состоянии без увеличения имеет размер contentWidth (такой же, как у холста).

01
02
03
04
05
06
07
08
09
10
11
12
13
14
private function CameraBounds(pt:Point):void
{
    // horizontally
    if (pt.x < contentWidth/2 * magnifyStepList[zoom])
        pt.x = contentWidth/2 * magnifyStepList[zoom];
    else if (pt.x > contentWidth — contentWidth/2 * magnifyStepList[zoom])
        pt.x = contentWidth — contentWidth/2 * magnifyStepList[zoom];
 
    // vertically
    if (pt.y < contentHeight/2 * magnifyStepList[zoom])
        pt.y = contentHeight/2 * magnifyStepList[zoom];
    else if (pt.y > contentHeight — contentHeight/2 * magnifyStepList[zoom])
        pt.y = contentHeight — contentHeight/2 * magnifyStepList[zoom];
}

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

Границы камеры

Масштабирование выполняется путем добавления масштаба к камере. Мы используем класс Tweener и переход "easyOutQuint" чтобы сделать это гладко. bZooming — переменная, используемая, чтобы видеть, масштабирует ли камера уже или нет. Вы не можете снова увеличить страницу, пока она все еще занята, увеличивая или уменьшая масштаб. При каждом обновлении камеры вызывается функция Update , которая выполняет масштабирование контента.

01
02
03
04
05
06
07
08
09
10
11
12
public function Zoom(newx:int, newy:int, newscale:Number):void
{
    bZooming = true;
    Tweener.addTween(this, { time:2, x:newx, y:newy, transition:»easeOutQuint», scaleX:newscale, scaleY:newscale,
        onComplete:
        function():void
        {
            bZooming = false;
        },
        onUpdate: Update
    });
}

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
private function PictureMouseDown(e:MouseEvent):void
{
    var newScale:Number;
    var screenRatio:Number = Canvas.viewerWidth / Canvas.viewerHeight;
    var imgW:Number = Math.max(e.target.width * 1.05, Canvas.viewerWidth);
    var imgH:Number = Math.max(e.target.height * 1.05, Canvas.viewerHeight);
    var imgRatio:Number = e.target.img.width / e.target.img.height
             
    // Calculate image scaling
    if (imgRatio < 1)
        newScale = imgH / Canvas.contentHeight;
    else
        newScale = imgW / Canvas.contentWidth;
             
    Canvas.cam.Zoom(e.target.x + e.target.width/2,
                            e.target.y + e.target.height/2,
                            newScale);
                                     
    Canvas.cam.bLocked = true;
    PictureMouseDisable();
}

Основная концепция здесь заключается в том, что сначала необходимо определить соотношение сторон изображения. Если ширина изображения больше высоты, то значение imgRatio < 1 будет сохраняться, и наоборот, если высота больше ширины. Мы всегда будем масштабировать до самой большой части изображения, что означает, что если изображение имеет размер, например, 200x400px, мы будем обрабатывать изображение как квадрат 400×400. Еще одно добавление заключается в том, что мы сначала масштабируем изображение до 1,05, что означает, что оно становится на 5% больше Таким образом, изображение не касается сторон при увеличении. Чтобы вычислить масштаб изображения по сравнению с размером содержимого, мы делим его на высоту или ширину содержимого.

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

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

Увеличение изображения в действии

Если вы не заметили, при увеличении на странице вы можете переместить курсор мыши к краям экрана, чтобы прокрутить и просмотреть большую часть страницы. Код, показанный ниже, может показаться вам немного странным; это то, что я написал некоторое время назад для игрового движка в стиле RTS и с тех пор повторно использую его для всего, что требует прокрутки. Основными принципами здесь является то, что вы проверяете, где находится позиция мыши, и в случае, если она перемещается без определенного диапазона вокруг размеров ( mouse_scroll_areax_reduced и mouse_scroll_areay_reduced ), тогда она начнет двигаться в одну сторону со скоростью, пропорциональной тому, как далеко вы находитесь внутри этого диапазона. , Когда курсор мыши не находится в пределах диапазона, он будет перетаскивать прокрутку, чтобы в конечном итоге замедлить ее.

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
// Get the amount of scolling needed based on the mouse position
mx = viewerContainer.mouseX;
my = viewerContainer.mouseY;
if (mx < mouse_scroll_areax && mx > 0)
{
    scrollAmountX = ((((mx — mouse_scroll_areax) / mouse_scroll_areax_reduced) * mouse_scroll_factor) + .5) << 0;
}
else if ((viewerContainer.width — mx) < mouse_scroll_areax && mx < viewerContainer.width)
{
    scrollAmountX = (((1 — (viewerContainer.width — mx) / mouse_scroll_areax_reduced) * mouse_scroll_factor) + .5) << 0;
}
if (my < mouse_scroll_areay && my > 0)
{
    scrollAmountY = ((((my — mouse_scroll_areay) / mouse_scroll_areay_reduced) * mouse_scroll_factor) + .5) << 0;
}
else if ((viewerContainer.height — my) < mouse_scroll_areay && my < viewerContainer.height)
{
    scrollAmountY = (((1 — (viewerContainer.height — my) / mouse_scroll_areay_reduced) * mouse_scroll_factor) + .5) << 0;
}
                 
// Put drag on the scroll, so it does not keep on moving forever and slows down smoothly.
scollAmountX *= .95;
scrollAmountY *= .95;
                 
// Update camera position
cam.x += int(scrollAmountX);
cam.y += int(scrollAmountY);
                 
// Make sure the camera is within bounds
var pt:Point = new Point(cam.x, cam.y);
CameraBounds(pt);
cam.x = pt.x;
cam.y = pt.y;
 
// Update the camera view
cam.Update();

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

Границы прокрутки визуализируются.

Я считаю, что это суммирует все цели обучения, которые я поставил перед этим уроком. К сожалению, не все, что показано в приложении, могло быть обсуждено из-за объема учебного пособия, но я надеюсь, что вам удалось изучить основы XML-данных, заполнения страниц и манипуляции с камерой. Спасибо за прочтение!