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

第3回TitaniumでTwitterクライアント─⁠─レイアウトの調整

前回の記事が公開された直後からTitaniumには大きなバージョンアップがあったので、今回はTwitterクライアントの続きを始める前に、そのお知らせから始めます。

Titanium Moble 1.5.1 リリース

前回までの記事は Titanium Mobile 1.4.2 で動作確認を行いながら執筆していたのですが、この度新バージョンの 1.5.1 がリリースされました。開発元のAppceleratorによると、この1.5系列によってAndroidでの開発はベータ版から正式版になったという扱いのようです。本連載ではまだAndroid開発について説明していませんが、Intent、Activity、Menuボタンからのメニュー表示といったAndroidらしい機能が使えるようになったので、Androidでの開発もいよいよ面白くなってきたと言えるでしょう。Android SDKのインストールなど開発環境の整備が少し難しいのですが、今までの連載のサンプルはAndroidでも動作しますので興味のある方は是非トライしてみてください。

また、iPhoneで開発を行っている方も、Titanium Mobile 1.5.1をダウンロードするように即すダイアログをTitanium上で見ているかもしれません。1.4.2から利用されている方は、現状のプロジェクトは設定を変えなければ1.4.2でコンパイルされ続けますし、1.5.1でも今までのコードは動作しますので、新しく始めた方や1.5.1に設定を変更された方もサンプルコードの動作にはまったく問題ありません。

ツイートを綺麗に表示しよう

さて前置きはこのぐらいで本題に入りましょう。前回は自分のツイートが一欄できるところまで実装しましたが、これではツイートの本文がただ並んでいるだけの状態です。Twitterからとれる情報には、ツイートした時間や、ユーザー名、ユーザーアイコンの情報も入っています。今回はこれらの情報を綺麗にレイアウトしていく方法を解説します。

では早速次のようにコードを変更してみましょう。前回作ったupdateTimeLine関数を変更していきます。まずは、レイアウトの機能を実装した上で、それぞれの機能を確認していきます。

function updateTimeline (timeline) {
    var currentData = [];
    for (var i=0;i<timeline.length;i++) {
        var tweet = timeline[i];
        //ここから変更開始
        var row = Ti.UI.createTableViewRow(
            {
                height: 150,
                layout: 'absolute'
            }
        );

        var imageView = Ti.UI.createImageView(
            {
                image: tweet.user.profile_image_url,
                width: 48,
                height: 48,
                top: 5,
                left: 5
            }
        );
        row.add(imageView);

        var nameLabel = Ti.UI.createLabel(
            {
                width: 120,
                height: 12,
                left: 58,
                top: 5,
                fontSize: 6,
                fontWeight: 'bold',
                color: '#2b4771'
            }
        );
        nameLabel.text = tweet.user.screen_name;
        row.add(nameLabel);

        var commentLabel = Ti.UI.createLabel(
            {
                width: 257,
                left: 58,
                top: 18,
                height: 100,
                fontSize: 8
            }
        );
        commentLabel.text = tweet.text;
        row.add(commentLabel);

        var dateLabel = Ti.UI.createLabel(
            {
                width: 200,
                height: 12,
                left: 58,
                bottom: 8,
                fontSize: 6
            }
        );
        dateLabel.text = tweet.created_at;
        row.add(dateLabel);
        //変更終わり

        currentData.push(row);
    }
    tableView.setData(currentData);
}

一度Titaniumの開発環境から起動してみると次のような表示になるはずです。

画像

まだ不恰好ですが一応Twitterのタイムラインらしくなってきました。ではこのレイアウトの機能について一つ一つ解説していきましょう。

WindowとView

画面全体を覆っている一番下の部分のことをTitaniumでは「window」と呼んでいます。JavascriptのDOMにおけるWindowと考えてもよいですし、iOS SDKのUIWindowと同じようなものと考えてよいと思います。windowの上には複数のviewを配置することができます。また、viewの上に、さらにviewを重ねていくこともできます。前回までに説明したUITableViewもUITableViewRowもviewの一つです。このviewもiOS SDKにおけるUIViewとほぼ同じものです。

そのviewのレイアウトですが、CSSでHTMLのレイアウトを整えていくのと非常に似たイメージでTitaniumのviewはレイアウトを指定していくことができます。

