Статьи

Создайте виджет слайдера фотографий AS3 для сайтов социальных сетей

Сегодня мы собираемся создать виджет Photo Slider, который можно разместить на сайтах социальных сетей, таких как Orkut или MySpace . Этот виджет будет основан на Google Open Social API и содержит приложение Flash, которое будет показывать все фотографии из ваших общедоступных фотоальбомов в виде слайд-шоу.

«Классы плагинов» будут обрабатывать переходы слайд-шоу, и этот урок будет включать в себя написание двух из этих «плагинов».


Чтобы построить и протестировать виджет, нам понадобится пара вещей. Я перечислил их ниже:

  • Flash CS4
  • Редактор JavaScript / PHP / XML (Eclipse / Aptana)
  • Некоторое пространство для размещения (с поддержкой PHP)
  • Учетная запись на сайте социальной сети, которая поддерживает OpenSocial (по крайней мере версия 0.8, для этого урока я буду использовать Orkut).
  • Статус разработчика на этом сайте социальной сети.

Для части этого урока по XML / JavaScript / PHP я буду использовать Aptana, так как это мой выбор по выбору, но, конечно, подойдет любой редактор. Я выбрал Orkut, потому что Orkut – это Google, а OpenSocial – это … Google. Для этого урока нам нужна последняя версия API OpenSocial (Orkut поддерживает версию 0.9).

Прежде чем мы начнем, мы должны убедиться, что все настроено и настроено так, как мы этого хотим. Для проверки этого виджета нам понадобится статус разработчика в Orkut. Перейдите по адресу http://sandbox.orkut.com/SandboxSignup.aspx и заполните форму. Вы получите письмо, подтверждающее ваш новый статус. Будучи разработчиком, вы можете добавлять виджеты в свой профиль, которые еще не одобрены Orkut. В целях безопасности эти виджеты доступны только вам. Позже я более подробно покажу, как добавить виджет в Orkut.

Далее, поскольку нам нужен общедоступный фотоальбом, добавьте несколько фотографий в новый альбом и опубликуйте его.

Мы собираемся создать этот виджет снизу вверх, поэтому первое, что мы собираемся сделать, это написать XML-файл виджета. Если это ваше первое знакомство с OpenSocial, я бы посоветовал вам зайти на http://www.opensocial.org/ и почитать об этом. Короче говоря, OpenSocial – это фреймворк, разработанный Google, который может быть реализован на сайтах социальных сетей, что облегчает разработчикам создание приложений для них.

Обычно виджет состоит из файла XML и некоторых дополнительных ресурсов. Этот XML-файл и дополнительные ресурсы должны быть размещены где-то на сервере, поэтому нам нужно веб-пространство. Файл XML для этого виджета будет состоять из двух частей; спецификации XML и код JavaScript, которые будут извлекать фотографии из ваших альбомов из вашего профиля и передавать их в приложение Flash.

Расположение файла XML не является ракетостроением. Он содержит 2 узла, ModulePrefs и Content, которые важны для нас. На этом шаге я расскажу о первом, ModulePrefs.

Этот узел определяет переменные, которые необходимы для описания виджета. Я не думаю, что мне нужно что-то объяснять о первых, я надеюсь, но если вы запутались, пожалуйста, посетите веб-сайт OpenSocial для получения дополнительной информации.

Последний, однако, имеет большее значение. С этим узлом мы заявляем, что нам требуется как минимум версия 0.8 OpenSocial API, иначе он не будет работать. Для этого приложения это действительно единственное требование, которое нам понадобится, но есть много других, которые вы можете определить здесь. Опять же, если вы хотите узнать больше, OpenSocial.org – это то, что вам нужно.

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
<?xml version=”1.0″ encoding=”UTF-8″?>
<Module>
    <ModulePrefs
            title=”FlashTuts Widgets”
            author=”Gerb Sterrenburg”
            author_email=”gerbster@gmail.com”
            description=”FlashTuts Widget”
            height=”300″
            width=”400″
            scaling=”false”
            scrolling=”false”
            screenshot=”http://85.17.135.135/~gerbster/projects/FlashTutsWidget/screenshot.png”
            thumbnail=”http://85.17.135.135/~gerbster/projects/FlashTutsWidget/thumbnail.png”>
        <Require feature=”opensocial-0.8″/>
    </ModulePrefs>
    <Content type=”html”>
        <![CDATA[
            <script type=”text/javascript”>
         
                // Fetches all of the viewer’s albums that are publicly viewable (ie
                // “shared with everyone”
                 
                var masterAlbumArray = new Array();
                 
                function fetchAlbums()
                {
                    var req = opensocial.newDataRequest();
                    var idspec = opensocial.newIdSpec({ “userId” : “OWNER”, “groupId” : “SELF” });
         
                    req.add(req.newFetchAlbumsRequest(idspec), ‘ownerAlbums’);
                    req.send(fetchAlbumsHandler);
                };
         
                // Callback function, executed when orkut finishes fetching the viewer’s
                // public albums
                function fetchAlbumsHandler(resp)
                {
                    var ownerAlbumsResp = resp.get(‘ownerAlbums’);
         
                    if (!ownerAlbumsResp.hadError())
                    {
                        var ownerAlbums = ownerAlbumsResp.getData();
             
                        // every album is an object inside the main object ‘viewerAlbums’
                        // we’re going to create one big album which we’re gonna feed to
                        // the flash application
                        ownerAlbums.each(
                            function(album)
                            {
                                addAlbumToMasterAlbum(album.getId());
                            }
                        );
                      }
                };
         
                // Fetches all photos from the album with the passed ID
                function addAlbumToMasterAlbum(albumId)
                {
                  var req = opensocial.newDataRequest();
                  var idspec = opensocial.newIdSpec({ “userId” : “OWNER”, “groupId” : “SELF” });
         
                  req.add(req.newFetchMediaItemsRequest(idspec, albumId), ‘albumPhotos’);
                  req.send(addAlbumToMasterAlbumHandler);
                };
         
                // Callback function, executed when orkut finishes fetching the
                // requested media items
                function addAlbumToMasterAlbumHandler(resp)
                {
                    var albumPhotosResp = resp.get(‘albumPhotos’);
         
                    if (!albumPhotosResp.hadError())
                    {
                        var albumPhotos = albumPhotosResp.getData();
         
                        albumPhotos.each(
                            function(photo)
                            {
                                masterAlbumArray.push(photo.getField(‘url’));
                            }
                        );
                         
                        // now that all the Photo’s are in
                        // the masterAlbum, launch the Flash App
                         
                        launchFlashApp();
                    }
                };
         
                // show the flash app
                function launchFlashApp()
                {
                    var content = ‘<embed src=”http://85.17.135.135/~gerbster/projects/FlashTutsWidget/widget.swf” width=”400″ height=”300″ quality=”high” bgcolor=”000000″ pluginspage=”http://www.macromedia.com/go/getflashplayer” allowScriptAccess=”always” flashvars=”” type=”application/x-shockwave-flash”>’;
                     
                    document.getElementById(“flashTutsWidgetDiv”).innerHTML = content;
                };
                 
                function retrieveMasterAlbum()
                {
                    return masterAlbumArray;
                };
         
                // Execute fetchAlbums function when gadget loads
                gadgets.util.registerOnLoadHandler(fetchAlbums);
              </script>
         
              <div id=”flashTutsWidgetDiv” style=”width:380;height:400″><img src=”http://85.17.135.135/~gerbster/projects/FlashTutsWidget/loader.gif” /> Loading…</div>
               
            ]]>
      </Content>
