T

2013年のTiIconicFont + Alloy

ウェブ上の情報が少し古くなっていたようでいくつか質問があったので、現時点で動作するサンプルをまとめておきます。あすとろなんだっけかさんの『Titanium mobile “early” Advent Calendar 2012』の18日目、@hoyo1111 さんのエントリー『【18日目】AlloyでもTiIconicFontを使いたい』を参考にしています。というかこれをほぼまるまる使っています。書き方を入門者に対応しただけですね。

TiIconicFontはサイレントヒルのひげ怪人Titaniumユーザー会ではおなじみのk0sukeyさんが公開してくれている、Font AwesomeやLigature Symbols、SS PikaのようなウェブフォントをTitaniumのLabelでも利用できるようにするモジュールです。インストール方法は次の通りです:

(1)ダウンロード

Alloyのプロジェクトはもう作成しましたね?まだなら、先に作成しておいてください。

TiIconicFontはGithubのページからZIP形式でダウンロードしたりgitでcloneして入手します。

(2)展開

Zipで落とした人はTiIconicFont-masterとかいう名前のディレクトリの下のResources/lib、cloneした人はTiIconicFont/Resouces/libを自分のAlloyのプロジェクトのapp/assets/以下にコピーします。app/assets/libディレクトリが作成されていることを確認しましょう。

(3)フォントデータの用意

TiIconicFontはそれだけでは動作しません。フォントのデータ(ttf)ファイルが必要です。例えばFont Awesomeならプロジェクトのウェブサイトからダウンロードして、展開したディレクトリのfont/fontawesome-webfont.ttfをapp/assets/fonts/以下にコピーします。app/assets/fontsディレクトリは存在しないので作成してからコピーしましょう。

app/assets以下にこれらが用意されればOKです。

app/assets/
├── fonts
│   └── fontawesome-webfont.ttf
└── lib
    ├── FontAwesome-deprecate.js
    ├── FontAwesome.js
    ├── IconicFont.js
    ├── LigatureSymbols-deprecate.js
    ├── LigatureSymbols.js
    └── ti.ss-pika.js

(4)iOS用の設定

iOSの場合は必要なファイルを生成するためにいったんビルドします。ビルドして作成されるbuild/iphone/Info.plistをプロジェクトディレクトリの直下にコピーして、コピーした方のファイルの最下部(/dictの上)に以下を追記します。Androidの場合は無視して地球の未来のことを考えます。

	<key>UIAppFonts</key>
        <array>
          <string>/fonts/fontawesome-webfont.ttf</string>
          <string>/fonts/LigatureSymbols.ttf</string>
          <string>/fonts/ss-pika.ttf</string>
        </array>

(5)動作試験用のViewを用意

app/views/index.xmlに下のような記述を用意します。

  <Label id="symbol" />

app/styles/index.tssにも適当な値を設定しておきましょうか。

"Label": {
  width: Ti.UI.SIZE,
  height: Ti.UI.SIZE,
  color: "Black"
}

(6)動作試験用のcontrollerを用意

app/controllers/index.jsに次のように記述します。

// Font Awesome
var fontawesome = require('lib/IconicFont').IconicFont({font: 'lib/FontAwesome'});
$.symbol.setFont({fontSize: 32, fontFamily: fontawesome.fontfamily()});
$.symbol.setText(fontawesome.icon('icon-thumbs-up'));

$.index.open();

Font Awesomeで利用できるアイコンの一覧はlib/FontAwesome.jsにあります。

(7)自慢

awesome_android

ImageAsResized更新

AndroidにもTiBlobの機能としてImageAsResizedが実装されたにも関わらず細々と利用されているImageAsResizedモジュールですが、Titanium側の仕様変更に追随していなかった部分を更新しておきました。2.0系に合わせて更新して以来の変更です。今回の変更でローカルファイルのリサイズが正常に動作するようになりました(これまでもTiBlobを渡せば動いていたんですが)。

特徴は、回転に対応していること、支点と距離を指定すると切り取りにも対応していること、元画像とプロポーションが違う大きさにリサイズしようとすると勝手に切り取られること(仕様です)。

// Resources以下のファイルは先頭に「/」があってもなくても大丈夫です。
// SDCARDの中のファイルはTi.Filesystem.externalStorageDirectoryでフルパスを渡します。
var androimage = require('org.selfkleptomaniac.ti.imageasresized');
var image_data = androimage.imageAsResized(width, height, "images/boy.jpg", 0);
var rotated_image_data = androimage.imageAsResized(width, height, "/images/boy.jpg", 90);
var sd_card_image = androimage.imageAsResized(width, height, Ti.Filesystem.externalStorageDirectory + "tmp.jpg", 0);

