LOGIN
ID
PASS 新規登録
パスワードを忘れた方はこちら
SSLでログイン
前月2007年 12月翌月
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
イベント イベント  

Flash+APIによるモバイルサービス実跡開発

はてなブックマークに追加 livedoorクリップに追加 Yahoo!ブックマークに追加 del.icio.usに追加 イザ!ブックマーク ニフティクリップに追加 Choixにブックマーク Buzzurlに追加

Flash+APIによるモバイルサービス実践開発

Flash Lite1.1とHotpepperAPIとの連携

携帯電話でのFlashの利用事例で、先ず思いつくのが各キャリアのトップページです。動きのあるインタラクティブなメニューは、皆さん、毎日目にしているのではないかと思います。一方、公式サイト、勝手サイトを問わず、その他の一般サイトに目を向けると、Flashで動きのあるバナーを制作するということは多いものの、モバイルサイトで全面的にFlashを採用した事例は少ないように思われます。

理由としては、PC版Flashでできるのにモバイル版Flash(Flash Lite)ではできないといった制約の多さにあります。と、同時に「制約が多いらしいね」という先入観で、モバイル版Flashの利用を躊躇されてい方も多いのではないかと思います。そこで、本チュートリアルでは、先ず最初に、モバイル版Flashの制約を解説し、何ができて何ができないかを整理します。その後、いくつかの制約の解決方法しながら、モバイル版Flashでもこんな実用的なアプリケーションが作れますよ!、ということを例示したいと思います。

なお、本チュートリアルで対象にしたFlashLiteのバージョンは2007年11月現在での普及版1.1です(本文中で特に指定が無ければバージョン1.1を指します)。新しいバージョン2.0では、また事情が異なる(制約が緩和される)点はご承知おきください。

「制約が多いらしいね」から「意外にいろいろできるね」へ、みなさんの認識が変わる一助になればと思います。

1.Flash Liteの制約

PC版Flashでできて、Flash Liteでできないという制約が幾つかあります。以下では、アプリケーションの実装において課題になると思われる制約事項についてまとめてあります。

インラインモード(HTMLに埋め込み)での、on(イベント)の取得ができません

インラインモード(HTMLに埋め込み)のswf では、エンターキーを含めすべてのキーイベントを受け取ることができません。インラインモードはFlashバナーで使われますが、リンクの設定はHTMLの側、つまりswf全体を<a>タグで囲むことで行います。

loadVariables / loadMovieは1クリックにつき1回しか呼び出せません

loadVariablesで読み込んだ変数でJPEG画像のURLを組立て、LoadMovieでそのJPEG画像読み込むということはできません

loadVariables / loadMovieは必ずon(イベント)で呼び出さないといけません

PC版のように、FlashVarsや、URL引数を使って、_rootに変数を渡すことはできません

これは、インライン再生・インタラクティブ再生共にできません

DoCoMo端末では、100KBの制限があります

DoCoMo端末では、loadVariables/loadMovieで読み込んだデータが、現在読み込んでいるSWFファイルのサイズに加算され、最大許容量100KBを超えると、以降の処理ができなくなります

XMLの読み込みとパースができません

Flash Lite 2.0からはできるようになります。

など、いろいろ不便なことがあります。

2.APIにアクセスする2つの方法

1. Flash Liteの制約で述べたとおり、Flash Lite 1.1ではXMLの読み込みとパースができません
よって、Webサービス(通常、HotPepper APIのように結果はXMLで返ってきます)を直接呼び出すことができません。
これを解決する方法として次の2つの方法があります。

  1. PHPやPerl等で作成したサーバアプリケーションが、Webサービスと携帯電話上のswfを仲介し、データ形式を適宜変換する。
  2. PHP TurbineLibSWFmingなどを使用して、Webサービスから得られたデータや画像をもとに、サーバーアプリケーションが動的にSWFファイルを生成し携帯電話に渡す。

QRコード

今回は1つめの、サーバアプリケーションが、Webサービスと携帯電話上のswfを仲介する方法を使って、HotPepperAPIで得られる情報を携帯電話上で表示するアプリケーションを作成してみました。

実際に動作するものはこちらから(PC非対応。携帯電話でアクセスしてください。なおキャリア、機種によっては動作しない場合がありますのでご了承下さい。)

3.プロキシの仕組みと実装

SWFからWebサービスへのアクセスの流れ