</Module>

Теперь мы подошли к действительно интересной части XML-файла – узлу контента. Как упоминалось выше, OpenSocial – это API, написанный на JavaScript, который предоставляет разработчикам доступ ко всем видам данных на сайте социальной сети.

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

1
// Fetches all of the viewer’s albums that are publicly viewable (ie // “shared with everyone” var masterAlbumArray = new Array(); function fetchAlbums() { var req = opensocial.newDataRequest(); var idspec = opensocial.newIdSpec({ “userId” : “OWNER”, “groupId” : “SELF” }); req.add(req.newFetchAlbumsRequest(idspec), ‘ownerAlbums’); req.send(fetchAlbumsHandler); }; // Callback function, executed when orkut finishes fetching the viewer’s // public albums function fetchAlbumsHandler(resp) { var ownerAlbumsResp = resp.get(‘ownerAlbums’); if (!ownerAlbumsResp.hadError()) { var ownerAlbums = ownerAlbumsResp.getData(); // every album is an object inside the main object ‘viewerAlbums’ // we’re going to create one big album which we’re gonna feed to // the flash application ownerAlbums.each( function(album) { addAlbumToMasterAlbum(album.getId()); } ); } }; // Fetches all photos from the album with the passed ID function addAlbumToMasterAlbum(albumId) { var req = opensocial.newDataRequest(); var

Общая идея этого куска кода не очень сложна. Мы определим массив, который мы заполним URL-адресами всех фотографий в общедоступных альбомах. Если это будет сделано, мы покажем приложение Flash, которое затем получит массив, который мы только что создали.

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

Абонент:

1
2
3
4
5
6
7
8
function fetchAlbums()
{
    var req = opensocial.newDataRequest();
    var idspec = opensocial.newIdSpec({ “userId” : “OWNER”, “groupId” : “SELF” });
 
    req.add(req.newFetchAlbumsRequest(idspec), ‘ownerAlbums’);
    req.send(fetchAlbumsHandler);
};

Обработчик:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
// Callback function, executed when orkut finishes fetching the viewer’s
// public albums
function fetchAlbumsHandler(resp)
{
    var ownerAlbumsResp = resp.get(‘ownerAlbums’);
     
    if (!ownerAlbumsResp.hadError())
    {
        var ownerAlbums = ownerAlbumsResp.getData();
         
        // every album is an object inside the main object ‘viewerAlbums’
        // we’re going to create one big album which we’re gonna feed to
        // the flash application
        ownerAlbums.each(
            function(album)
            {
                addAlbumToMasterAlbum(album.getId());
            }
        );
    }
};

Я не хочу вдаваться в подробности, касающиеся OpenSocial, поскольку он не является основной темой этого руководства. Но чтобы дать вам некоторые подсказки о том, что здесь происходит, я объясню это в общих чертах.

В среде OpenSocial у вас будет две роли, а именно «владелец» и «зритель». Само собой разумеется, что владелец – фактический владелец профиля, а затем зритель, ну, это человек, который посещает. Если вы собираетесь запрашивать некоторые данные, вам нужно указать, чьи данные вы хотите; зритель или владелец.

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

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