var nameLabel = Ti.UI.createLabel(
    {
        width: 120,
        height: 12,
        left: 58,
        top: 5,
        fontSize: 6,
        fontWeight: 'bold',
        color: '#2b4771'
    }
);

widthやhightでそのviewの大きさを、top, buttom, right, leftでそのviewの位置を指定できます。fontの指定や、文字色についても同様に指定できます。

レイアウト基準

CSSでのHTMLのレイアウトは、static, relativeなどをpositionに指定することにより基準となる位置を選ぶことができます。同じようにTitaniumでは基準となる位置とviewの並び方を2種類から選ぶことができます。

何も指定しない場合

デフォルトでの動作は、親viewとなるTi.UI.TableViewRowを基準とした座標での指定です。明示的に指定するなら「layout: 'absolute'」と指定します。topの値は要素を追加するTableViewRow上端からのピクセル数で指定するすることになります。right, left, bottomもそれぞれの右端、左端、下端からのピクセル数です。

vertical

layoutパラメータにverticalを指定すると、絶対位置ではなくそのviewを追加する前にTableViewRowに追加したviewを基準に位置を指定することになります。

先程のコードを次のように変えてみましょう


function updateTimeline (timeline) {
    var currentData = [];
    for (var i=0;i<timeline.length;i++) {
        var tweet = timeline[i];
        //再度ここから変更開始
        var row = Ti.UI.createTableViewRow(
            {
                height: 150,
                layout: 'vertical'
            }
        );

        var imageView = Ti.UI.createImageView(
            {
                image: tweet.user.profile_image_url,
                width: 48,
                height: 48,
                top: 5,
                left: 5
            }
        );
        row.add(imageView);

        var nameLabel = Ti.UI.createLabel(
            {
                width: 120,
                height: 12,
                left: 58,
                top: -48,
                fontSize: 6,
                fontWeight: 'bold',
                color: '#2b4771'
            }
        );
        nameLabel.text = tweet.user.screen_name;
        row.add(nameLabel);

        var commentLabel = Ti.UI.createLabel(
            {
                width: 257,
                left: 58,
                top: 1,
                height: 100,
                fontSize: 8
            }
        );
        commentLabel.text = tweet.text;
        row.add(commentLabel);

        var dateLabel = Ti.UI.createLabel(
            {
                width: 200,
                height: 12,
                left: 58,
                top: 5,
                fontSize: 6
            }
        );
        dateLabel.text = tweet.created_at;
        row.add(dateLabel);

        //変更終わり
        currentData.push(row);
    }
    tableView.setData(currentData);
}

このようにコードを変更しても見た目はほとんど変わりません。しかしverticalを指定すると前に追加されたviewを基準にtopを指定できるようになります。topにはマイナス値も指定できますので、細かいレイアウトをverticalを利用しながら行うことも可能です。各viewの絶対位置を計算しなくてもレイアウトしていくことができるので、verticalを指定したほうが手軽にレイアウトを変更できそうですね。

auto指定

また、heightに 'auto' を指定することで、表示する内容がすべて表示される高さに自動で調整してくれる機能もあります。上のサンプルにあるheightパラメータの値をすべて 'auto' に指定する(ただしImageViewのheightは除く)と次のような見た目になります。

画像

こちらも、手軽なレイアウト方法としてはとても便利です。

画像を表示する

Twitterのユーザーアイコンを表示する部分では、ImageViewを利用しています。ImageViewは非常に便利なViewで、urlを指定するだけで画像を表示することができます。httpを介したイメージの表示だけでなく、プロジェクト内に配置した画像を表示することもできます。以下のコードはプロジェクト内の画像を呼び出すサンプルです。


var imageView = Ti.UI.createImageView(
    {
       image: 'iphone/appicon.png',
       //ファイル名はプロジェクトのResorcesフォルダからの相対パス
       width: 48,
       height: 48,
       top: 5,
       left: 5
    }
);
row.add(imageView);

コードを分割しよう

ここまで実装をしてくると、そろそろコードが1ファイルに収めるには長くなってきました。あまり長いコードを1ファイルに収めておくと、見通しが悪くなるだけでなくデバッグもしづらくなるのでファイルを分割してみましょう。

Titaniumではファイルを分割する方法がいくつかありますが、ここではwindowを作るときにファイルを指定して開く方法を解説します。window単位でファイルを分けることができるので機能別にコードを分けることが簡単にできます。