Webサービスと携帯電話上のswfを仲介する、PHPで作成したサーバアプリケーションを以下ではPHPプロキシと呼びます。

PHPプロキシの説明

  1. SWF内部でPHPプロキシへの検索クエリを組み立て、リクエスト
  2. PHPプロキシが短縮パラメータを、HotPepper API本来のパラメータに変換し、HotPepper APIにアクセス
  3. PHPプロキシがHotPepper APIのレスポンスを受け取り、“&”(アンパサンド)区切りの文字列にして、text/plainで出力
  4. SWFがPHPプロキシより“&”(アンパサンド)区切りの文字列を受け取る。

PHPプロキシの構成

プロシキ構成図


携帯電話上のswf用に短縮パラメータの処理や、XMLから(アンパサンド)区切り文字列への変換処理を行っているのは、PHPプロキシ本体のproxy.phpです。HotPepperAPIへのアクセスはGourmetSearchクラス (GourmetSearch.class.php)で行います。 今回はHotPepper APIのうち、グルメサーチAPIだけを使用しますが、HotPepperAPIのほか のAPIも利用できるよう、汎用の関数をもったHotpepper.class.phpを作成し、それを継承させて各API用のクラス(例えば、GourmetSearch.class.php)を作成しています。Hotpepper.class.php、GourmetSearch.class.php、いずれも、モバイルアプリケーション、PCアプリケーション問わず汎用的に使えます。


proxy.php

プロキシ本体

inc/Hotpepper.class.php ソースファイルを見る

<?php
@mb_internal_encoding("UTF-8");

require_once("./inc/Hotpepper.class.php");
//http://api.hotpepper.jp/reference.html#we34860a
function proxy() {
//モバイル用短縮パラメータ
$alias = array(
'sid' => 'ShopIdFront', 'skn' => 'ShopNameKana', 'snm' => 'ShopName', 'stl' => 'ShopTel',
'sad' => 'ShopAddress', 'ls' => 'LargeServiceAreaCD', 'sv' => 'ServiceAreaCD',
'la' => 'LargeAreaCD', 'ma' => 'MiddleAreaCD', 'sa' => 'SmallAreaCD','kw' => 'Keyword',
'x' => 'Latitude', 'y' => 'Longitude', 'z' => 'Range',
'dt' => 'Datum', 'kcp' => 'KtaiCoupon', 'gr' => 'GenreCD', 'fd' => 'FoodCD',
'bgc' => 'BudgetCD', 'pcap' => 'PartyCapacity', 'wd' => 'Wedding', 'cs' => 'Course',
'fdr' => 'FreeDrink', 'ffd' => 'FreeFood', 'prv' => 'PrivateRoom',
'hgt' => 'Horigotatsu', 'ttm' => 'Tatami', 'cct' => 'Cocktail','sch' => 'Shochu',
'sake' => 'Sake', 'wine' => 'Wine', 'card' => 'Card', 'nsmk' => 'NonSmoking',
'chtr' => 'Charter', 'k' => 'Ktai', 'prk' => 'Parking','bfr' => 'BarrierFree',
'smm' => 'Sommelier', 'ngv' => 'NightView', 'oair' => 'OpenAir', 'show' => 'Show',
'eq' => 'Equipment', 'kok' => 'Karaoke','band' => 'Band', 'tv' => 'Tv',
'lnc' => 'Lunch', 'mdn' => 'Midnight', 'mml' => 'MidnightMeal', 'eng' => 'English',
'pet' => 'Pet', 'chd' => 'Child', 'odr' => 'Order', 'p' => NULL
);

$paramobj = array();
foreach($_GET as $k => $v) {
if(!array_key_exists($k,$alias)&&isset($v))
error("不正なパラメータ");
else if($alias{$k})
$paramobj{$alias{$k}} = $v;
}

$count = 10;
$paramobj{Count} = $count;
$page = @is_numeric($_GET{p}) ? $_GET{p} : 0;
$paramobj{Start} = $page + 1;

$gs = new GourmetSearch($paramobj);

if($gs->userIsMobile()) header("Content-type: text/plain");

if($gs->success) {
if($gs->total > 0) {
$output = "total="
.$gs->total
."&nextn=". $gs-> getNextResultsNum()
."&prevn=". $gs-> getPrevResultsNum()
."&hit=".count($gs->shops)
."&curp=${page}&";
$n = 0;
foreach($gs->shops as $shop) {
$nm = $shop->ShopName;
if(strlen($nm)>50) $nm = substr($nm,0,49) . "...";
$output .= "nm${n}=${nm}&"
. "url${n}=".urlencode($shop->KtaiShopUrl)."&"
. "ad${n}=".$shop->ShopAddress."&"
. "op${n}=".$shop->Open
. ($shop->Close?"\n定休日:".$shop->Close:"")."&"
. "st${n}=".$shop->StationName."&"
. "bg${n}=".$shop->BudgetDesc."&"
. "sc${n}=".$shop->ShopCatch."&";
$n++;
}

$output .= "success=1&loaded=1"

echo kana($output);

} else error("該当する結果が\n見つかりませんでした");

} else error($gs->errormsg);
}