1
2
3
4
5
6
7
8
9
// Fetches all photos from the album with the passed ID
function addAlbumToMasterAlbum(albumId)
{
    var req = opensocial.newDataRequest();
    var idspec = opensocial.newIdSpec({ “userId” : “OWNER”, “groupId” : “SELF” });
     
    req.add(req.newFetchMediaItemsRequest(idspec, albumId), ‘albumPhotos’);
    req.send(addAlbumToMasterAlbumHandler);
};

Обработчик:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Callback function, executed when orkut finishes fetching the
// requested media items
function addAlbumToMasterAlbumHandler(resp)
{
    var albumPhotosResp = resp.get(‘albumPhotos’);
     
    if (!albumPhotosResp.hadError())
    {
        var albumPhotos = albumPhotosResp.getData();
         
        albumPhotos.each(
            function(photo)
            {
                masterAlbumArray.push(photo.getField(‘url’));
            }
        );
         
        // now that all the Photo’s are in
        // the masterAlbum, launch the Flash App
         
        launchFlashApp();
    }
};

Примечание: мы получаем только URL из объекта фотографии, но это еще не все. Для получения полного списка всех свойств, которые вы можете получить, таких как комментарии и статистика, перейдите на opensocial.org.

Последняя строка описанной выше функции-обработчика вызывает функцию “launchFlashApp”.

1
launchFlashApp();

Это делает именно то, что подразумевает название, оно помещает приложение flash внутри тега div, определенного в нижней части узла содержимого. После загрузки флэш-приложение вызовет функцию «retrieveMasterAlbum».

1
2
3
4
5
6
7
// show the flash app
function launchFlashApp()
{
    var content = ‘<embed src=”http://85.17.135.135/~gerbster/projects/FlashTutsWidget/widget.swf” width=”400″ height=”300″ quality=”high” bgcolor=”000000″ pluginspage=”http://www.macromedia.com/go/getflashplayer” allowScriptAccess=”always” flashvars=”” type=”application/x-shockwave-flash”>’;
     
    document.getElementById(“flashTutsWidgetDiv”).innerHTML = content;
};

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

1
2
// Execute fetchAlbums function when gadget loads
gadgets.util.registerOnLoadHandler(fetchAlbums);

Теперь, когда файл XML готов, нам нужно сделать еще одну вещь, прежде чем мы сможем начать с флэш-части руководства. Ограничения безопасности флэш-плеера не позволяют ему получать доступ к данным растрового изображения, загруженного из другого домена. Поскольку нам нужны эти растровые данные для эффектов, которые мы собираемся добавить позже, нам нужно решение этой проблемы. Решение приходит в виде небольшого кусочка PHP. Этот скрипт будет действовать как прокси между сайтом социальных сетей и нашим приложением flash.

Это действительно просто, просто передайте URL этому сценарию, и он получит и отобразит содержимое этого URL. Таким образом, флэш-приложение будет думать, что изображение получено с нашего собственного сервера, и нам не нужно беспокоиться об ограничении безопасности.

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
<?php
 
    $post_data = $HTTP_RAW_POST_DATA;
     
    $header[] = “Content-type: text/xml”;
    $header[] = “Content-length: “.strlen($post_data);
     
    preg_match(“/url=(.*)/”,$_SERVER[‘REQUEST_URI’],$params);
     
    $ch = curl_init( $params[1] );
     
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($ch, CURLOPT_TIMEOUT, 10);
    curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
     
    if ( strlen($post_data)>0 )
    {
        curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
    }
     
    $response = curl_exec($ch);
     
    if (curl_errno($ch))
    {
        print curl_error($ch);
    }
    else
    {
        curl_close($ch);
        header(“Content-type: text/xml; Content-length: “.strlen($response));
        print $response;
    }
 
?>

Так как этот урок не о PHP, я взял на себя смелость использовать существующий скрипт. Он написан Абдулом Кабизом и делает именно то, что мы хотим, поэтому не нужно изобретать велосипед. Более подробную информацию об этом скрипте можно найти здесь ..

… и более подробную информацию об ограничении безопасности можно найти здесь .

Как упомянуто выше, приложение флэш-памяти покажет все фотографии в слайд-шоу, и «классы плагинов» выполнят переходы. Первое, что нам нужно сделать, это настроить новый проект Flash и назначить его базовый класс. Я не хочу вдаваться в подробности, потому что полагаю, что это не первое приложение, которое вы создали.