いままで書いてきたapp.jsを以下のように2つのファイルに分割します。

app.js
Titanium.UI.setBackgroundColor('#000');

var tabGroup = Titanium.UI.createTabGroup({});

var win1 = Titanium.UI.createWindow({
    url: 'table_view.js',
    title:'Tab 1',
    backgroundColor:'#fff'
});
var tab1 = Titanium.UI.createTab({
    window:win1
}); 

win1.hideTabBar();

tabGroup.addTab(tab1);  
tabGroup.open();
table_view.js
var win = Ti.UI.currentWindow;
var data = [];
var tableView = Ti.UI.createTableView({
    data:data
});

function updateTimeline (timeline) {
    var currentData = [];
    for (var i=0;i<timeline.length;i++) {
        var tweet = timeline[i];
        var row = Ti.UI.createTableViewRow(
            {
                height: 150,
                layout: 'vertical'
            }
        );

        var imageView = Ti.UI.createImageView(
            {
                image: 'iphone/appicon.png',
                width: 48,
                height: 48,
                top: 5,
                left: 5
            }
        );
        row.add(imageView);

        var nameLabel = Ti.UI.createLabel(
            {
                width: 120,
                height: 12,
                left: 58,
                top: -48,
                fontSize: 6,
                fontWeight: 'bold',
                color: '#2b4771'
            }
        );
        nameLabel.text = tweet.user.screen_name;
        row.add(nameLabel);

        var commentLabel = Ti.UI.createLabel(
            {
                width: 257,
                left: 58,
                top: 1,
                height: 100,
                fontSize: 8
            }
        );
        commentLabel.text = tweet.text;
        row.add(commentLabel);

        var dateLabel = Ti.UI.createLabel(
            {
                width: 200,
                height: 12,
                left: 58,
                top: 5,
                fontSize: 6
            }
        );
        dateLabel.text = tweet.created_at;
        row.add(dateLabel);

        currentData.push(row);
    }
    tableView.setData(currentData);
}

var xhr = Ti.Network.createHTTPClient();
var user = 'hatenatech';
var url = "http://api.twitter.com/1/statuses/user_timeline.json?screen_name=" + user;
xhr.open('GET', url);
xhr.onload = function() {
    var timeline = JSON.parse(this.responseText);
    updateTimeline(timeline);
};
xhr.send();

win.add(tableView);

app.jsは内容が一番最初のサンプルのようになり、テーブルを描画する機能についての記述は新しく作ったtable_view.jsのほうに移動してしまいました。注意して見て欲しいのは、app.jsのcreateWindowの引数のなかでurlを指定している点です。これにより、新しいwindowの中で開くJavaScriptファイルを指定しています。

var win1 = Titanium.UI.createWindow({
    url: 'table_view.js', //ここに注意!
    title:'Tab 1',
    backgroundColor:'#fff'
});

もう一つの注意ポイントは新しく作ったtable_view.jsファイルの中で、

var win1 = Ti.UI.currentWindow;

というコードが先頭に追加されている点です。分割前のコードではviewを追加する対象のwindowがグローバルな変数win1として見えていました。しかし分割後のファイルからはこの変数が参照できなくなります。createWindowで指定されるファイルのコードからは、windowを開く元のファイルにある変数を参照できないのです。具体的に言えば、table_view.jsからapp.jsに書かれている変数にアクセスすることは基本的にできません。

そこで、このcurrentWindowパラメータを使って現在開かれているwindowを参照し、そこにviewを追加していくようにコードを書き換えています。

ほかのファイルから変数が参照できなくなる、つまりはスコープが変わるという仕様は、windowごとに変数を完全に分離できるので非常に便利な仕組みです。windowからwindowにデータを渡したい時には少し工夫が必要ですが、それほど問題になることはまずありません。なので、これから開発を進めていくにあたってwindowを新しく作るときは積極的にファイルを分割するようにしていきましょう。

まとめ

今回はWindowとViewの関係とそのレイアウト方法について解説しました。また後半では、ファイルをいくつかに分割して開発を進めていく方法を説明しました。次回はOAuthを使ったAPI呼び出しも解説し、よりTwitterクライアントらしいアプリへ実装を進めていく予定です。次回もよろしくお願いします。

おすすめ記事

記事・ニュース一覧