function kana($str) {
return mb_convert_encoding(mb_convert_kana($str,'aknrs'), "SJIS", "auto");
}

function error($str) { die(kana("error=${str}&success=0&loaded=1")); }

//

proxy();

exit;

?>

このソースを閉じる

proxy.phpの、$aliasオブジェクトに、モバイルアプリケーション用の短縮パラメータを記述します。
"短縮パラメータ" => "HotPepper APIのパラメータ"という記述です。 今回のサンプルでは使わないパラメータがほとんどですが、リファレンスに載っているパラメータすべてに対応してあります。

GourmetSearchクラスにも、不正パラメータのエラーチェックがありますが、短縮パラメータでも同じ処理をしています。

Hotpepperクラスにある、userIsMobileという関数で、ユーザーはPCかモバイルかを判断し、モバイルの場合、Content-typeをtext/plainに設定します。

GourmetSearchクラスは、コンストラクタでAPIにアクセスします。newを使って、オブジェクトを生成した時点で、

  • $shops 店舗一覧
  • $total 総件数
  • $count 1ページあたりに表示する件数
  • $current 現在の結果表示開始数
  • $results 検索結果のSimpleXMLElementオブジェクト

が返されます

Flash LiteのLoadVariablesで使える、“&”(アンパサンド)区切りの文字列を組み立てます。 成否フラグが0のとき、もしくは総件数が0のとき、エラーとして、メッセージを出力します。(loadedは成否に限らず1です)

mb_convert_encodingを使用して、全角英数、カナを半角に変換し、文字コードをShift_JISにします。

出力結果は以下のようになります。
(可読性のため、改行しています。)

total=10&nextn=0&prevn=0&hit=10&curp=0&

nm0=吉野家 アクアシティお台場& url0=http%3A%2F%2Fhpr.jp...&
ad0=東京都港区台場1-7-1アクアシティお台場1F&
op0=24時間営業
定休日:アクアシティお台場に準ずる&
st0=台場&
bg0=〜2000円&
sc0=うまい・はやい・やすい。定番、吉野家の牛丼!&

nm1=築地すし好 アクアシティお台場店&
url1=http%3A%2F%2Fhpr.jp%...&
ad1=東京都港区台場1-7-1 アクアシティお台場1F&
op1=月〜土/11:00〜翌4:00(LO.翌3:30)日・祝/11:00〜23:00(LO.22:30)
定休日:不定休(アクアシティお台場に準ずる)&
st1=台場&
bg1=2001〜3000円&
sc1=毎日築地から直送!新鮮命のネタが自慢です☆&

[中略]

nm9=和牛炭火焼肉 平城苑 お台場店&
url9=http%3A%2F%2Fhpr.jp...&
ad9=東京都港区台場1-7-1 アクアシティお台場1F&
op9=11:00〜15:00(ランチ)15:00〜翌5:30(L.O.翌5:00)
定休日:不定休(アクアシティお台場に準ずる)&
st9=台場&
bg9=5001円〜7000円&
sc9=落ち着いた空間で極上の黒毛和牛を堪能できる店&
success=1&loaded=1

それぞれのパラメータの意味は以下通りです。

  • total 総件数
  • nextn 次のページの表示件数
  • prevn 前のページの表示件数
  • hit 現在のページの表示件数
  • curp 現在のページの表示開始件数
  • nm[0-9] 店名
  • url[0-9] 詳細情報URL
  • ad[0-9] 店住所
  • op[0-9] 営業時間+定休日
  • bg[0-9] 予算
  • nm[0-9] 店キャッチ
  • success APIアクセス成否フラグ
  • loaded 読み込み成否フラグ