Если вы загрузили исходные файлы этого проекта, вы заметите, что в корневом каталоге (тот, который содержит .fla файл) есть папка с именем «widget», а внутри этой папки есть файл с именем «Widget.as». , Это будет наш базовый класс.

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

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
package widget
{
    import flash.display.*;
    import flash.events.*;
    import flash.geom.*;
    import flash.net.URLRequest;
    import flash.system.*;
     
    import gs.TweenMax;
     
    import widget.transitions.*;
    import widget.OpenSocialPhotoLoader;
     
    public class Widget extends MovieClip
    {
        private var myProxy:String = “http://85.17.135.135/~gerbster/projects/FlashTutsWidget/proxy.php?url=”;
        //private var myProxy:String = “”;
         
        private var myOSPL:OpenSocialPhotoLoader;
        private var photoArray:Array;
         
        private var myPhotoLoader:Loader;
        public var myPhotoHolder:MovieClip;
         
        private var fadeInOutTransition:FadeInOut;
        private var cubes3DTransition:Cubes3D;
        private var transitionsArray:Array = new Array();
         
        private var currentPhotoIndex:Number = 0;
        private var transitionIndex:Number = NaN;
         
        private var photoTime:Number = 5;
         
        public function Widget():void
        {
            Security.allowDomain(“orkut.com”);
             
            myPhotoHolder = new MovieClip();
            myPhotoHolder.name = “photoHolder”;
             
            addChild(myPhotoHolder);
             
            initPhotoArray();
            initTransitions();
             
            if(photoArray.length > 0)
            {
                loadPhoto(currentPhotoIndex);
            }
        }
 
        private function initPhotoArray()
        {
            myOSPL = new OpenSocialPhotoLoader();
             
            addChild(myOSPL);
             
            myOSPL.loadAlbum();
             
            photoArray = myOSPL.masterAlbum;
        }
         
        private function initTransitions()
        {
            fadeInOutTransition = new FadeInOut();
            cubes3DTransition = new Cubes3D();
             
            transitionsArray.push(fadeInOutTransition);
            transitionsArray.push(cubes3DTransition);
        }
 
        private function loadPhoto(index:Number):void
        {
            if(index >= photoArray.length)
            {
                index = 0;
            }
             
            if(index == -1)
            {
                index = photoArray.length;
            }
             
            myPhotoLoader = new Loader();
            var photoUrl:String = photoArray[index];
            var photoUrlReq:URLRequest = new URLRequest(myProxy + photoUrl);
             
            myPhotoLoader.load(photoUrlReq);
             
            // if the photo is loaded, call showNextPhoto which will do the transition
            myPhotoLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, showNextPhoto);
 
            // increment currentPhotoIndex and call this function again in 5 seconds
            currentPhotoIndex = ++index;
            TweenMax.delayedCall( photoTime, loadPhoto, [currentPhotoIndex]);
        }
         
        private function showNextPhoto(e:Event)
        {
            // we want our photo’s nicely scaled and centered
            var scaleValue:Number = calculateScaleValue(e.target.loader.contentLoaderInfo.width, e.target.loader.contentLoaderInfo.height);
             
            var translateX:Number = (stage.stageWidth – (e.target.loader.contentLoaderInfo.width * scaleValue)) / 2;
            var translateY:Number = (stage.stageHeight – (e.target.loader.contentLoaderInfo.height * scaleValue)) / 2;
             
            // …so we use a matrix
            var resizeMatrix:Matrix = new Matrix();
            resizeMatrix.scale(scaleValue, scaleValue);
            resizeMatrix.translate(translateX, translateY);
             
            var newPhotoBitmapData:BitmapData = new BitmapData(stage.stageWidth, stage.stageHeight, false, 0x000000);
            newPhotoBitmapData.draw(e.target.loader, resizeMatrix, null, null, null, true);
 
            // if there’s a photo on stage do a transition
            if(myPhotoHolder.numChildren > 0)
            {
                var oldPhotoBitmapData:BitmapData = new BitmapData(stage.stageWidth, stage.stageHeight, false, 0x000000);
                 
                oldPhotoBitmapData.draw(myPhotoHolder.getChildAt(0), null, null, null, null, true);
                 
                // remove the old transition object
                if(myPhotoHolder.getChildAt(0) is ITransition)
                {
                    transitionsArray[transitionIndex].destroyTransition();
                }
                 
                // remove the old transition object
                myPhotoHolder.removeChild(myPhotoHolder.getChildAt(0));
                 
                // pick a random transition
                transitionIndex = Math.round(Math.random() * (transitionsArray.length – 1));
                 
                myPhotoHolder.addChild(transitionsArray[transitionIndex]);
                 
                transitionsArray[transitionIndex].doTransition(oldPhotoBitmapData, newPhotoBitmapData);
            }
            else
            {
                var newPhoto:Bitmap = new Bitmap(newPhotoBitmapData);
                 
                myPhotoHolder.addChildAt(newPhoto, 0);
            }
        }
         
        private function calculateScaleValue(oldWidth:Number, oldHeight:Number):Number
        {
            var photoRatio:Number = oldHeight / oldWidth;
            var screenRatio:Number = stage.stageHeight / stage.stageWidth;
             
            if(photoRatio < screenRatio)
            {
                return stage.stageWidth / oldWidth;
            }
            else
            {
                return stage.stageHeight / oldHeight;
            }
        }
    }
}

Прежде чем мы сможем запустить что-либо, нам нужно импортировать некоторые классы. Первые пять являются родными классами флеш-памяти и не очень интересны. Далее нам нужно импортировать TweenMax . TweenMax – это часто используемый класс анимации, но мы собираемся использовать его здесь, потому что он имеет функцию delayedTask. Мы обсудим это более подробно позже. Наконец, нам нужно импортировать классы перехода и класс OpenSocialPhotoLoader.

01
02
03
04
05
06
07
08
09
10
import flash.display.*;
import flash.events.*;
import flash.geom.*;
import flash.net.URLRequest;
import flash.system.*;
 
import gs.TweenMax;
 
import widget.transitions.*;
import widget.OpenSocialPhotoLoader;

