Titanium Mobileで作る! iPhone/Androidアプリ

第14回iPhoneサンプルアプリをAndroidに対応させる

今回は実際に今まで作成してきたiPhone向けのTwitterクライアントを、Androidでも動作するように変更していきます。同時にiPhoneとAndroid向けのアプリが同じコードで生成できるるようにしてみます。今回はエレガントな解法ではないのですが、OSごとに表示を分ける必要がある部分ではif文で分岐させることで対応させていきます。

TwitterAPIを対応させる

では早速コードの変更を行っていきましょう。まずはTwitter APIを呼び出すコードをAndroidに対応させます。TwitterのOAuthインターフェースは最近HTMLが変更になっていて、古いHTMLを対象にしたコードでは動作しません。Androidに対応すると同時にこのHTMLの変更についても追従します。と、偉そうに書きましたが、いままでのサンプルで利用しているtm_twitter_apiを作成されている@mogyaさんがつい先日ライブラリの更新をされたので、実際にはライブラリを更新するだけでもこの点については解決してしまいます。著者のgithubから最新のサンプルアプリをダウンロードするか、mogyaさんのtm_twitte_apiを最新にして対応してください。サンプルアプリで利用しているtwitter_api.jsは著者が改編を加えているので、特に必要がなければサンプルアプリを最新にして対応してください。Android対応のために行われている変更についての細かな検証は最後に付録しますので、興味のある方はそちらを御覧ください。

/lib問題

TwitterAPIについてはライブラリを新しくすることで対応できるのですが、そもそもライブラリがResorcesディレクトリに作られたlib以下にあるとAndroidでは正しくincludeできないというバグが存在します。Androidでは Ti.include('lib/twitter_api.js') というinclude指定がなぜか、Ti.include('lib/lib/twitter_api.js') という指定に勝手に解釈されます。いろいろな解決法があるのですが、筆者の場合はlibディレクトリ以下にさらにlibディレクトリを作り、新しく作った(深い位置にあるほうの)libディレクトリから、元のlibディレクトリにあるファイルに向けてシンボリックリンクを張ることで対処しています。ちなみに、Resources/lib/libがResources/libを指すようなシンボリックリンクを作成するとビルドに失敗しますので、lib以下のファイル一つ一つに対して、lib/libからシンボリックリンクを作る必要があります。そのほかの方法については、前述の@mogyaさんのblogなどをご覧になってください。

TabGroupにひと工夫

前回の記事でも書いていますが、AndroidでのTabGroupはTabBarが消せないなどの問題もあって使い勝手がよくありません。そこでTabGroupの利用をやめてしまいたいのですが、この後に使うmenuボタンの機能がTabGroupなしでは動作しなかったので、TabBarを無理やり消すことにします。かなり汚い実装になりますが、Tiatnium側で改善されるまでは仕方がなさそうです。

app.jsの変更
var js_file;
if (Titanium.Platform.osname !== 'android') {
    js_file = 'table_view.js';
} else {
    js_file = 'dummy.js';
}

var win1 = Titanium.UI.createWindow({
    url: js_file,
    title:'Tab 1',
    backgroundColor:'#fff'
});

TabGroupを利用してwindowを開くのは今までと同じですが、Androidの時だけ、dummy.jsを開くようにしています。

そして、dummy.jsの実装は次のようになっています。

var win = Ti.UI.currentWindow;

Ti.API.debug(win.activity);
var win1 = Titanium.UI.createWindow({
    url: 'table_view.js',
    title:'Tab 1',
    backgroundColor:'#fff'
});

Ti.UI.currentTab.open(win1);

こうするとtable_view.jsを全画面に表示し、あたかもTabBarが存在しないように見せることができます。そしてTabGroupを利用していないと使用することができない、menuボタンの利用が可能になります。

menuボタンを使おう

AndroidではNavigationBarが無くなっているので、NavigationBarに設置していた新しいMessageを作成するボタンや検索ボタンが当然表示できません。そこで、NavigationBarのボタンに実装していた機能を、Android端末のmenuキーに割り当ててしまいます。menuボタンが利用できるというのはAndroidがiPhoneとは大きく違う点です。