inc/hotpepper/GourmetSearch.class.php

GourmetSearchクラス

inc/hotpepper/GourmetSearch.class.php ソースファイルを見る

<?php

class GourmetSearch extends Hotpepper {

var $querylist = array(
'ShopIdFront', 'ShopNameKana', 'ShopName', 'ShopTel', 'ShopAddress',
'LargeServiceAreaCD', 'ServiceAreaCD', 'LargeAreaCD', 'MiddleAreaCD', 'SmallAreaCD',
'Keyword', 'Latitude', 'Longitude', 'Range', 'Datum', 'KtaiCoupon',
'GenreCD', 'FoodCD', 'BudgetCD', 'PartyCapacity', 'Wedding', 'Course',
'FreeDrink', 'FreeFood', 'PrivateRoom', 'Horigotatsu', 'Tatami', 'Cocktail',
'Shochu', 'Sake', 'Wine', 'Card', 'NonSmoking', 'Charter', 'Ktai', 'Parking',
'BarrierFree', 'Sommelier', 'NightView', 'OpenAir', 'Show', 'Equipment', 'Karaoke',
'Band', 'Tv', 'Lunch', 'Midnight', 'MidnightMeal', 'English',
'Pet', 'Child', 'Order', 'Start', 'Count'
);

function GourmetSearch($params=array()) {
if($this->validate($params,$this->querylist)) {
$qstr = $this->paramString($params);
$loc = $this->apihost . "GourmetSearch/" . $this->apiversion . "/?" .$qstr;
$r = $this->getXMLObject($loc);
} else if(!$this->errormsg) $this->errormsg = "不明なエラー";
}
}

?>

このソースを閉じる

GourmetSearchクラスはコンストラクタでHotPepper APIにアクセスします。コンストラクタの引数に、検索クエリの連想配列を渡します。

$queryListは、入力可能なパラメータ名の一覧です。ここにないパラメータがコンストラクタの引数に入っていた場合、不正なパラメータとして扱います。

inc/Hotpepper.class.php

Hotpepperクラス

inc/hotpepper/GourmetSearch.class.php ソースファイルを見る

<?php

require_once(realpath(dirname(__FILE__))."/hotpepper/GourmetSearch.class.php");

class Hotpepper {
var $apikey = "guest", $apiversion = "V110", $apihost = "http://api.hotpepper.jp/";
var $areamaster = array();

function Hotpepper() { }

function getXMLObject($url=NULL) {
if(!$url) return 0;
//
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, $url);
curl_setopt ($curl, CURLOPT_HEADER, 0);
curl_setopt ($curl, CURLOPT_RETURNTRANSFER, 1);
$f = curl_exec($curl);
$cinfo = curl_getinfo($curl);
$this->success = $cinfo{http_code} == "200" ? 1 : 0;
curl_close($curl);
$xml = @new SimpleXMLElement($f);
//
if($this->success) {
$this->shops = $xml->Shop;
$this->total = $xml->NumberOfResults;
$this->count = $xml->DisplayPerPage;
$this->current = $xml->DisplayFrom;
$this->results = $xml;
return $xml;
} else {
switch($cinfo{http_code}) {
case "400":
case "403":
case "503":
$msg = $xml->Message;
$this->errormsg = $msg ? $msg : "不明なエラー";
break;
default:
$this->errormsg = "ウェブサービスに接続できません。";
break;
}
return 0;
}
}

function getNextResultsNum() {
$t = $this->total;
$cu = $this->current;
$co = $this->count;
$n = ( $t > $t + $cu * 2 ) ? $co : ( $t - ($cu + $co) + 1 );
return $n > 0 ? ( $co > $n ? $n : $co ) : 0;
}

function getPrevResultsNum() {
$t = $this->total;
$cu = $this->current;
$co = $this->count;
$n = $cu > $co ? $co : $cu -1;
return $n > 0 ? ( $co > $n ? $n : $co ) : 0;
}

function paramString($params=array()) {
$qstr = "key=".$this->apikey."";
foreach($params as $k => $v) $qstr .= "${k}=${v}";
return $qstr;
}

function validate($params=array(),$keys=array()) {
foreach($params as $k => $v) {
if($v&!in_array($k, $keys)) return false;
}
return true;
}