Как вы, вероятно, знаете, конструктор будет вызываться при запуске приложения, поэтому здесь мы сделаем все инициализации. Сначала мы сообщим этому приложению, что Orkut.com является безопасным доменом. Нам нужно сделать это, иначе мы не сможем вызвать функцию javascript, чтобы получить массив фотографий. Затем мы добавим пустой MovieClip на сцену, который будет действовать как контейнер для фотографии, которую мы собираемся показать. В-третьих, мы будем вызывать две функции инициализации. Эти функции инициализируют переходы и извлекают массив фотографий из JavaScript. Последние 3 строки кода проверят, есть ли в массиве фотографии, и, если да, покажут первую фотографию.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
public function Widget():void
{
    Security.allowDomain(“orkut.com”);
     
    myPhotoHolder = new MovieClip();
    myPhotoHolder.name = “photoHolder”;
     
    addChild(myPhotoHolder);
     
    initPhotoArray();
    initTransitions();
     
    if(photoArray.length > 0)
    {
        loadPhoto(currentPhotoIndex);
    }
}

Функция initPhotoArray создаст новый экземпляр класса OpenSocialPhotoLoader, добавит его на сцену, затем вызовет функцию loadAlbum внутри этого класса и, наконец, назначит переменную masterAlum внутри этого класса нашей локальной переменной photoArray (которую мы объявили в верхней части класса ). На следующем шаге мы обсудим класс OpenSocialPhotoLoader более подробно.

01
02
03
04
05
06
07
08
09
10
private function initPhotoArray()
{
    myOSPL = new OpenSocialPhotoLoader();
     
    addChild(myOSPL);
     
    myOSPL.loadAlbum();
     
    photoArray = myOSPL.masterAlbum;
}

Чтобы убрать очевидный вопрос, зачем использовать отдельный класс для загрузки фотографий? Что ж, если вы хотите повторно использовать это приложение в другой среде, вам не нужно будет полностью переписывать базовый класс, просто создайте новый класс Loader и реализуйте его. Приветствую ООП!

Этот класс очень прост, хотя. Он содержит функцию loadAlbum, которая будет использовать класс внешнего интерфейса из библиотеки AS3 для вызова функции retrieveMasterAlbum из javascript. Эта функция возвращает объект, который содержит все URL. Наконец, все элементы этого объекта будут добавлены в локальный массив.

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
package widget
{
    import flash.external.ExternalInterface;
    import flash.display.MovieClip;
     
    public class OpenSocialPhotoLoader extends MovieClip
    {
        private var rawMasterAlbum:Object;
        public var masterAlbum:Array = new Array();
         
        public function OpenSocialPhotoLoader()
        {
        }
         
        public function loadAlbum():void
        {
            if (ExternalInterface.available)
            {
                try
                {
                    rawMasterAlbum = ExternalInterface.call(“retrieveMasterAlbum”);
                     
                    for each (var value:* in rawMasterAlbum)
                    {
                        masterAlbum.push(value.toString());
                    }
                }
                catch (error:SecurityError)
                {
                     
                }
                catch (error:Error)
                {
 
                }
            }
            else
            {
 
            }
        }
    }
}

Так как вызов функции из javascript подвержен всевозможным ошибкам, здесь мы будем использовать конструкцию try catch.

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

1
2
3
4
5
6
7
8
private function initTransitions()
{
    fadeInOutTransition = new FadeInOut();
    cubes3DTransition = new Cubes3D();
     
    transitionsArray.push(fadeInOutTransition);
    transitionsArray.push(cubes3DTransition);
}

Почему мы это сделаем, я расскажу позже.

Последние 3 строки конструктора были ответственны за «запуск приложения». Если в массиве есть какие-либо фотографии, то loadPhoto вызывается с currentPhotoIndex в качестве аргумента. Переменная currentPhotoIndex объявлена ​​в верхней части класса и содержит порядковый номер текущей фотографии на экране. Он объявлен со значением по умолчанию 0. Давайте рассмотрим функцию, а затем обсудим ее.

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
private function loadPhoto(index:Number):void
{
    if(index >= photoArray.length)
    {
        index = 0;
    }
     
    if(index == -1)
    {
        index = photoArray.length;
    }
     
    myPhotoLoader = new Loader();
    var photoUrl:String = photoArray[index];
    var photoUrlReq:URLRequest = new URLRequest(myProxy + photoUrl);
     
    myPhotoLoader.load(photoUrlReq);
     
    // if the photo is loaded, call showNextPhoto which will do the transition
    myPhotoLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, showNextPhoto);
 
    // increment currentPhotoIndex and call this function again in 5 seconds
    currentPhotoIndex = ++index;
    TweenMax.delayedCall( photoTime, loadPhoto, [currentPhotoIndex]);
}

Первые два оператора if будут проверять, находится ли currentPhotoIndex вне границ, и если это так, исправьте его. Следующим шагом будет загрузка картинки. Мы используем наш прокси-скрипт, чтобы обойти ограничение безопасности флэш-памяти. Строка myProxy также объявлена ​​в верхней части класса. Мы добавим в этот загрузчик eventListener, который будет выполнять функцию showNextPhoto в случае полной загрузки фотографии.

В качестве последнего шага мы увеличим currentPhotoIndex и используем функцию TweenMax delayedCall, чтобы повторно вызвать эту функцию через 5 секунд. Дополнительную информацию о функции delayedCall можно найти на домашней странице TweenMax: http://blog.greensock.com/tweenmaxas3/ .

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

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

Первое, что мы собираемся сделать, это изменить масштаб изображения и правильно расположить его. Независимо от размеров фотографии, ее центрируют и помещают на черный фон. Для этого мы используем матрицу. Хотя на первый взгляд это может показаться страшным, использование матрицы очень эффективно и не так сложно. Более подробную информацию о матрицах можно найти в API AS3 на adobe.com. Для определения масштабного коэффициента, необходимого для изменения размера фотографии, мы используем небольшую функцию Calculate Scalue.