// ちなみにimage_view2は90度回転しています。
// そう、回転にも対応しているんです。
var image_view1 = Ti.UI.createImageView({image:image_data, top:20, canScale:true, width:width, height:height});
var image_view2 = Ti.UI.createImageView({image:rotated_image_data, top:20, width:height, height:width});
var image_view3 = Ti.UI.createImageView({image:sd_card_image, top:20, width:width, height:height});

wrap.add(image_view1);
wrap.add(image_view2);
wrap.add(image_view3);

結果はこんな感じ。

resized_mame

TiTwilio

TitaniumでTwilioを簡単に扱うためのモジュール、TiTwilioを公開しましたので、使い方を説明します。Android、iOS共通です。

最初にTwilioのアカウントを作成します。電話番号を取得したら、次にアプリを登録します。自分が用意する認証サーバとTwilioを連携させるために必要なので、必ず登録してください。

tw1

「TwiMLAppを作成する」ボタンから登録することができます。

大事なのは、Request URLのところです。認証が始まってCapability Tokenを取得した端末は、このRequest URLにPOSTリクエストを発行します。このリクエストへのレスポンスでアプリの挙動が決まります。
tw

TiTwilioでは、リダイレクト時にサーバに指定されたパラメータを追加する機能があります。Titaniumで作成したアプリと認証サーバ間との連携に利用します。実際のコードで説明します。

var TiTwilio = require('org.selfkleptomaniac.mod.titwilio');

// call
TiTwilio.connect({
  url: 'http://your-auth-server.example.com', // auth server (required)
  params: {key: value, post: data} // post data (optional)
});

これだけのコードで上の図の一連の動作が実行されます。paramsに指定した値がPOSTリクエストのキーと値としてRequest URLに送信されます。従って、サーバ側ではこのparamsによってアプリ同士の通話や携帯・固定電話への通話、自動返信などの挙動を変えることができます。

githubのレポジトリにはsinatraで動くRubyのサーバ側スクリプトがserverディレクトリにあります。こちらではRequest URLに「/call-to-me」で終わるURLを指定するようになっています。POSTで送信するtypeの値によってアプリ間通信や携帯・固定電話への通話など機能が分離しています。

アプリ間の通信や携帯・固定電話からの通話を受け付けるためには、認証サーバにログインしている必要があります。Androidの場合はPendingIntentに受信時に起動するサービスを登録しておきます。

var TiTwilio = require('org.selfkleptomaniac.mod.titwilio');

var pendingIntent;
if(Ti.Platform.osname === 'android'){
  var intent = Ti.Android.createServiceIntent({url:'service.js', twilio: TiTwilio});
  pendingIntent = Ti.Android.createPendingIntent({intent: intent});
}

// login
TiTwilio.login({
  url: 'http://your-auth-server.example.com', // auth server (required)
  params: {key: value, post: data}, // post data (optional)
  pendingIntent: pendingIntent
});

これで受信の準備が完了します。iOSの場合はincomingCallイベントをTiTwilioオブジェクトに追加するだけで受信することができます。

Androidの場合は、Resources/android/以下にservice.jsを用意します。通話を受信するたびにこのサービスが起動しますので、ダイアログを出したりするなど受信時の動作をアプリケーションレベルのイベントに登録しておいて、それをこちらからfireEventで呼び出します。

try{
  var service = Titanium.Android.currentService;
  var intent = service.intent;

  Ti.App.fireEvent('inComingCall', {intent: intent});
  service.stop();
}catch(e){
  Ti.API.info(e);
}
service.stop();

サービスの詳細についてはこちらをご覧ください。ただし、intervalで登録しても意味がないので、tiapp.xmlは

<services>
  <service url="service.js" />
</services>

のように記述します。また、Androidのパーミッションの設定も忘れないようにしてください。以下2行を追記する必要があります。

  <uses-permission android:name="android.permission.RECORD_AUDIO"/>
  <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>

Titanium + VMware(Android x86)でAndroidアプリ開発

そう、さっきのエントリを読んでお気づきの方もいらっしゃるでしょうが、実はほぼ同じやり方でVMware上のAndroid x86を動作させることも可能です。インストールについてはこちらの記事なんかが参考になるでしょうか。ネットワークの設定はブリッジで追加のデバイスとすれば

$ adb connect 192.168.1.5 #VMware上のAndroidのIPアドレス(例)
$ adb devices
List of devices attached 
emulator-5554	device
192.168.1.5:5555	device