function userIsMobile() {
$e = $_SERVER{HTTP_USER_AGENT};
$mcode = "/DoCoMo|J\-Phone|SoftBank|Vodafone|KDDI|UP\.Browser/i";
if(preg_match($mcode,$e)) return true;
return false;
}
}
?>

このソースを閉じる

このクラスのコンストラクタは何もしません。HotPepper APIに用意された、それぞれのAPIに対応するクラスの親になり、汎用の機能を提供します。

XMLを解析するために、SimpleXMLモジュールを使用しています。SimpleXMLモジュールには、指定したXMLファイルを読み込んで、パースしてくれる、便利な関数simplexml_load_fileがありますが、丁寧にエラー処理したかったので、先ず、cURLモジュールを使って、HotPepper APIにアクセスし、アクセス成功時の取得結果を文字列としてSimpleXMLElementオブジェクトに渡すようにしました。

サーバーからのレスポンスコードは、curl_getinfoで取得しています。取得したオブジェクトの中のhttp_codeがレスポンスコードです。http_codeが200であれば、Hotpepperのprototype変数successを1、それ以外の場合は何らかのエラーがあるので、successは0、HotPepper APIからのエラーメッセージがあればprototype変数messageに、そのメッセージをセットしています。

4.Flash Liteのしくみ

タイムラインの画像

Flashのタイムラインパネルは以上のようになっていて、それぞれのフレームラベルに対して、フレームアクションが記述されています。Flash Lite 1.1では関数が宣言できないので、gotoAndStopメソッドを使って、フレームアクションを関数のように繰り返し使用します。
Flashlightアプリケーションは、上述100Kの制限がシビアなので、記述するアクションの数を減らすなど工夫が必要になります。

1.初期設定

初期設定 Action Scriptファイル ソースファイルを見る

// ================================================
//
//  [初期設定] フレーム番号:1
//
// ================================================
//
_focusrect = false;
fscommand2("SetQuality", "high");
fscommand2("FullScreen", false);
//
//local時刻取得
//
localhour = fscommand2("GetTimeHours");
if(localhour >= 18 || localhour <= 2 ) {
header_mc.frm = "dinner";
} else if(localhour >= 11 && localhour <= 14) {
header_mc.frm = "lunch";
} else {
header_mc.frm = "default";
}
tellTarget("results_mc") gotoAndStop("init");
tellTarget("header_mc") gotoAndStop(frm);
gotoAndStop("map");

このソースを閉じる

2.スポット表示

スポット表示 Action Scriptファイル ソースファイルを見る

// ================================================
//
//  [スポット表示] フレーム番号:6
//
// ================================================
//
stop();
_mode = "map";
xpos = ""; ypos = "";
tellTarget("header_mc/badge") {
text = "スポットを\n選んでね";
gotoAndPlay(1);
}
tellTarget("results_mc") {
nm0 = "六本木ヒルズ";
x0 = "35.660556"; y0 = "139.729099";
nm1 = "シオサイト";
x1 = "35.665503"; y1 = "139.761031";
nm2 = "お台場";
x2 = "35.625908"; y2 = "139.771416";
nm3 = "表参道ヒルズ";
x3 = "35.666414"; y3 = "139.710554";
nm4 = "銀座交差点";
x4 = "35.673821"; y4 = "139.76779";
nm5 = "新宿サザンテラス";
x5 = "35.686806"; y5 = "139.700354";
nm6 = "渋谷109";
x6 = "35.659569"; y6 = "139.698471";
nm7 = "池袋サンシャイン";
x7 = "35.729583"; y7 = "139.71807";
nm8 = "恵比寿ガーデンプレイス";
x8 = "35.642318"; y8 = "139.713607";
nm9 = "丸の内ビル";
x9 = "35.681213"; y9 = "139.763775";
hit = 10;
}
gotoAndStop("list");

このソースを閉じる

Flash Lite1.1では、配列が使えないので、変数の最後に連番をつけて、配列をシュミレートします。
参考:Flash CS3 ドキュメンテーション : 配列のシミュレート

それぞれ、x[n]、y[n]、nm[n]に、緯度、経度、名前をセットし、一覧を初期化するスクリプトがあるフレームに移動させます。

3.ジャンル表示

ジャンル表示 Action Scriptファイル ソースファイルを見る