1
if(myPhotoHolder.numChildren > 0)

Далее мы определим, есть ли еще картинки на экране. Мы должны сделать это, потому что первая картинка должна быть показана напрямую без перехода (переход от него не к чему).

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
if(myPhotoHolder.numChildren > 0)
{
    var oldPhotoBitmapData:BitmapData = new BitmapData(stage.stageWidth, stage.stageHeight, false, 0x000000);
     
    oldPhotoBitmapData.draw(myPhotoHolder.getChildAt(0), null, null, null, null, true);
     
    // remove the old transition object
    if(myPhotoHolder.getChildAt(0) is ITransition)
    {
        transitionsArray[transitionIndex].destroyTransition();
    }
     
    // remove the old transition object
    myPhotoHolder.removeChild(myPhotoHolder.getChildAt(0));
     
    // pick a random transition
    transitionIndex = Math.round(Math.random() * (transitionsArray.length – 1));
     
    myPhotoHolder.addChild(transitionsArray[transitionIndex]);
     
    transitionsArray[transitionIndex].doTransition(oldPhotoBitmapData, newPhotoBitmapData);
}

Однако, если изображение уже есть, нам сначала нужно удалить старый объект перехода, затем выбрать новый из массива переходов с помощью случайной функции, добавить его на экран и, наконец, упорядочить класс перехода как «doTransition». В качестве аргументов мы подаем старые, текущие растровые данные и новые растровые данные. Обратите внимание, что мы используем интерфейс ITransition, чтобы проверить, является ли дочерний элемент на этапе переходным классом или нет. Более подробная информация об этом интерфейсе появится в несколько шагов.

1
2
3
4
5
6
else
{
    var newPhoto:Bitmap = new Bitmap(newPhotoBitmapData);
     
    myPhotoHolder.addChildAt(newPhoto, 0);
}

Если нет, мы просто поместим фотографию на экран.

Как упоминалось ранее, эффекты перехода обрабатываются плагинами. Плагины – это классы, которые реализуют интерфейс ITransition и будут обрабатывать визуальный переход от старой фотографии к новой. Я сделал два из этих классов, один простой переход постепенного появления / исчезновения и сложный, который показывает вращающиеся трехмерные кубы. В следующих нескольких шагах мы обсудим этот интерфейс и 2 перехода.

Если это ваше первое знакомство с интерфейсом, я бы посоветовал вам поискать в Интернете и прочитать об этом. Вкратце, интерфейс – это концепция ООП, которая определяет некоторые спецификации для различных классов подключаемых модулей. Мы будем использовать его здесь, чтобы убедиться, что наши переходные классы включают определенные функции. Код этого интерфейса очень прост:

01
02
03
04
05
06
07
08
09
10
11
package widget.transitions
{
    import flash.display.*;
     
    public interface ITransition
    {
        function doTransition(oldPhotoBitmapData:BitmapData, newPhotoBitmapData:BitmapData):void;
         
        function destroyTransition():void
    }
}

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

Первый из двух наших переходных классов – это эффект постепенного исчезновения. Это простой класс, который расширяет класс MovieClip (потому что мы добавляем его в качестве дочернего для нашего контейнера фотографий в базовом классе) и реализует интерфейс ITransition. Это функция doTransition, которая поместит обе фотографии на экран, сделает новую прозрачной, а с помощью TweenMax ее можно будет постепенно увеличивать и уменьшать. Это занимает 1 секунду, что определяется переменной playtime в верхней части класса.

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
package widget.transitions
{
    import flash.display.*;
     
    import gs.TweenMax;
    import gs.easing.*;
     
    public class FadeInOut extends MovieClip implements ITransition
    {
        // playtime in seconds
        public var playTime:Number = 1;
         
        public function FadeInOut()
        {
        }
         
        public function doTransition(oldPhotoBitmapData:BitmapData, newPhotoBitmapData:BitmapData):void
        {
            var oldPhoto:Bitmap = new Bitmap(oldPhotoBitmapData);
            this.addChild(oldPhoto);
             
            var newPhoto:Bitmap = new Bitmap(newPhotoBitmapData);
            newPhoto.alpha = 0;
            this.addChild(newPhoto);
             
            TweenMax.to(oldPhoto, playTime, {alpha: 0, ease:Quad.easeIn});
            TweenMax.to(newPhoto, playTime, {alpha: 1, ease:Quad.easeOut});
        }
         
        public function destroyTransition():void
        {
            // no need to destroy anything.
        }
    }
}

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

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