こんな感じで認識します。CLIからのビルドとインストールはこんな感じ(デバイスの指定は例です):

titanium build --platform=android --build-only && \
adb -s 192.168.1.5:5555 install -r build/android/bin/app.apk

androx86

こっちはActionBarに対応していましたね。

もちろん、Androidのエミュレータでx86のイメージを使ってもいいんですが、あれっていまいち不安定でちゃんと動かなかったりすることが多くないですか?原因の切り分けが難しい割に再現する頻度は高いので、最近ちょっとくじけています。

Titanium + BlueStacksでAndroid開発

Androidのエミュレータの動作の遅さには困ったものがあります。ホストと異なるアーキテクシャのマシンをエミュレートするのですから遅いのは当然のことではありますが、開発の意欲を削ぐほどのノロさなので、いくらなんでも限界というものがあります。そういう場合、普通は(1)諦める(2)報復を仄めかす(3)工夫するといった選択肢が考えられますが、われわれ歌と踊りが大好きな愉快で楽しいTitaniumユーザーとしては、3の路線でいきたいものです。

というわけで、先日発表されて話題になった、WindowsやMac OS上でAndroidのアプリが実行できる環境を提供するBlueStacksを使ってみました。BlueStacksのインストールは簡単で、Mac OS Xならダウンロードしたファイルを展開してコピーするだけです。さっそく起動してみましょう。

bs

なんかそれっぽい画面が出てきましたね。さっそくゲームとかいろいろやってみたいところですが、いったん我慢して、Titaniumで作成したAndroidアプリをインストールできるようにします。ターミナルからadbをリロードしてみましょう。

$ adb kill-server
$ adb start-server

おそらくこれだけでBlueStacksがエミュレータとして認識されるはずです。されていなければ、ここだけの話ですが、localhostの10000とか10001とか10002、あるいは5550から順番にconnectを試したら繋がります。

$ adb connect localhost:1000x
$ adb connect localhost:555x

ひどい話ですね。

$ adb devices
List of devices attached 
emulator-5554	device

こんな風に表示されたらしめたものです。あとはTitaniumがエミュレータとして認識してくれれば楽なのですが、面倒くさい+CLIしか使わないので「build-onlyでapkファイルだけ作成」したら「adbでインストール」という手順でやってしまうことにしました。プロジェクトのディレクトリから

$ titanium build --platform=android --build-only && \
adb -s emulator-5554 install -r build/android/bin/app.apk

これで、apkファイルを作成し、インストールするはずです。さっそく実行します。ところが…

なぜだ…

なぜだ…

あれーっ!エラーがいっぱい!そう、実は3.0.2.GA時点でAndroidはbuild-onlyオプションが正しく動作しないのです。仕方がないので対応させてみましょう。$HOME/Library/Application\ Support/Titanium/mobilesdk/osx/3.0.2.GA/android/cli/commands/_build.jsを編集します。