// ================================================
//
//  [ジャンル表示] フレーム番号:11
//
// ================================================
//
_mode = "genre";
tellTarget("header_mc/badge") {
text = "ジャンルを\n選んでね";
gotoAndPlay(1);
}
//ローカル時刻でジャンル選択肢を出し分け
results_mc._time = header_mc.frm;
tellTarget("results_mc") {
page = 0;
switch(_time) {
case "dinner":
cd0 = "all";
nm0 = "すべて";
cd1 = "G001";
nm1 = "居酒屋";
cd2 = "G002";
nm2 = "ダイニングバー";
cd3 = "G003";
nm3 = "創作料理";
cd4 = "G008";
nm4 = "焼肉・韓国料理";
cd5 = "G011";
nm5 = "カラオケ・パーティ";
cd6 = "G012";
nm6 = "バー・カクテル";
hit = 7;
break;
case "lunch":
cd0 = "all";
nm0 = "すべて";
cd1 = "G004";
nm1 = "和食";
cd2 = "G005";
nm2 = "洋食";
cd3 = "G006";
nm3 = "イタリアン・フレンチ";
cd4 = "G007";
nm4 = "中華";
hit = 5;
break;
default:
cd0 = "all";
nm0 = "すべて";
cd1 = "G004";
nm1 = "和食";
cd2 = "G005";
nm2 = "洋食";
cd3 = "G006";
nm3 = "イタリアン・フレンチ";
cd4 = "G007";
nm4 = "中華";
hit = 5;
break;
}
}
gotoAndStop("list");

このソースを閉じる

スポット表示と同じく、連番の変数を作り、配列をシュミレートします。

初期設定で、header_mcに設定した、変数:frmを使って、昼/晩/デフォルトのジャンルの出し分けを行い、一覧を初期化するスクリプトがあるフレームに移動させます。

4.リクエスト初期化

リクエスト初期化 Action Scriptファイル ソースファイルを見る

// ================================================
//
//  [リクエスト初期化] フレーム番号:16
//
// ================================================
//
stop();
_mode = "result";
tellTarget("header_mc/badge") {
text = "";
gotoAndStop(1);
}

if(gnr eq "all") gnr = "";

requri = "./proxy.php?x=" add xpos add "&y="
add ypos add "&gr=" add gnr add "&z=4&p="
add results_mc.page add "&";

switch(header_mc.frm) {
case "dinner": requri = requri add "mml=1&"; break;
case "lunch": requri = requri add "lnc=1&"; break;
}

loadVariables(requri, "results_mc");
gotoAndPlay("loading");

このソースを閉じる

スポット選択ジャンル選択で選択された値を使って、proxy.phpへのリクエストを組み立て、loadVariablesを呼びます。結果はresults_mcにセットされますが、読み込みが完了するまでのローディングメッセージを表示するため読み込み中画面に遷移します。

スポット選択、ジャンル選択に関しては後述

5.読み込み中

読み込み中 Action Scriptファイル ソースファイルを見る

// ================================================
//
//  [読み込み中] フレーム番号:21/23
//
// ================================================
//
if(results_mc.loaded == 1) {
fscommand2("StartVibrate", 50, 10, 2);
gotoAndStop(results_mc.success == 1? "list" : "error");
} else if(_currentframe == 23) {
gotoAndPlay("loading");
}

このソースを閉じる

Flash Lite 1.1はonEnterFrameや、setIntervalが使えないので、同じスクリプトをフレーム21、23に記述し、proxy.phpの返り値にある、読み込み完了フラグ loadedが1でない間、フレーム21とフレーム23をループさせます。

loadedが1になったら、一覧を初期化するスクリプトがあるフレームに遷移します。

6.リスト初期化

リスト初期化 Action Scriptファイル ソースファイルを見る

// ================================================
//
//  [リスト初期化] フレーム番号:26
//
// ================================================
//
stop();

//既に表示されている結果を削除
if(results_mc.tmphit) {
for (i=0; i<results_mc.tmphit; i++) {
removeMovieClip("/results_mc/btn" add i);
}
}

//デフォルトのY座標を取得
if (!results_mc.default_y) {
results_mc.default_y = getProperty("/results_mc",_y);
}

//Y座標を初期化
setProperty("/results_mc",_y,results_mc.default_y);