menuボタンのコード
if (Titanium.Platform.osname !== 'android') {
    var messageButton = Ti.UI.createButton(
        {
                systemButton: Titanium.UI.iPhone.SystemButton.ADD
        }
    );
    messageButton.addEventListener(
        'click',
        function () {
            var messageWindow = Ti.UI.createWindow(
                {
                    url: 'message_window.js',
                    title: 'message',
                    backgroundColor: '#fff'
                }
            );
            Ti.UI.currentTab.open(messageWindow);
        }
    );
    win1.rightNavButton = messageButton;
} else {
    win1.activity.onCreateOptionsMenu = function(e) { //(A)
        var menu = e.menu;
        var menuItem1 = menu.add({ title: "Message",itemId:1 }); //(B)
        menuItem1.addEventListener(
            "click",
            function(e) {
                var messageWindow = Ti.UI.createWindow(
                    {
                        url: 'message_window.js',
                        title: 'message',
                        backgroundColor: '#fff'
                    }
                );
                Ti.UI.currentTab.open(messageWindow);
            }
        );
        var menuItem2 = menu.add({ title: "Search",itemId:2 });
        menuItem2.addEventListener(
            "click",
            function(e) {
                search.show();
            }
        );
    };
}

これがmenuボタンに機能を割り当てるコードになります。elseの前までが今まで通りのiOS向けの実装で、それより後ろ側が今回新しく実装したandroid向けのmenuボタンを利用するためのコードです。これによりiOSで利用するときはNavigationBarのボタンが利用され、Androidの時は端末のmenuキーを利用するようになります。

ちょっと今までとは違ったコードになっていて分かりづらいので、少し詳しく解説します。AndroidではTi.UI.Windowオブジェクトにactiveityというパラメータが増えています。activityというのはAndroidに特有の概念で、Ti.UI.Windowとほとんど同じとようなものと考えることができます。詳しく知るにはAndoridのアーキテクチャを知る必要があるので、世界を目指せ!Androidアプリ開発入門などを参考にしてください。ここでは、Android固有のwindowの機能というくらいに捉えておいてください。

このactivityには、独自のイベントがいくつも定義されており、そのなかのひとつにonCreateOptionsMenuというイベントがあるので、このイベントリスナを(コード中の(A)部分で)定義しています。このイベントリスナに渡ってくるイベントオブジェクトeには、e.menuというオブジェクトがあり、Androidのmenuを操作することができます。このmunuオブジェクトのaddメソッドにtitleとitemIdを渡すと、menuItemオブジェクトが作成され、返り値として取得できます(コード中の(B)部分⁠⁠。menuIdはmenuの項目ごとに違っている必要があります。

このmenuItemオブジェクトにaddEventListenerでイベントリスナを追加することで、ボタンが押されたときの動作を実装することができます。ここでは、新しくmessage_windos.jsに実装されているwindowを開くようにしています。

変更を確認してみる

さて、ここまでの変更を行った上で実際にAndroidエミュレータで実行してみましょう。実行するときは、ScreenのサイズをHVGAで表示すると、iPhoneと解像度に差がなく表示が崩れにくくなります。この解像度による表示の崩れについては次回以降解消していきます。

Androidエミュレータでの動作
Androidエミュレータでの動作

さて、それほど多くないコードの変更で、Androidでも動作するコードに変更できることが確認できたのではないでしょうか。写真の取得、位置情報の取得、地図の表示、加速度の取得など、今までの連載で実装してきたものも、なんのコードの変更もなく動作していますので確認してみてください。

実機での動作。twitpicへのアップロードもできる
実機での動作。twitpicへのアップロードもできる

まとめ

実際にiPhone向けに実装されたコードをAndroid向けに変更しながら、両者の違いを少しずつ確認してみました。TabGroup回りなど色々とiPhoneに比べるとAndroid回りは問題が多いのですが、少しずつよくなってきています。実は今回の実装には非常に困った点が一つあって、一般的なAndroidアプリのように戻るボタンを押してアプリからホーム画面へ戻ろうとすると、一つ謎の画面が挟まれてしまいます。これはTabBarを消すために作成したdummy.jsのウインドウなのですが、どうにかこれを経由しない方法を現在模索中です。次回連載までにご報告できればと思っています。

おすすめ記事

記事・ニュース一覧