$ diff /tmp/_build.js /tmp/_build_old.js 
404,405d403
<       } else if (cli.argv['build-only']){
<         return;

うーん、問題なければPull Requestしようかな。これでbuild-onlyを無視しなくなります。もう一度さっきのコマンドを実行してみます。

$ titanium build --platform=android --build-only && \
adb -s emulator-5554 install -r build/android/bin/app.apk

BlueStacksには普通のAndroidデバイスにあるようなアプリ一覧みたいなメニューがないので、ホーム画面左側の大きな「My Apps」ボタンをクリックします。

bs2

インストールされていましたね(画像のActionBar2っていうのがそうです。ええ、ついうっかりFruit Ninja Freeも入れましたとも)。ActionBarの動作を試したかったのですが、どうやら対応していないみたい。

bs3

Android実機上でのデバッグ

Androidもやりましたよ。

原文:http://docs.appcelerator.com/titanium/latest/#!/guide/Debugging_on_Android_Devices

====================================================

Android実機上でのデバッグ

【はじめに】

Titanium SDK 3.0から、Titanium Studioを利用してAndroid実機上でデバッグセッションを開始することができるようになりました。Android実機でのデバッグは以下のステップで実行できます:

  • 「Debug on Android Device」を選択する
  • Stuidoでアプリをビルドする
  • アプリを自動または手動で実機に同期する
  • アプリを実機上で起動する
  • LANのWifiを経由してStudioとデバッグセッションを開始する

本項ではこれらのステップとデバッグセッションの起動について解説します。

事前に「Deploying to Android devices」で解説されているようにAndroid実機でアプリを実行できるようにセットアップしておく必要があります。

【必要なもの】

デバッグセッションを実行するために必要なのは:

  • Titanium SDKリリース3.0以降
  • Titanium Studioリリース3.0以降
  • USBケーブルでマシンに接続されたAndroid実機

【デバッグセッションの起動】

デバッグを開始する前に、AndroidをターゲットにしたTitaniumのプロジェクトを作成しておく必要があります。基本的には次の2つのステップがあります:

  1. Titanium Studioでデバッグ用セッションを初期化する
  2. 実機でアプリを実行してデバッグを開始する

デバッグ用セッションの開始

  1. Titanium StudioのApp ExplorerまたはProject Explorerビューからプロジェクトを選択
  2. ツールバーのDebugアイコンをクリック
  3. 「Android Device」を選択して「Debug on ANdroid Device」プログレスダイアログを開く
  4. Titanium Studioでアプリをビルドして実機にインストール

作成したアプリでデバッグ用のセッションを開始すると、Titanium Studioは起動設定を作成し、Titanium Android Device – <アプリ名>として保存します。この起動設定をDebugのドロップダウンメニューから選択するとウィザードをスキップして同じ設定の新しいデバッグ用セッションを開始することができます。

アプリのSDKバージョンの指定を変更する場合は:

  1. Debug > Debug Configurationをクリック
  2. 起動時の設定を選択し、Applyをクリックして変更内容を保存するか、Debugをクリックして変更内容を保存した上でデバッグ用セッションを開始

【アプリの起動とデバッグ】

アプリのインストールが完了したら:

  1. 実機上でアプリを実行
  2. Titanium Studioに実機が接続されてデバッグ用セッションが開始(して、Titanium StudioのDebugパースペクティブが表示される)

【関連トピック】

TiCordova

まあ、そんなに他意はないんですが、そういうのを作ってみました。

このモジュールはWebViewに登録したイベントリスナーをリモートのHTMLから起動することが出来るようにするものです。使い方を間違えると大変なことになってしまいますが、例えば信用できる接続先で動作させるととても便利なものになります。

var ticordova = require('org.selfkleptomaniac.ti.mod.ticordova');
var webview = ticordova.createWebView({
  url:'http://example.com/test',
  top:0, height:Ti.UI.FILL,
  width:Ti.UI.FILL
});

webview.addEventListener('my_event', function(e){
  Ti.API.info(e);
  alert(e);
});

アプリ側はこのようにイベントリスナーを用意します。接続先のHTMLは

<a href="fireecent://my_event/data/to/pass">Click</a>

上のようにfireeventというschemaで別ページを開こうとしてください。するとmy_eventというイベントが発火してくれます。ちょっとしたPhoneGap/Apache Cordovaっぽいことができるようになると思うので、TiCordovaと名付けましたが、その辺は冗談です。

TiAudioRecorder

Titanium MobileにはAndroid用の録音機能が実装されていないので、Twitter上で誘われたこともあって空き時間にちょちょいと作ってみました。Java初心者が引き続きなんかやってるとゆるくご笑覧ください。

TiAudioRecorder

最低限の機能しかありませんが、とりあえず録音は出来ています。ファイルは好きなところに設置できますが、フォーマットは3gpで保存します。ただ、最近のモジュール作成方法を追いかけていなかったのであんまり感じのよろしくないAPIになっています。まあ、AndroidのMediaRecorderを使用する限りはメソッドの呼び出し順が決まっているのでそこら辺の制約はJavaと一緒だからいいかと無視しています。

そうそう、Ti.Filesystem.applicationDataDirectoryに保存した音声ファイルはなぜかTi.Media.createSoundで開いて再生することができませんので、録音したデータを再生する際はご注意ください。

使い方は下の通りです:

var audio_file = Ti.Filesystem.getFile(Ti.Filesystem.externalStorageDirectory + 'test.3gp');
var tiaudiorecorder = require('org.selfkleptomaniac.ti.mod.tiaudiorecorder');
tiaudiorecorder.setPath(audio_file.nativePath);
tiaudiorecorder.prepare();

モジュールをrequireしたら、まず録音データを保存するファイルをnativePathを使ってフルパスで渡してください。それからprepareを実行すれば録音の準備が完了します。prepareを実行すると非同期でinitializedイベントが発生するので、録音を開始するときにはaddEventListenerでこのイベントをキャッチすることができます。

tiaudiorecorder.addEventListener('initialized', function(e){
  var dialog = Ti.UI.createAlertDialog({title:'Audio Recorder', message:'OK, we are ready.', buttonNames:['Start', 'Cancel']});
  dialog.addEventListener('click', function(e){
    if(e.index == 0){
      tiaudiorecorder.start();
    }else{
      tiaudiorecorder.reset();
    }
    return;
  });
  dialog.show();
  win.add(dialog);
});

イベントは他に録音開始したとき(start)、停止したとき(complete)、エラーが発生したとき(error)にfireEvnetで発火しますので、それぞれにlistenerを用意するといいでしょう。詳しくはexample/app.jsをご覧ください。

ただ、録音開始になっても冒頭が切れたりすることがあるので、Androidってそういうものだと思って工夫してください。

それから、最大録音可能時間やファイルサイズでキャップしたりそこに到達したときにcallbackを実行させたりすることも可能らしいので、時間ができたらやっておきます。またpauseも追加してみたのですが、うまく動くかよくわかりませんでした。そもそもAndroidのMediaRecorderにはpauseというメソッドはありません。

ImageAsResized for Android更新

無料公開なので誰が使っているのか(あるいは誰も使っていないのか)さっぱり把握していないImageAsResized for Androidですが、前回の更新から1年くらい放置したままだったので、いくつか機能を追加して更新しました。というのも、AppceleratorからImageFactoryというAndroidで使える画像処理用モジュールが出ていたので、ああこれは役割を終えたなと思っていたところ、ImageFactoryを実際の案件で使ってみたらout of memoryが出まくって使い物にならなかったので、またこちらに戻ってきたのでした。

ビルドが面倒な方はAppceleratorのMarketplaceからビルド済みのファイルをダウンロードできます。

追加した機能はこちら:

  • 画像の回転
  • 画像の切り抜き

実際のところ回転は以前も動いていたので、実質的には切り取りを追加しただけですけどね。久しぶりなので復習しておくと、このモジュールには大きくわけて2つの機能があります。ひとつは、カメラやギャラリーから受け取るTiBlobを扱うcameraImageAsResizedとcameraImageAsCroppedのセットで、例えばTi.Media.showCameraのsuccessコールバックの中でe.mediaを渡すとそれをリサイズします。

image_mod.cameraImageAsResized(TiBlob, 幅, 高さ, 回転角度);
image_mod.cameraImageAsCropped(TiBlob, 幅, 高さ, 回転角度, 切り取り開始位置(横), 切り取り開始位置(縦));

切り取り開始位置は0がそれぞれ左端と一番上です。そこから幅と高さで指定された分だけの範囲を切り取ります。

button.addEventListener('click', function(){
  showCamera({
    success:function(e){
      var androimage = require('org.selfkleptomaniac.ti.imageasresized');
      var image = androimage.cameraImageAsResized(e.media, w, h, 0); //これでリサイズされる
      ......
      ......
    },
    error:function(e){...},
    cancel:function(e){...}
  });
});

もうひとつは、ローカルのファイルシステムにある画像を加工するimageAsResizedとimageAsCroppedのペアです。

image_mod.imageAsResized(幅, 高さ, ファイル, 回転角度);
image_mod.imageAsCropped(幅, 高さ, ファイル, 回転角度, 切り取り開始位置(横), 切り取り開始位置(縦));

使い方はこんな感じ。画像素材はexamle/imagesに入っています。

var self = Ti.UI.createWindow();
var image_mod = require('org.selfkleptomaniac.ti.imageasresized');

// 半分サイズ
var cropped_image = image_mod.imageAsResized(64, 106, "images/tzara.jpg", 0);
var imageView = Ti.UI.createImageView({image:cropped_image, top:250});
self.add(imageView);

// 顔のアップ
var cropped_image2 = image_mod.imageAsCropped(24, 56, "images/tzara.jpg", 0, 17, 5);
var imageView2 = Ti.UI.createImageView({image:cropped_image2, top:380});
self.add(imageView2);

// 手のアップ
var cropped_image3 = image_mod.imageAsCropped(64, 26, "images/tzara.jpg", 0, 0, 80);
var imageView3 = Ti.UI.createImageView({image:cropped_image3, top:450});
self.add(imageView3);

// 顔のアップを回転させてみた
var cropped_image4 = image_mod.imageAsCropped(24, 56, "images/tzara.jpg", 90, 17, 5);
var imageView4 = Ti.UI.createImageView({image:cropped_image4, top:520});
self.add(imageView4);

 return self;

注意事項ですが、元の画像のプロポーションと異なる幅と高さを指定すると勝手に画像が切り取られます。まあ、そりゃそうですよね。

Androidあるある

Ti.Media.createAudioPlayerしたら。

 audioPlayer.addEventListener('error', (e)=>
    # htcだと常にこのエラーが出る
    if e.message != 'Unknown media issue'
      audio.pause()
      error_message()
    return
  )