//一覧を作成
tgy = 0;
hit = results_mc.hit;
if(results_mc.total>0 && _mode eq "result") {
header_mc.badge.text = results_mc.total add "件\nあるよ";
tellTarget("header_mc/badge") gotoAndPlay(1);
}
master = "/results_mc/_original";
for (i=0; i<hit; i++) {
strn = "btn" add i;
path = "/results_mc/" add strn;
duplicateMovieClip(master, strn, hit+10-i);
setProperty(path,_visible,true);
setProperty(path,_y,tgy);

tellTarget(path) { stop(); }
eval(path).n = i;

//表出項目
eval(path).name = results_mc["nm" add i];
eval(path).link = results_mc["url" add i];
eval(path).address = results_mc["ad" add i];
eval(path).open = results_mc["op" add i];
eval(path).sta = results_mc["st" add i];
eval(path).bgd = results_mc["bg" add i];
eval(path).scatch = results_mc["sc" add i];
eval(path).code = results_mc["cd" add i];
eval(path).xpos = results_mc["x" add i];
eval(path).ypos = results_mc["y" add i];

tgy += 32; //ボタンの高さを加算
}

//ページスクロールボタン
_next = Number(results_mc.nextn);
_prev = Number(results_mc.prevn);
pagenavi_mc.strnext = _next ? "次の" add _next add "件" : "";
pagenavi_mc.strprev = _prev ? "前の" add _prev add "件" : "もどる";
if( _next>0) {
f = "both"; nn = 2;
} else if(_mode ne "map") {
f = "prev"; nn = 1;
} else {
f = "none"; nn = 0;
}
pagenavi_mc.frm = f;
tellTarget("pagenavi_mc") gotoAndStop(frm);
results_mc.navinum = nn;

tellTarget("results_mc") {
btnnum = "";
active = "";
init = false;
}

gotoAndStop("listview");

このソースを閉じる

スポット選択ジャンル選択で組み立てられた、もしくは、PHPプロキシから読み込まれた配列変数(実際は連続した番号つきの変数)を使って、リストを作ります。

Flash Lite 1.1では、attachMovieが使えないので、result_mcに配置されている_originalというムービークリップを複製し、それぞれのムービークリップに、スポット、ジャンル選択の場合は、遷移に必要なパラメータ、検索結果一覧のときは、詳細で表出させる値を渡します。

7.リスト項目選択

リスト項目選択 Action Scriptファイル ソースファイルを見る

// ================================================
//
// それぞれのボタンに記述されているアクション。
// btnnumは、それぞれ0、1、2となる。
//
// ================================================
//
on(rollOver) {
tellTarget("/results_mc") {
gotoAndStop(btnnum == 2 || btnnum == ""?"select_down":"select_up");
btnnum = 0;
}
}

on(press, keyPress "<Enter>") {
gotoAndStop("enter");
}

// ================================================
//
// clearbtn_mcの中のボタンが押されたとき、
// 共通して実行されるスクリプト(フレームラベル:enter)
//
// ================================================
//
tellTarget("/") {
path = "results_mc/btn" add results_mc.active;
switch(_mode) {
case "map":
xpos = eval(path).xpos;
ypos = eval(path).ypos;
tellTarget("/results_mc") gotoAndStop("init");
gotoAndStop("genre");
break;
case "genre":
gnr = eval(path).code;
if(length(gnr) > 0) {
tellTarget("/results_mc") gotoAndStop("init");
gotoAndStop("load");
} else if(getProperty("/pagenavi_mc/prevbtn",_currentframe) == 6) {
tellTarget("/results_mc") gotoAndStop("init");
gotoAndStop("map");
}
break;
case "result":
if(length(eval(path).name) > 0) {
gotoAndStop("popup");
} else if(getProperty("/pagenavi_mc/prevbtn",_currentframe) == 6) {
if(results_mc.prevn > 0) {
results_mc.page -= 10;
tellTarget("/results_mc") gotoAndStop("init");
gotoAndStop("load");
} else {
tellTarget("/results_mc") gotoAndStop("init");
gotoAndStop("genre");
}
} else if(getProperty("/pagenavi_mc/nextbtn",_currentframe) == 6) {
results_mc.page += 10;
tellTarget("/results_mc") gotoAndStop("init");
gotoAndStop("load");
}
break;
}
}
gotoAndStop("default");

このソースを閉じる

リスト項目選択