Опять же, класс расширит MovieClip и реализует интерфейс ITransition. В дополнение к функциям doTranstion и destroyTransition есть некоторые другие функции. Я буду обсуждать их в следующие несколько шагов. Полный код этого класса перехода выглядит следующим образом:

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
package widget.transitions
{
    import flash.display.*;
    import flash.events.*
    import flash.geom.*;
     
    import org.papervision3d.materials.*;
    import org.papervision3d.materials.utils.*;
    import org.papervision3d.objects.primitives.Cube;
    import org.papervision3d.objects.DisplayObject3D;
    import org.papervision3d.cameras.Camera3D;
    import org.papervision3d.render.BasicRenderEngine;
    import org.papervision3d.scenes.Scene3D;
    import org.papervision3d.view.Viewport3D;
     
    import gs.TweenMax;
    import gs.easing.*;
     
    public class Cubes3D extends MovieClip implements ITransition
    {
        private var viewport:Viewport3D;
        private var renderer:BasicRenderEngine;
        private var scene:Scene3D;
        private var camera:Camera3D;
         
        private var cubeSize:Number = 100;
        private var horizontalAmount:Number = 4;
        private var verticalAmount:Number = 3;
         
        public function Cubes3D()
        {
        }
         
        public function doTransition(oldPhotoBitmapData:BitmapData, newPhotoBitmapData:BitmapData):void
        {
            init3d();
             
            for(var v:int = 0; v < verticalAmount; v++)
            {
                for(var h:int = 0; h < horizontalAmount; h++)
                {
                    var oldPhotoPart:BitmapData = new BitmapData(cubeSize, cubeSize);
                    oldPhotoPart.copyPixels(oldPhotoBitmapData ,new Rectangle(h*cubeSize, v*cubeSize, cubeSize, cubeSize), new Point(0,0));
                     
                    var newPhotoPart:BitmapData = new BitmapData(cubeSize, cubeSize);
                    newPhotoPart.copyPixels(newPhotoBitmapData ,new Rectangle(h*cubeSize, v*cubeSize, cubeSize, cubeSize), new Point(0,0));
                     
                    var frontMaterial:BitmapMaterial = new BitmapMaterial(oldPhotoPart);
                    var backMaterial:BitmapMaterial = new BitmapMaterial(newPhotoPart);
                     
                    var ml:MaterialsList = new MaterialsList({
                                                                front: frontMaterial,
                                                                right: new ColorMaterial(0x000000, 0),
                                                                left: new ColorMaterial(0x000000, 0),
                                                                back: backMaterial,
                                                                top: new ColorMaterial(0x000000, 0),
                                                                bottom: new ColorMaterial(0x000000, 0)
                                                            });
                     
                    var cube:Cube = new Cube(ml, cubeSize, cubeSize, cubeSize, 4, 4, 4);
                     
                    cube.name = “cube_”+v+”_”+h;
                     
                    cube.x = -(h * cubeSize) + (stage.stageWidth / 2) – (cubeSize / 2);
                    cube.y = -(v * cubeSize) + (stage.stageHeight / 2) – (cubeSize / 2);
                                     
                    scene.addChild(cube);
                }
            }
             
            // cubes are loaded, so start rotating!
            for(var i:int = 0; i < verticalAmount; i++)
            {
                for(var j:int = 0; j < horizontalAmount; j++)
                {
                    var cubeNumber:Number = i*verticalAmount + j;
                     
                    viewport.getChildLayer(scene.getChildByName(“cube_”+i+”_”+j), true).layerIndex = cubeNumber;
                     
                     but not all at once, so use a delayedcall
                    TweenMax.delayedCall(cubeNumber/10, rotateCube, [“cube_”+i+”_”+j]);
                }
            }
        }
         
        private function rotateCube(cubeName:String)
        {
            TweenMax.to(scene.getChildByName(cubeName), 0.75, {rotationY: -180, ease:Linear.easeNone, onComplete: sortLayers, onCompleteParams: [cubeName]});
        }
         
        private function sortLayers(cubeName:String)
        {
            viewport.getChildLayer(scene.getChildByName(cubeName), true).layerIndex = 1;
        }
         
        private function init3d()
        {
            viewport = new Viewport3D(stage.stageHeight, stage.stageWidth, true);
            renderer = new BasicRenderEngine();
            scene = new Scene3D();
            camera = new Camera3D();
         
            var cameraPoint:DisplayObject3D = new DisplayObject3D();
             
            cameraPoint.x = 0;
            cameraPoint.y = 0;
            cameraPoint.z = 0;
 
            camera.z = 2450;
            camera.zoom = 120;
            camera.focus = 20;
             
            camera.target = cameraPoint;
 
            addEventListener(Event.ENTER_FRAME, render);
             
            addChild(viewport);
        }
         
        private function render(e:Event):void
        {
            renderer.renderScene(scene, camera, viewport);
        }
         
        public function destroyTransition():void
        {
            removeEventListener(Event.ENTER_FRAME, render);
 
            removeChild(viewport);
             
            viewport = null;
            renderer = null;
            scene = null;
            camera = null;
            cameraPoint = null;
        }
    }
}

Первым шагом функции doTransition является вызов функции init3d. Эта функция инициализирует различные компоненты, необходимые для papervision3d. Здесь нет ракетостроения. Чтобы сгладить эффект 3d, мы помещаем камеру на некотором расстоянии и используем свойство zoom, чтобы увеличить кубы. Далее мы добавим прослушиватель событий, который рендерит всю сцену.

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 init3d()
{
    viewport = new Viewport3D(stage.stageHeight, stage.stageWidth, true);
    renderer = new BasicRenderEngine();
    scene = new Scene3D();
    camera = new Camera3D();
 
    var cameraPoint:DisplayObject3D = new DisplayObject3D();
     
    cameraPoint.x = 0;
    cameraPoint.y = 0;
    cameraPoint.z = 0;
 
    camera.z = 2450;
    camera.zoom = 120;
    camera.focus = 20;
     
    camera.target = cameraPoint;
 
    addEventListener(Event.ENTER_FRAME, render);
     
    addChild(viewport);
}