画面からはみ出たボタンインスタンスは、onRollOver、onRollOutなどのマウスアクションや、キーイベントを取得できないため、それぞれの表出項目をボタンにするのは適しません。そこで、clearbtn_mcに透明ボタンを3つ置き、前にフォーカスが当たっていたボタンとの比較で、リストの上移動、下移動、決定ボタンの押された時のアクションを行います。

8.リスト

リスト Action Scriptファイル ソースファイルを見る

// ================================================
//
//  [リスト項目選択時] フレーム番号:6
//
// ================================================
//
stop();
//
tellTarget("/pagenavi_mc/prevbtn") gotoAndStop("default");
tellTarget("/pagenavi_mc/nextbtn") gotoAndStop("default");
if(active>=hit) {
switch(_parent.pagenavi_mc.frm) {
case "prev": b = "prevbtn"; break;
case "next": b = "nextbtn"; break;
case "both":
switch(active-hit) {
case 0: b = "prevbtn"; break;
case 1: b = "nextbtn"; break;
}
break;
default: b = false; break;
}
tgy = default_y;
tellTarget("/pagenavi_mc/" add b) gotoAndStop("hover");
} else tgy = default_y - getProperty("btn" add active,_y);
//ボタンを選択状態に
for(i=0;i<hit;i++) {
eval("btn" add i).f = i==active?"hover":"default";
tellTarget("btn" add i) gotoAndStop(f);
}
//;
gotoAndPlay("ease");

// ================================================
//
//  [イージング処理] フレーム番号:11/12
//
// ================================================
//
mov = (tgy - _y)*0.3;
if(Math.abs(mov)>1) {
_y += mov;
if(_currentframe != 11) gotoAndPlay("ease");
} else {
_y = tgy;
gotoAndStop("default");
}

// ================================================
//
//  [下選択] フレーム番号:16
//
// ================================================
//
if(!init) {
active = 0;
init = true;
} else if(active < hit+navinum-1) {
active++;
} else {
active = 0;
}
if(active>=0) gotoAndPlay("hover");

// ================================================
//
//  [上選択] フレーム番号:21
//
// ================================================
//
if(!init) {
active = hit + navinum -1;
init = true;
} else if(active > 0) {
active--;
} else {
active = hit + navinum -1;
}
if(active>=0) gotoAndPlay("hover");

このソースを閉じる

タイムライン

clearbtn_mc内のボタンがロールオーバーした際、上下選択のフレームラベルが呼ばれます。前途のように、Flash Lite 1.1ではonEnterFrameが使えないので、上下移動の際の、イージング効果は、2フレームに同じスクリプトを記述します。また、表示件数の前後も、このボタンで行います。

9.リスト内変数初期化

リスト内変数初期化 Action Scriptファイル ソースファイルを見る

// ================================================
//
//  [変数初期化] フレーム番号:26
//
// ================================================
//
nextn = 0;
prevn = 0;
setProperty("_original",_visible,false);
loaded = 0;
success = 1;
error = "";
_visible = true;
//既に表示されている結果を削除
tmphit = hit;
for (i=0; i<tmphit; i++) eval("nm" add i) = "";
hit = 0;
gotoAndStop("default");

このソースを閉じる

一覧生成のときに使う、諸々の変数を初期化します。既に表示されている結果があれば、ここで削除します。

5. まとめ

プロキシを利用することで、WebサービスにアクセスするFlash Liteアプリケーションを作成する方法をご紹介しました。ちょっとの工夫で、Flash Liteでもいろいろなことができるのが分かっていただけたかと思います。

さて、今回、作成したアプリケーションを更にアップグレードするとしたら、先ず思いつくのが画像の表示です。HotPepper APIでは携帯電話向けのJPEG画像が用意されているので、是非、使いたいところですが、1. Flash Liteの制約で見たとおり、loadMovieとloadVariableは同時に使えないという制約があります。この問題は、やはり前述したサーバーサイドで動的にSWFファイルを生成することで解決できます。

SWF生成用のモジュールをインストールしないといけないなど、一般的なレンタルサーバーではやや敷居が高くなりますが、よりリッチな表現を実現するには、魅力的な方法です。こちらについては、機会があれば、また、別途ご紹介したいと思います。

執筆協力 : 長瀬 敦史

24歳男 フリーランス(2007年12月現在)
Flash/JavaScript/CSS/XHTMLを駆使して、ゆるゆるとポータルサイトや商品サイトのデザイン・開発をしています。
http://ngsdev.org/


RECRUIT Media Technology Labs