Когда papervision3d будет правильно инициализирован, функция doTransition продолжится, и в трехмерную сцену будет помещено всего 12 кубов. У всех этих кубиков есть часть старой фотографии и новой фотографии на их передней и задней части. Эти части сочетаются друг с другом как головоломка. Чтобы идентифицировать отдельные кубы, все они получают имя, которое определяет их положение в сетке.

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
public function doTransition(oldPhotoBitmapData:BitmapData, newPhotoBitmapData:BitmapData):void
{
    init3d();
     
    for(var v:int = 0; v < verticalAmount; v++)
    {
        for(var h:int = 0; h < horizontalAmount; h++)
        {
            var oldPhotoPart:BitmapData = new BitmapData(cubeSize, cubeSize);
            oldPhotoPart.copyPixels(oldPhotoBitmapData ,new Rectangle(h*cubeSize, v*cubeSize, cubeSize, cubeSize), new Point(0,0));
             
            var newPhotoPart:BitmapData = new BitmapData(cubeSize, cubeSize);
            newPhotoPart.copyPixels(newPhotoBitmapData ,new Rectangle(h*cubeSize, v*cubeSize, cubeSize, cubeSize), new Point(0,0));
             
            var frontMaterial:BitmapMaterial = new BitmapMaterial(oldPhotoPart);
            var backMaterial:BitmapMaterial = new BitmapMaterial(newPhotoPart);
             
            var ml:MaterialsList = new MaterialsList({
                                                        front: frontMaterial,
                                                        right: new ColorMaterial(0x000000, 0),
                                                        left: new ColorMaterial(0x000000, 0),
                                                        back: backMaterial,
                                                        top: new ColorMaterial(0x000000, 0),
                                                        bottom: new ColorMaterial(0x000000, 0)
                                                    });
             
            var cube:Cube = new Cube(ml, cubeSize, cubeSize, cubeSize, 4, 4, 4);
             
            cube.name = “cube_”+v+”_”+h;
             
            cube.x = -(h * cubeSize) + (stage.stageWidth / 2) – (cubeSize / 2);
            cube.y = -(v * cubeSize) + (stage.stageHeight / 2) – (cubeSize / 2);
                             
            scene.addChild(cube);
        }
    }

Далее мы начнем вращать их. Снова мы используем функцию delayedCall TweenMax, чтобы добавить небольшую задержку, чтобы получить эффект развертки.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
    // cubes are loaded, so start rotating!
    for(var i:int = 0; i < verticalAmount; i++)
    {
        for(var j:int = 0; j < horizontalAmount; j++)
        {
            var cubeNumber:Number = i*verticalAmount + j;
             
            viewport.getChildLayer(scene.getChildByName(“cube_”+i+”_”+j), true).layerIndex = cubeNumber;
             
            // but not all at once, so use a delayedcall
            TweenMax.delayedCall(cubeNumber/10, rotateCube, [“cube_”+i+”_”+j]);
        }
    }
}

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

1
2
3
4
private function sortLayers(cubeName:String)
{
    viewport.getChildLayer(scene.getChildByName(cubeName), true).layerIndex = 1;
}

В отличие от класса fade in / out, нам нужно что-то сделать с помощью функции destroyTransition. Мы будем использовать его для удаления прослушивателя событий, который обрабатывает рендеринг, и различных других компонентов papervision3d. Почему?Что ж, если мы этого не сделаем, производительность приложения будет резко падать при каждом использовании этого перехода. Поскольку объект перехода не уничтожается в базовом классе после его завершения, он будет каждый раз вызывать новую среду papervision3d, вызывая серьезные проблемы с производительностью.

01
02
03
04
05
06
07
08
09
10
11
12
public function destroyTransition():void
{
    removeEventListener(Event.ENTER_FRAME, render);
 
    removeChild(viewport);
     
    viewport = null;
    renderer = null;
    scene = null;
    camera = null;
    cameraPoint = null;
}

Теперь, когда у нас есть все на месте, пришло время разместить все это в Интернете. Убедитесь, что все пути внутри файла widget.xml и путь к сценарию прокси внутри базового класса указаны правильно.

После загрузки и тестирования прокси-скрипта и приложения Flash пришло время добавить виджет в свой профиль Orkut. Для этого перейдите на Orkut.com, войдите в систему и перейдите по адресу: http://sandbox.orkut.com/ . Убедитесь, что у вас есть статус разработчика. Пока вы находитесь в песочнице, у вас есть возможность добавлять свои собственные приложения. Для этого нажмите «Изменить» рядом с «Приложения» в левом столбце, и вы увидите дополнительное поле ввода. Введите URL-адрес вашего файла widget.xml, нажмите «Добавить приложение» и все готово.

Если все правильно, вы увидите очень хороший слайдер фотографий!

В заключение я хочу дать вам несколько советов, касающихся создания и тестирования приложений OpenSocial. Чтобы упростить задачу, сообщество OpenSocial только что выпустило инструмент под названием OpenSocial Development Environment (OSDE), в котором вы можете моделировать сайт социальной сети (это плагин для Eclipse). Тем не менее, по сей день он поддерживает только OpenSocial API версии 0.7, чего было недостаточно для этого урока.

Для отладки javascript-части приложения вы можете использовать встроенную функцию Gadgets.util.log, которая по сути является просто оболочкой для console.log (firebug / firefox).

Чтобы протестировать ваше флэш-приложение, может быть удобно использовать другой класс PhotoLoader. Тот, который просто загружает массив картинок с вашего HD, потому что, поверьте мне, вы не хотите загружать XML-файл и флэш-приложение каждый раз, когда вы хотите проверить, работает ли переход должным образом 🙂

Спасибо за чтение, надеюсь, вы чему-то научились!