Quantcast
Channel: Y.A.M の 雑記帳
Viewing all 415 articles
Browse latest View live

Google I/O 2013 - Android : Fireside Chat with the Android Team

$
0
0
Fireside Chat with the Android Team


最初は Developer Relations チームによく来る質問から


Q. (Reto Meier) Android は巨大なプロジェクトです。クオリティを犠牲にせずに同じような速度でメンテナンスを続けるにはどうしたらいいでしょうか?

A. (Dave Burke) 僕は単にあっちにいるすごい人たちに助けられてるだけだよ。よくわからない。とてもいい質問だと思う。我々はリリース周りで計画を立てます。リリースでなにをやりたいのか考えます。全チームを横断して調査して、チームと一緒になにができるかを探します。つまりそれの周りに結集するチームのことです。 Android の多くのエンジニア、UX担当、プロダクトマネージャーは何をするかについて情熱的だし、可能な限りベストなプロダクトを作りたいと思っている。エネルギーにあふれていて、本当にすごい、ただそれだけのことです。
その他に言えることは、とても競争の激しい業界であるということです。それがすべての時間を変えています。Android で何かするときはとてもアジャイルにやろうとしています。我々がどこにいて、何が弱くて何が強いのか絶えず評価しています。それに我々の計画をあわせています。これが開発速度を保ついい助けになっていると思います。

A. (Romain Guy) 彼の言いたいことは、彼が僕達をデスクにつなげ続けてるってことだと思うよ。まじで。

A. (Dave Burke) うん。そのとおりw

Q. それについてのフォローアップ質問がきてる。もし開発時にもう少し時間があったら、どこで何をしたかったですか?

A. (Ficus Kirkpatrick) どのくらい時間あるの?

Q. (Reto Meier) 36分くらいあるよw(セッションの残り時間のこと)

A. (Dianne Hackborn) これは好きじゃないと思うけど、いくつかの部分ではことなる方法をするべきだった。 アプリケーション全体に対してをもっとコントロールすべきだった。いい例は設定の provider です。開発者がそれを利用できるようにすると、そこから離れるのはすごく大変です。そういうものをクリーンアップするのにすごく時間がかかる。API の実装についてもそうです。一度開発者が利用できるようにするとリカバーするのはすごく大変です。

Q. (Reto Meier) 他になにかある?

A. (Ficus Kirkpatrick) Content Provider のちょっとしたごみについて Jeff と話してた。長いリストがある。最初に全部を得ようとは思わないだろう。僕らが犯した間違いを公開することはない。僕達がやったスピードでリリースして、フィードバックの機会を得てイテレーションすることがもっとも重要だから。


Q. (Reto Meier) G+ からの質問をします。彼は BOS(?)や Palm OS のあと、現在サードパーティの実装になっている Android IPC 向けの binder software を提案している。Android IPC binder の実装に満足ですか?機会があるとしたら変えたいことはありますか?

A. (Dianne Hackborn) すべてをパーフェクトに得ることはできないということだと思うけどw。まぁ私たちは満足しています。質問は少しミスリードだと思います。Android では我々は異なる環境にいます。C++ などで作られていた以前のバージョンの代わりに IPC 用にただそれを使っているだけです。C++ にはない dynamic language features があります。一度 Android に移ってからは Java でやっています。Java 言語にはすでにそれらの機能(dynamic)があります。なのでやるべきことは我々が今必要なことをして、開発者が必要なものを作ることです。



会場の観客からの質問


Q. Android のアップデートについて。Android の fragmentation を問題だと思っている人が多い。未だに多くの人が 2.2 や 2.3 を使っている。去年の I/O で OEM と協力してより早くスムーズにアプデートできるような施策が発表されたが、その後実際に何かが変わったことを見た人がいない。これに対する努力は続けられているのか?

A. (Dave Burke) とてもいい質問です。実際いろいろなことを考えています。それには複数のレベルがあります。あなたが我々がすることを考えると、オープンソースをリリースし、ベンダーがソースを取得し、彼らの特定の SOC に載せ、彼らが BSP と呼ぶものを作り、OEM 用の BSP を手に入れ、OEM が電話を作る。実際のアップデートが行われるまでにこの大きなチャネルがある。どうしたらこのプロセスを実際にスリム化できるのか考えるなど、多くのことを内部でやっている。一部としてプラットフォームをよりレイヤー化するためのコードを作っている。シリコンベンダーが変更したいときに、それをするためのきれいや抽象化レイヤーがある。我々はそれをスリム化をしようとしている。 他にしようとしていることは、異なる Android Version が走っている異なるデバイスの hardware profile 理解すること。例えば進行成長市場では、Gingerbread に優位性がある。その理由の1つは新しい Android バージョンよりも少ないメモリで動くから。システムはそれほどでもないが、実際のアプリケーションはより前進しリッチになっている。この状況を改善するため、エントリーレベルのスマートフォンに合うように Android をより効果的にする方法を探している

A.(Matias Duarte)Android にとってアップデートがどのように重要なのか再確認するために、別の部分にも言及したい。さまざまな方法でそれについて作業している。OEM パートナーとの関係は我々にとってとても重要です。ときどき君のように何もないように見えるけども、我々にとってはとても複雑な問題なのです。我々にとっても問題になるので、努力しています。我々の努力の小さなサインとして、Galaxy S4 についての発表があります。これは Nexus software の体験をもち、よりタイムリーなアップデートを行う予定です。


Q. マルチコア並列化を開発したい NDK 開発者についてです。新しい機能面で何かしていることはありますか?もしくは今あるツールやライブラリーでおすすめはありますか?

A. (Romain Guy) 僕たちはスレッドを使ってるよ。[会場笑い] OK。フェアにいこう。NDK はとてもシンプルで使うのが難しいことを知っているよ。あなたが気分を良くすることはないと思うけど、native code を書くときに我々にとってよりよいということではない。数年前はすごくたくさん native code を書いていたけど。マルチスレッディングやマルチコアを使うにあたって、あなたが持っていない特定のなにかを我々が持っているということはないです。 ダウンロードできる API が NDK にエクポートされていると思うけど、コア数を作るときにそれに基づいて決定できるのでは。Dianne なにか知ってる?知らないって。


Q. Navigation Drawer について。セッションで言われたんだけど、これの利点の1つはアプリケーションのどこからでもアクセスできること。でも本当に root Activity(もしくは top Activity)からのみアクセスできるようにするべきではない?これを聞いた理由は、もしユーザーがどこからでも Navigation Drawer にアクセスできるようにすると、スタックが複雑になって、バックボタンやアップボタンを押した時にどうなるかユーザーが覚えているのが難しくなるのでは。

A.(Matias Duarte)アプリの構造を考慮しないで単に Activity をスタックに追加しているなら、それは正しくない。

A.(Adam Powell)もうちょっと詳しくいうなら...

A. (Reto Meier) そう?もう終わったんじゃない?

A.(Adam Powell)いやいや、頼むよ。もし Navigation Drawer をアプリケーションの深い階層で使うなら、それらのナビゲーションターゲットは、Notification や Widget からの遷移と同じ方法で扱うべき。遷移先の状態にあうようにタスクスタック全体を置き換える。


再び司会からの質問


Q. (Reto Meier) Android チームでプラットフォームのユーザーエクスペリエンスをデザインするときに使っているツールは何かありますか?また、インスピレーションを受ける場所はありますか?

A.(Rachel Garb)我々のもっとも重要なツールはオープンな環境で一緒に座ることができる我々のスタジオだと思います。常にお互いに話し合っています。だれもが異なるものごとからインスピレーションを得られると思っています。建築や車のデザイン、SF映画、マティアスのシャツw をシェアしているのを見ています。一緒にいてインスピレーションだけでなくいろいろなものやアイディアをシェアしています。


会場からの質問に戻る


Q. Android SDK は JDK1.6 の使用を勧めていますが、これはオフィシャルに終末期にあります。大きい企業では、ヘルプデスクがアップデートするように言ってきます。Android SDK は 1.7 に以降しますか?もしくはロードマップはどうなっていますか?

A. (Dave Burke) 我々は調査し、いくつかのオプションについて考えていますが、まだなにも決定していません。アナウンスできるものは持っていませんが、気にかけてはいます。

A.(Xavier Ducrohet)ちょっとまって、質問は JDK 1.7 のホスト上でツールが動くかどうかってこと?

Q. その SDK でコンパイラが動くかということです。

A. ツールは 1.7 でも動きます。以前は問題がありました。JDK 1.7 がインストールされていると署名ツールが動きませんでした。でもそれは 21.2 で修正されたはずです。なので今は 7 を使っていても適切な署名ができます。


Q. アプリケーションにオプショナルなパーミッションをつけることをどう考えていますか?私のアプリケーションは5つのパーミッションを必要としていますが、ユーザーがその機能を使いたいときにそのアクセスを与えればいいはずです。いい例が Pocket というアプリです。彼らは最近のアップデートで私のコンタクトにアクセスしたいと言ってきました。私はそれに少し不安感を覚えました。

A.(Adam Powell) 僕もそれアップデートしなかったよ

[会場笑い]

A. (Dianne Hackborn) 確かにそれについて思うところはたくさんあります。今すぐにコミットできるものはありませんが、間違いなく我々はそれのことを考えています。


再び司会からの質問


Q. (Reto Meier) Android はさまざまなハードウェアに動いています。そして我々は定期的に新しいプラットフォームと新しいデバイスを作っています。どれが最もチャレンジングなチップセットもしくはデバイスだったか、誰かコメントできますか?

A.(Rachel Garb)全部じゃない?w 私が思うに一番最初のやつではないかしら。なぜなら我々はスクラッチから始めたのだから。現在は、Dave が指摘したように我々の Silicon vendors は BSP と一緒に Android framework を使っています。今は Android が走る BSP が提供されていますが、G1 を始めたときは何もなかった。Qualcomm のハードウェア上の Linux のサポートもなかったし、間違いなくもっともチャレンジングだった。 新しいアーキテクチャでの初めてのときもチャレンジングだと思う。例えば Nexus 10。最も新しい ARM デザインで出荷された最初のデバイスでした。アーリーアダプターであることはいつもチャレンジングです。新しい CPU アーキテクチャの最初、新しい GPU アーキテクチャの最初。いつも小さなバグや新しい機能による痛い体験を伴います。

A. (Dave Burke) 我々はすべてのシリコンパートナーととても近くで働いています。例として accelerating composition に利用する Hardware Composer があります。新しいデザインとともに出てきます。Qualcomm や Nvidia、ARM などと一緒に動いています。「これが実装したい新しいインタフェースだよ。どう思う?」と言い、彼らは我々にフィードバックをくれます。「うーん、これを1チップセットに載せるのはすごく大変だよ。」とか「我々のチップセットでは動くようになるけど、まだやってないよ。こういう ふうに微調整するのはどう?」とか。こうやってイテレーションしてシステムを作るようにしている。これはハードウェアの広い範囲でうまくいっている。我々のハードウェアレイヤーをみれば多くのイテレーションと改善を見ることができるはずだ。我々のゴールはさまざまなハードウェアで Android がうまく動作すること、OEM がすばやく簡単にデバイスをリリースできるようにすること。


Q. (Reto Meier) 我々は今プレートの上にいると思いますか?(成長の曲線が平らになったところをプレートということから、成長が頭打ちになったところにいるか?という意味)全てのハードワークは我々の後ろにいった?新しいチップはなし。新しいセンサーはなし。我々はやりきった?

A. (Dave Burke) いやいや

[会場笑い]

A. (Dave Burke) どうだろう。Android は赤ちゃんみたいだと感じるけどな。できることはまだまだあると感じてる。今年 GPU はようやく十分パワフルになった。GPU で計算をするとか、描画以外のことに使うことを考えられるようになった。我々が RenderScript と読んでいるものを使うと、GPU への C Kernel をターゲットにできる。 新しい周辺機器がたくさん現れているのを見ています。例えば Samsung の S4 には IR トランスミッター がついています。カメラはより進化できる領域だと思います。電話のカメラを考えると、基本的にデジタルカメラ、昔のアナログな Kodak-type のカメラを模倣しようとしている。もっといろいろできることがある。ソフトウェアレベルだけでなくハードウェアレベルでも。広い範囲でまだまだイノベーションが起こると思う。


会場からの質問に戻る


Q. Android をヘルスケアや病院に応用しようとしています。iOS アプリ(カスタムアプリ、ネイティブアプリ)ではすでに大きな投資があることを知っています。それらに Android をポーティングするにあたってアドバイスやより費用対効果が高い方法があれば教えて下さい。あと、Android 用の iOS エミュレーター についてどう思いますか? Linux 用の Wine みたいな。

[会場笑い]

A. (Reto Meier) 法的にこれに答えられる人誰かいる?

A. (Dave Burke) 我々はクパチーノに行って Android アプリをエミュレートよう頼むべきだねw

[会場拍手]

A. (Ficus Kirkpatrick) 最適じゃないユーザー体験のためにやらなきゃいけないことがたくさんあるように思うよ。あんまり面白いとは思わないな。

[会場拍手]

A. (Reto Meier) これが我々の一般的なアプローチだと思う。他のプラットフォームで動いているものを単に持ってくるよりも、ベストな Android アプリを作ろうとしている人を奨励している。

Q. 現実世界のことです。すでにネイティブで書かれたアプリが 1000 あるとすると、それの移行コストは開発者数百年分にもなります。移行戦略または他のアイディアがある人はいないですか?現実の問題なのです。デバイスで動く Android アプリをどう作るか見つけるのにもがいています。

A.(Matias Duarte)ここにいる全ての開発者がヘルスケアにフォーカスしたアプリを書く機会のように聞こえるけど。(みんな退屈だからヘルスケアアプリを開発しだしちゃうよという皮肉)

A. (Reto Meier) ここには価値ある1000人の開発者がいるからね。いい機会だよw

A. (Romain Guy) それについて言及すべきではないんだけど、ぼくの同僚が最近言ったんだ。iOS から Android にポーティングされたあるゲームアプリでは、、、ごめんそのフレームワークの名前忘れたんだけど、誰かが iOS の UI toolkit を Android 上の OpenGL に書き換えたらしい。なので方法はあるんじゃないかな。でもユーザーエクスペリエンスにとっては良くないと思うけど。


司会からの質問


Q. (Reto Meier) Google I/O extended の参加者から質問がきてる。Volley では大きな画像をダウンロードして、必要に応じてリサイズした場合、ディスクキャッシュにはオリジナルサイズの画像が入る?それともリサイズされた画像が入る?

[回答者がちょっと考えたため会場笑い]

A. (Ficus Kirkpatrick) ちょっと待って、、、

Q. (Reto Meier) もっかい質問読む?

A. (Ficus Kirkpatrick) ディスクキャッシュについて聞いてる? OK. オリジナルはディスクキャッシュに行って、リサイズされた方はメモリキャッシュに行くよ。


会場からの質問に戻る


Q. フレームワークについての質問です。ConentObserver を使うと多くの content 変更を受け取る。いつもなんでそんなに多いのかと思っている。

A.(Jeff Hamilton)content が変更されたときに1つだけ受け取ると思うけど。。。もし多くの通知を受け取っているなら。。。

Q. 常に多いわけではない。ただ、データを観測するときは常に多い。アイテムを1つ追加したときにいつも 10, 15, 20 くらい受け取るので、スロットル(?)か何かに入れないといけない。

A.(Jeff Hamilton)観測している ContentProvider は通知を送る責任がある。もし ContentProvider が送るべき数よりもすごくたくさん通知を受け取っているなら集約すべきだね。もしくはトランザクションを使うべきだと思う。データベースを持っているなら、トランザクションを使うべき。もし大量のデータを一度にデータベースにいれるならトランザクションを使って、トランザクションの最後、つまり全トランザクションがコミットされたらその時点で ContentProvider は変更通知を送るべき。

Q. ContentProvider を変更できるということ?

A.(Jeff Hamilton)それぞれの ContentProvider はこれらの通知を送る責任がある。なので、使っている ContentProvider がどのようにデータを操作しているかによる。


Q. Google Play services により多くの API を追加する予定はありますか?これは新しい API を早く得られるということで開発者にとてもいいことだと思いますが、OEM 自身のストアでは使えないということでもありますよね。

A.(Jeff Hamilton)最初の部分について、我々は着実に API の数を増やしてきました。最新のリリースではとても多くの数の API を新しくリリースしました。今も新しい API をとても早く追加し続けています。Ficus、ストアの部分について話したい?

A. (Ficus Kirkpatrick) 2番目の部分は僕が答えるよ。彼らのストアから人々を維持する何かが Google Play services にあるとは思わない。Amazon app store は今でもちゃんと動くよ。


Q. (Reto Meier) ちょっと難しい質問にいこう。みんなパイを味見する機会を楽しみにしていると思う。ここで質問することじゃないのはわかってるけどw、オーストラリア人の観客に代わって、私が知りたい。だれか Lamington を試したことがある人がいるなら、それをどう思うか教えて。

[会場笑い]

Q. (Reto Meier) 誰か、Lamington について、いない?誰もオーストラリアに行ったことない?私の言ってる意味がわからない?

A.(Dave Burke)teleprompter が "lankington" って翻訳したから大きいデザートかなにか? Lamington ってなに?

Q. (Reto Meier) 誰も Lamington について知らないらしい。

A.(Dave Burke)なになに?

A.(Matias Duarte)ミートパイ?羊から作られてる?

Q. (Reto Meier) いつか職場に持って行くよw

A.(観客)ココナッツとチョコレートで覆われたスポンジだよ。

A. (Ficus Kirkpatrick) Tim Tam Slam を持ってたことあるよ。

A.(Dave Burke)Tim Tam Slam? Tim Tam Slam ってなに? Tim Tam とカクテル?

A. (Ficus Kirkpatrick) 茶だよ。

A.(Dave Burke)お茶か。つまらん。

A. (Ficus Kirkpatrick) そんなことないよ、いいよ。

A.(Dave Burke)そっか

Q. (Reto Meier) ははは、元に戻ろう。

[会場笑い]


会場からの質問に戻る

Q. キーノートでラリーペイジが言ったことや前の質問にも関連するのですが、Oracle と Java についてです。Java 7 や Java 8 で実装されている新しい機能のアドバンテージ、言語レベルの機能だけでなく fork, join, stuff などを得ることはありますか?

A.(Dan Morrill)コメントできることはないよ。

A.(Dave Burke)ラリーがなんて言ったか知りたいな。

Q. それは特に Java 7 や Java 8 の機能についての質問ではなかったです。Oracle との関係性についての質問でした。

A.(Dave Burke)Dan が言ったことと同意見だよ。

Q. (Reto Meier) このパネルの誰も意見を持つべきではないと言うことは公正だと思うよ。そしてもしそうするなら、確かに言うべきじゃないよね。

[会場笑い]


司会からの質問


Q. (Reto Meier) 別の質問を投げるよ。Bluetooth のベストプラクティスのセッションを見たんだけど、かなりの数 Bluetooth スタックを変えているように見えた。どうしてそうなのか誰か説明できるならお願いします。なぜ複数の Bluetooth 実装を持つのでしょうか?

A.(Dave Burke)我々は1度だけ変えました。何回もではないよ。

Q. (Reto Meier) 2回だったかも

A.(Dave Burke)そうだっけ。でもライフタイム全体で1回だと思う。最近 Blue Z から Blue Droid に変更しました。理由の1つのほとんどは Jay Kumar がそうしたがったからです。ある日彼は退屈だった、で、こんなふうに「ねぇ、これをみてよ」。でも彼が僕に言ったやりたい理由は、新しいスタックは組み込み機器により効果的だと感じたからだそうだ。以前のスタックは GBUS と GLIB に依存していて、それは組み込み向けにデザインされたものではなかった。それが理由の1つ。また、このスタックを使っている業界の多くの人、とくに Silicon 関係の人と会った。以前に言ったけど我々はスリム化を試みるポイントにあった。そのスタックを採用することは最も抵抗のすくないものの1つだった。さらに新しいこともできるようになる。次のリリースで Bluetooth LE をサポートする予定であることを昨日アナウンスした。


会場からの質問に戻る


Q. 日本の Android デバイスは DUN(?)プロファイルをサポートしているものがありますが、それらは DUN 接続を受け取れません。service discovery profile を処理したいのですが、方法はありますか?

A.(Dave Burke)いい質問だけど答えられないから、明日オフィスアワーがあるからそこに行ったらいいと思うよ。


Q. 多くのアプリが Navigation Drawer を使うと UP の存在感が減ると思うのですが、Navigation Drawer を使うときにどう UP と組み合わせればいいでしょうか?

A.(Matias Duarte)僕から話すね。言いたいことがあれば Adam に代わるよ。間違いなく UP ナビゲーションを使い続けると思っています。すべてのアプリが side drawer のナビゲーションに適しているわけではありません。2つめに、階層を上がる1ステップを置き換えることはいい方法ではない。いつも Back と同じユースケースであるとは限らない。Drawer を介することで2ステップのナビゲーションになってしまうのはやめる。よくない。UP パターンを廃止するつもりはないよ。Android Design Guide をみれば、Drawer を使うとき、そうじゃないときのパターンがある。もっと詳細を聞きたいなら、オフィーアワーにくるのがいいよ。

Q. 一番の質問は、Navigation Drawer を使うと、UP のボタンを押した時に自動で Drawer が開閉するので、ユーザーは混乱するのでは?ということです。

A.(Matias Duarte)いや、我々はその反対を見つけています。Navigation Drawer のアニメーションが作る全く別のアフォーダンスはユーザーにそれをはっきり伝えています。これは上に上がるのではない、これは別のアフォーダンスを与えるものだと。

A.(Adam Powell)これらの2つのことが一緒にどう働くのか、play music アプリの新しいアップデートで見ることができるよ。例えば、あるアーティストやアルバムのページにドリルダウンしたら、UP アフォーダンスを見ることができるし、Drawer を引き出してアプリの他のパートへのクイックリンクすることもできる。

Q. (Reto Meier) なんで UP アフォーダンスは上を指す代わりに左を指しているの?

[会場笑い]

A.(Adam Powell)最初は上を向いてたんだ、誰もみてないけどw

Q. (Reto Meier) 誰もみてないの?

A.(Adam Powell)Honeycomb の最初のリリースは、左上を指してたんだ。


Q. Android の ビデオ技術について2つ質問があります。SpotDash や、新しい video stream protocol や、subtitling や closed captioning のような VideoPlayer への進化を考えていますか? Android の video technology において short-term や mid-term でフォーカスしているものは何ですか?

A.(Dave Burke)いい質問ですね、最初の質問は Dash をサポートするかだっけ?以前のバージョンで多くのことをやっています。開発者が自身でストリーミングクライアントを書けるようにするためのローレベルの API を紹介しています。そのクライアントは HLS や Dash に基づいてるかもしれない。我々は Media Codec, Media Extractor, Media Crypto などを紹介した。あとリリースしてないので言及できないのが1個ある。我々は Dash に多くの興味をもっているので、いずれ紹介できるなにかがあると思うよ。その間にローレベル API を使って自身の Dash プレイヤーを作ることができるよ。closed captioning も同じく注目しています。 2つめの質問については、個人的にはもっとローレベル API に投資するべきだと思う。ストリーミング技術に投資したいと思っている。


司会からの質問


Q. (Reto Meier) 昨年我々は Project Butter について、Android からジャンクをなくす努力について話した。そのミッションは達成された?それともまだプロジェクトの途中?

A.(Dave Burke)まだまだプロジェクトの途中だよ。他の人にマイク渡したいな。他の人が話すべきだよ。

A. (Romain Guy) ジョークを言ってるのは誰?もう終わったよっていいたいよw

A.(Dave Burke)僕たちはチームで、Romain と僕は毎週この問題について作業しているよ。Jelly Bean で我々は多くの進展をはたした。でも、ほかにもたくさんできることが残っていると思う。我々ができると思っているレベルにまだ達していない。Nexus4 やその他のを手に入れたとき、GPU のパワーの恩恵をかなり受けるでしょう。異なるレベルの複数のデバイスでテストする必要があります。16.66 ミリ秒はウィンドウにとって致命的です。それを許すとジャンクです。フレームのスキップを見るだろうということです。致命的なので、我々の全てのアプリケーションが効果的なことを確認します。今でもいろいろやってます。

A. (Romain Guy) フレームワークでいつも新しい機能やアプリケーションを書いています。新しいコードはパフォーマンスにインパクトを与えないことを確認しなければなりません。4.1、4.2 での Project Butter の作業はすべて終わった。我々は新しいラウンドにいる。アプリケーション開発者のために直すまでバグを見つけ続けますw

[会場笑い]


Q. (Reto Meier) Project Butter でわくわくしたことはフレームワーク全体を横断したプロジェクトだったこと。同じようなことで話せることある?

A.(Dave Burke)あるけど、、、それについては言えないな。

A. (Dianne Hackborn) Froyo では、全ての Google Apps を SDK のトップでビルドするようにして、以前の部分をクリーンアップした。それは多くのアプリチームを巻き込んだ作業だった。多くの機能を既存のプラットフォームに届けるために Google Play services に何を許可するのか。
(# ここの話よくわからなかった)

A. (Romain Guy) Honeycomb と呼ばれたやつもあるよ。タブレットに対応するのにあたって多くの作業があった。40のアプリ。フレームワークでの多くの作業。みんなにとって大作業だった。


会場からの質問に戻る


Q. このチームの長年のすばらしい仕事に拍手を送りたいと思う。

[会場拍手]

A. (Reto Meier) こういう質問ほかにないかな?

[会場笑い]


Q. 私たちは Android のバージョン 1 の頃から Eclipse で開発しています。何人かはそれについての本を書いています。IntelliJ への新しい方向性の裏にはどのような理由や考えがあるのですか?

A. (Reto Meier) これの理由について書く必要があるひとがたくさんいるね。そのほうがいい答えになるよ。

A.(Xavier Ducrohet)新しい方向ではなく、単に並列な方向性です。依然として Eclipse を使うことができるし、Eclipse をサポートし続ける予定です。IntelliJ を好む多くの開発者がそれを使いたいと言っていた。我々はその開発者をサポートするべき。Eclipse ユーザーでないのなら、IntelliJ を試すべき。気に入ると思うよ。そうじゃないなら Eclipse を使い続けるのがいいと思う。

A. (Reto Meier) これは面白いですね。以前よく受けていた質問は、どうして Eclipse しかサポートしないのか?だったから。

A.(Adam Powell)Android であって、Ordroid じゃないからねw

[会場笑い&拍手]

A.(Dan Morrill)昔々、IntelliJ がオープンソースになった最初のとき、僕も IntelliJ を好む一人になった、そこで Zav を見つけて言ったんだ。「やぁ、IntelliJ がオープンソースになったね。いつ移行するの?」って。彼はこう言ったんだ。「Eclipse が終わったらね」。


Q. 1つの開発者アカウントで権限を変えたマルチユーザーをサポートするために、Google Play のデベロッパーコンソールをアップデートする予定はありますか?キャッシュレポートにアクセスするだけのアカウントや、ストアのレビューに返信できるだけのアカウントを作るとか。1つの開発者アカウントを大きなチームで共有するのは怖いです。

[会場拍手]

A. (Ficus Kirkpatrick) 拍手ありがとう。でもデベロッパーコンソールはすでに複数ユーザーと複数権限移譲に対応しているよ。もし質問がクラッシュレポートへのアクセス制限についてのものなら、全てのアクセスレベルに対しては終わっていません。でもそのための基礎が今すでにあります。

A. (Reto Meier) 答えをまとめると、今もそれについて作業しているということです。デベロッパーコンソールチームの仕事は驚異的で、その成果はこの I/O でリリースされたものにあらわれている。すぐにスローダウンするということはないよ。


Q. Jelly Bean で追加されたプレゼンテーションフレームワーク、セカンドディスプレイをサポートする機能についての質問です。1つのアプリケーションは2つのディスプレイを覆いますが、2つの独立したディスプレイとして扱う予定はありますか?もしそうならナビゲーションはどのように働きますか?セカンドディスプレイのキーボードやマウスなどのサポートは?

A.(Dianne Hackborn)プレゼンテーションフレームワークそれ自身はとてもフレキシブルです。現在すでにいろいろなセカンドディスプレイがあります。セカンドディスプレイの入力ははじきます。2番目のディスプレイはただのディスプレイです。将来はあるかもしれませんが、現在の機能セットの一部にはありません。





# ところどころ訳に自信ないのでご了承を




Google I/O 2013 - Android : A Moving Experience

$
0
0
A Moving Experience

アニメーションについてのセッション

アニメーションはより説得力のあるアプリケーションを作る助けになるし、よりアプリを楽しくする

# ここには書かないけど、全体的にコントみたいなやりとりが多いので見ると面白いかも?

ユーザーとアプケーションをより結びつけ、アプリの体験を記憶してもらう
クオリティの高い時間をユーザーにすごしてもらう

画面上で起こるアニメーションによって、ユーザーは遷移や変更が実際に起こっていることを認識できる
知るべきことを伝える、状態遷移を伝える、どこから来てどのようにこれを取得したのか


Elements of Good Animations
いいアニメーションの4つの原則
  • Short
  • Smooth
  • Natural
  • Purposeful
Short
  • 必要以上にうるさくしない
  • ユーザーが求めていないときのアニメーションはとてもつらい。ユーザーにとってつらいし、開発者にとってもつらい。開発時に何回も何回も見ないといけないから
  • ViewPropertyAnimator はデフォルトでアニメーションの時間を 300ms にしている
    300ms はモバイルデバイスの多くのアニメーション時間に適してる
    十分短いが短すぎるわけではない
    ただし、これが答えというわけではない。すべてのアニメーションを 300ms にしろ、というわけではない
    シチュエーション、ビューの大きさ、アニメーションの複雑度、移動する距離などに依存する
  • ウィンドウのアニメーションがいい例。これはスケールとアルファのアニメーションを組み合わせて、フルスケールのアニメーションをさせなくても 0 からサイズが変わったように感じられる
  • 小さいボタンを大きいボタンを同じ時間、同じ距離を移動させた場合、ユーザーには異なるように感じられる。
    「つまり、なんでガリレオは石と羽で実験したのかっていうと、アニメーションの時間をテストしてたのさw」(ジョークね)

Smooth

いろんな効果を組み合わせてかっこ良くしてもガタガタしてたらユーザーは嫌になる
最初から最後までスムーズな体験

1. アニメーション中にレイアウトを走らせないこと
2. レイアウトなどの重い処理はアニメーションを開始する前に行っておくこと

複数のアニメーションをつなげるときに、加速カーブを使うなど、滑らかにつながるように気をつける


Natural

ナチュラルだと感じられる様にする
単にアニメーションしていることを気づかせるだけでなく、なぜそれが必要なのか伝える
かっこいいからという理由だけでアニメーションするのはよくない


Purposeful

アニメーションに目的をもたせる

「僕の好きな例は、、、Linux のだったかな?」「Linux Desktop だね。あるウィンドウマネジャーがあって、ウィンドウを閉じるときに炎に包まれて消えて煙が出るんだ。かっこいいよ。。。でもそうしちゃだめだよw」「一番最初はすごく楽しんだけど、、、」「やってもいい状況が1つだけあるよ。カンファレンスのステージに上がってそれを見せて、観客にやるなって言うんだ」

UI でなにが起こりつつあるのか見せる
重要なアクションを示す

アニメーションの理由はかっこいいからじゃない


List Animation

リストのアイテムを右にスワイプして消すことができ、消した後その部分が閉じるアニメーションのサンプル

ArrayAdapter を継承した StableArrayAdapter クラス
  • stable ID を getItemId() で返す
  • getView() で一行の View に setOnTouchListener() をセットする
FrameLayout を継承した BackgroundContainer クラス
  • アイテムがスワイプアウトされたときの背景(ドロップシャドウあり)を描画する
  • 上と下用の2つのドロップシャドウを作って(9-patch を使ってもOK)、背景を表示すると言われたときに setWillNotDraw(false) をセットして描画する
  • onSizeChanged() でサイズが変更されたらドロップシャドウのサイズをアップデートする
  • onDraw() を Override して、canvas を translate してドロップシャドウを描画する
Activity を継承した ListViewRemovalAnimation
  • アニメーションのロジックのすべてがある
  • StableArrayAdapter を生成して ListView にセット
  • View.OnTouchListener でアクションをトラックしてスワイプに応じて透明度を変更
  • 指が離れたときに位置と速度に応じて、戻るのかスワイプアウトするのか決めアニメーションを開始する
  • アニメーションは ViewPropertyAnimator で alpha(), translationX(), withEndAction() を使う
  • リストのアイテムが消えるとその分リストがつまるので、つまる前の位置を保持しておき、つまる前からつまった後の位置までアニメーションさせたい
  • リストのアイテムが消えるとレイアウトが走り、他の子ビューの位置を変更する
  • まずレイアウトが起こる前に情報を集める
  • JellyBean で追加された setHasTransientState(true) を他の子ビューにセットする
  • ListView に false をセットするまでビューのコンテンツを台無しにしないために重要
  • それから ViewTreeObserver.OnPreDrawListener をセットする
  • レイアウトパスの結果を描画する前に OnPreDrawListener の onPreDraw() が呼ばれる
  • ここで OnPreDrawListener を remove
  • レイアウトが起こった後の情報を集め、レイアウト前の位置からレイアウト後の位置までアニメーションさせる
  • 終わったら setHasTransientState(false) をセットする

コードはあとで公開される


Activity Animations

window animation をカスタマイズする方法を提供しているが、とても制限がある

JellyBean 4.1 からサムネイルの状態からスケールアップして起動できるようになったが、カスタマイズできない

そこで、、、アプリ内のサムネイルをタップして写真を大きく表示するサブアクティビティを表示するサンプル
  • サムネイルの状態では白黒
  • サムネイルの位置からスケールアップして画像が画面全体に表示される
  • スケールアップに合わせて白黒じゃなくなり、背景も暗くなる
  • 画像のスケールアップが終わったら、詳細が画像のしたからアニメーションで出てくる
  • サブアクティビティは透明の Activity
  • トリックはすべてこの透明な Activity で行う

サムネイルを表示している Activity では
  • Grid を表示して、ColorMatrix filter を使って白黒にしているだけ
  • サムネイルがクリックされたら、getLocationOnScreen() でサムネイルの画面上の位置を取得、orientation を取得
  • これらの情報を Intent を介してサブアクティビティに伝える
  • startActivity() を呼んだあとに overridePendingTransition(0, 0); を呼んでデフォルトのアニメーションが走らないようにする

サブアクティビティでは
  • savedInstanceState が null かどうかチェックして、画面回転時にアニメーションが走らない様にする
  • ViewTreeObserver.OnPreDrawListener をセットする
  • onPreDraw() が呼ばれたら、OnPreDrawListener を削除し、getLocationOnScreen() で画像の位置を取得
  • もとのサムネイルとのスケールを計算し、Intent で渡された位置からレイアウトされた位置までアニメーションする
  • 合わせて背景の透明度、画像の saturation(ColorMatrixColorFilter を利用したカスタマイズ property)なども一緒にアニメーションする
  • finish() 時に overridePendingTransition(0, 0); を呼ぶ
  • バックボタンが押されたときに、サムネイルに戻るアニメーションを実行してから finish() を呼ぶようにする


ShadowLayout
  • BlurMaskFilter を使って深さを表現したレイアウト

コードはあとで公開される



Cartoon Animation Techniques
  • ディズニーのアニメーションについて
  • 彼らはアニメーションの12の原則を作った
  • これらは観客の理解を助けることにフォーカスしていた
Squash and Stretch
  • 原則の1つに Squash and Stretch というのがある
  • ボタンが跳ねるときのアニメーションを例に説明
  • 2つのアニメーションをつなげるときにナチュラルになるようにしようということ


ボタンが画面の端に達したら横に広がるアニメーションを走らせて、それが終わったら跳ね返るアニメーションを走らせる


Anticipation

走り出す前に、走りそうなモーションを入れたりすること
のけぞってから動きだし、止まったところで揺れるボタンのアニメーションサンプル

Follow Through and Overlapping Action

自然界では壁にぶつかったとして、すぐに止まれるわけではない Non-linear motion を利用する サンプルアプリのデモ Path アプリの画像ストレッチ


ViewTreeObserver

便利なクラスなので見ておくこと
システム内部でもよく使っている

Layers

アニメーションさせたい複雑なビューや複雑なビュー階層がある場合、アニメーション中に hardware layer をセットすることを考える
実際に変化するものには使わないこと、フレーム毎にレイヤーを再生成しないといけなくなるから

More info





Google I/O 2013 - Android :Beyond the Blue Dot: New Features in Android Location

$
0
0
Beyond the Blue Dot: New Features in Android Location

我々がやってきたことについて紹介したい。
1つ目は新しいタイプのアプリが可能になるということについて。2つ目はロケーションを支える進化したテクノロジーについて。

Fused Location Provider
Geofence
Activity Recognition

最後に新しい Location API のポテンシャルが何かを見る機会をもうけたい。


Growing Importance of Location

有名なアプリのほとんどが位置情報を使っている。本当に世界中でたくさんのアプリが位置情報を使っている。
GoogleMap, Google+, Facebook, Twitter, Google Now, 著名なアプリケーションはみんな使ってる。

3つの質を改善する機会を与えたい
・Power(電池消費)
・Accuracy(正確さ)
・coverage(検出範囲)


New Type of App: Contextual Apps

Contextual App の例の1つが Google Now。
Google Now チームはカードを追加し続けているが、Google Now は全てのカードにおいてコンテキストに基づいている。
本質的に、深く位置情報に紐づいている。

多くの Contextual App ではこれが正しいと我々は思っている。
世界クラスの Contextual App を本当に作りたいなら、ハイクオリティな位置情報が必要だ。
しかし、問題がある。Google+ のような Contextual App が必要としているものは、Google Maps のようなアプリが必要としているものとは少し異なる。

Google Maps を使うときは情報をすぐに欲しいとき。タスク指向。Map を起動して何かを達成する。
Google Now はこれとは大きく異なる。Google Now は時々通知するし、ユーザーはときどき Google Now を引っ張りだして待ってたり、準備できてたりするものがあることを期待する。
使い方のモデルが異なる。
位置情報で必要としているものも違う。

例えば、電源消費について見てみると、スクリーンがオンのとき約350mW消費され、プロセッサーがオンのとき約300mW消費される。それと比較して、GPS は100mW消費する。画面をオンにしてマップを見ているとき、GPS は 13%, 14% くらいを消費している。
Google Now のようなものだと、常に100mWを消費すると電池はすぐに枯渇してしまう。
電池にはすごく注意しなければならない。

正確さについても見てみよう。Google Now のようなアプリでは常にすごく正確なものである必要はない。



ハイクオリティの位置情報モデルを構築するのはものすごく大変。

ハイクオリティの位置情報アプリを作るには4つのコンポーネントとやり取りする必要がある。
  • GPS
  • Wi-Fi
  • cell(基地局)
  • sensors(センサー)
それぞれのコンポーネントはことなる特徴がある。
GPS は電池消費が大きく100mW。一方 Wi-Fi は約30mW で 1/3。
しかし、正確さは GPS がすばらしい。Wi-Fi はときどきよくて、ときどきすごいだめ。
Coverage では GPU は屋内で使えない。
基地局は電池消費が少ないが正確さはよくない。1/2 マイル(=約800m)くらい。



複雑さの問題について

ハイクオリティの位置情報アプリを作るには3つの特徴 Power, Accuracy, Coverage を理解して、上図の複雑な処理を扱わなければならない。GPS から LocationProvider を取得し、Passive Location Provider も使いたいかもしれない。センサーともやりとりしないといけない。
開発者は単に位置情報を欲しいだけなのに、これらすべての面倒を見ないといけない。



ロケーションのドキュメント。複雑な処理のタイムラインにうんざりする。
この面倒な処理を我々は新しい API 内でやった。


センサーの重要性について

センサーはゲームだけじゃない。



センサーは位置情報の取得にも有用。Accelerometer は平行・側面の移動を、gyro は回転スピードを、コンパスと magnetometer はどっちの方角を向いているか、barometer は高さを検出できる。

センサーは電池消費を抑えるのにも使える。座っている状態なら位置情報をアップデートする必要はない。accelerometer を使えば座っているかどうかわかる。
どういう活動をしているか検出することもできる。運転しているならより頻繁に位置情報をアップデートしたほうがいい。
クオリティ、とくに屋内での正確性にも役立つ。

[モスコーニ内を移動したときの実際の位置と Fused Location Provider で検出した位置の遷移の動画デモ]

[WiFi だけで検出した場合も比較として動画デモ]

Goals of the Changes
  • 電池消費を抑える
  • 正確性を改善する(屋内でも十分な正確性をとれる)
  • APIs をよりシンプルにする(シンプルにしただけではない、より効果的になっている)
  • クールな新しい機能を提供する
  • ほとんどの Android デバイスで利用できるようにする


Fused Location Providero

上記の複雑な処理が Fused Location Provider を使うとこうなる。



この下で何をしているのか知りたいですよね?

[実際の位置と GPS の位置を遷移を示した動画]

GPS は屋内では全然使えなく、人が屋内からでてきたとき位置に少しジャンプがある

[実際の位置と WiFi の位置を遷移を示した動画]

Wi-Fi は屋外では全然ダメ、屋内では結構いい、また屋外にでると全然使えない

そこで Fused Location Provider では GPS, WiFi, Cell, sensors を組み合わせ利用している

[実際の位置と Fused Location Provider の位置を遷移を示した動画]

屋内でも屋外でも正確にトラッキングしている。

・Listeners vs Pending Intent
・Foreground vs Background

アプリがフォアグラウンドにいる場合、Listener を登録して、同期で位置情報を取得する方法は適している。
マップアプリやナビゲーションアプリなど。

Contextual Apps ではバックグラウンドで動作したい。アプリはロケーションプロセスと強く紐づく必要はない。そのために PendingIntent を使う。
PendingIntent とは何か?これはアプリからロケーションプロセスに渡すトークンで、ロケーションプロセスは必要なときにそれを使ってアプリを起動する。つまり、バックグラウンドで常に動いている必要さえないということ。
そのため電池消費を抑えられる。

mLocationClient = new LocationClient(...); mLocationClient.connect(); // public void onConnected(Bundle bundle) // Get the latest known location. Useful when you need location quickly. Location location = mLocationClient.getLastLocation(); // Create location request. LocationRequest locationRequest = LocationRequest.create() .setInterval(5000).setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY); LocationListener mLocationListener = new LocationListener() { @Override public void onLocationChanged(Location location) { Log.i(TAG, "Received a new location " + location); } }; // Request updates. mLocationClient.requestLocationUpdates(locationRequest, mLocationListener); LocationClient のインスタンスを作って connect() を呼ぶ。そうするとコールバックの onConnected() が呼ばれる。getLastLocation() を呼ぶと、システムが管理している最新の位置情報がすぐに返ってくる。

プライオリティやインターバルをセットして位置情報のリクエストオブジェクトを作成し requestLocationUpdates() を呼ぶ。


PendingIntent の方法 PendingIntent mIntent = PendingIntent.getService(...); mLocationClient.requestLocationUpdates(locationRequest, mIntent); public void onHandleIntent(Intent intent) { String action = intent.getAction(); if (ACTION_LOCATION.equals(action)) { Location location = intent.getParcelableExtra(...) } } getService() を呼んで PendingIntent を作成し、requestLocationUpdate() を呼ぶ。LocationRequest の作成方法は Listener のときと同じ。

getLastLocation() はブラウザの検索のように、すぐに大ざっぱな現在地が知りたいときに使える。requestLocationUpdates() はマップアプリなどで使える。



Priority には3種類ある。
HIGH_ACCURACY は屋外では GPS を使い、屋内では Wi-Fi や cell を使う。典型的なインターバルは5秒。マップやナビゲーションアプリ向け。正確性が高いが電池も消費する。
BALANCED_POWER は GPS を使わない、Wi-Fi や cell を使う。典型的なインターバルは20秒。
NO_POWER は別のアプリのリクエストで取得した位置を取得するときに使える。正確性はそのときの状況に依存する。


Geofencing

ある領域を定義し、ユーザーがそこに入ったり出たりしたときに通知する。

[Geofencing のデモ]

アプリあたり100個の Geofence をサポートする。

[興味のある家をお気に入りに入れておくと、近くにいったときに通知してくれるアプリのデモ]

mLocationClient = new LocationClient(...) mLocationClient.connect(); // - Entry point for Location and Geofence APIs public void onConnected(Bundle bundle) { Geofence geofence = new Geofence.Builder().sentRequestId(stringId) .setTransitionTypes(transitionTypes) .setCircularRegion(getLatitude(), getLongitude(), getRadius()) .setExpirationDuration(expiration).build(); mLocationClinet.addGeofences(List geofences, PendingIntent intent, ...); } geofenceId, transition type(範囲に入ったり出たりするときのトランジションについて), 領域のための latitude, longitude, radius(geofence は現在円形の範囲をサポートしている), geofence が無効になるまでの時間をセットする。 public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (GeofenceActivity.ACTION_GEOFENCE.equals(action)) { // This is an intent that indicates geofence transition. handleGeofenceCrossed(context, intent); } } private void handleGeofenceCrossed(Context context, Intent intent) { int transition = LocationClient.getGeofenceTransition(intent); List crossedGeofences = LocationClient.getTriggeringGeofences(intent); } Geofence を横切るとコールバックが呼ばれる。


addProximitlyAlert API がすでにあったが、それと比較して Geofence は電池消費が 1/3 くらいになる。



例えばサンフランシスコにいてゴールデンゲートブリッジを楽しんでるとする。一方でハリウッドとロサンゼルスに Geofence がある。電話はどこにいるのか知っているので、geofence を横断したかどうかポーリングし続ける必要がない。
ユーザーが止まっていたり歩いていたりする。電話はそれを知っているので、それをポーリング間隔を調整するのに利用する。
我々はハードウェアでの処理にも移行したい。
geofence は基本的にバックグラウンドでの処理。つまりアプリケーションの処理担当は常に起きている必要は無い。geofence のモニタリングをハードウェアでできる。GPS のハードウェアのように、ハードウェアが我々に geofence に入ったよと伝えることができる。


Activity Recognition

スマホのセンサーを使って、ユーザーが車にのっているのか、歩いているのか、座っているのか、自転車に乗っているのか検出する。 多くの Acitivity データを集めて機械学習でクラス分けを行い、ユーザーが今していることがどのクラスのものなのかわかるようになった。



Endomondo というフィットネスアプリの例

Moves という 3rd-party の Activity トラッカーアプリの例

[エンジニアがポケットにスマホをいれて、ステージ上を歩いたり自転車に乗ったりして Activiy Recognition が動くかどうかのデモ]

Activity から Activity へ遷移しているときなどは TILTING という状態になる。 ActivityRecognitionClient mActivityClient = new ActivityRecognitionClient(...); mActivityClient.connect(); // Called when a connection to the ActivityRecognitionService has been established. public void onConnected(Bundle connectionHint) { mActivityClient.requestActivityUpdates(30000, callbackIntent); } /** * public void requestActivityUpdates(long detectionIntervalMillis, * PendingIntent callbackIntent) */ ActivityRecognitionClient を作成し、connect() を呼ぶ、接続したら requestActivityUpdates() でアップデートを受けとる。 protected void onHandleIntent(Intent intent) { if(ActivityRecognitionResult.hasResult(intent)) { ActivityRecognitionResult result = ActivityRecognitionResult.extractResult(intent); DetectedActivity activity = result.getMostProbableActivity()); } } // DetectedActivity - IN_VEHICLE, ON_BICYCLE, ON_FOOT, STILL, TILTING // Detected Activities have associated probabilities. // Apps would need to use new ACTIVITY_RECOGNITION permission 新しい ACTIVITY_RECOGNITION パーミッションが必要。


Making It Available Everywhere

新しい API は今すぐ使える
・Fused Location Provider
・Activity Recognition
・Geofencing

・アプリは Cient library を一緒にビルドする



API を提供するのに Google Play Services を使う。
Google Play Services は Play Store からアップデートされる単一の apk。


なぜ Google Play Services なのか?
  • Backward compatible to Froyo
  • Google Data Models(屋内の位置検出など)
  • Shorter Release Cycle(プラットフォームのアップデート待つよりもっとずっと早く改善できる)



What's next

これからのこと。
1つ目は正確性の改善。GPS, Wi-Fi, cell, sensor に続く新しいコンテキスト。たとえば Bluetooth low energy が次バージョンでサポートされる。屋内での正確性をもっと改善したい。飛行時間を考慮することで Wi-Fi の位置のジャンプを取り除く 802.11v(?)のような基準がある(# この文ちょっと自身ない)。新しいテクノロジーでより位置情報を改善していく予定。開発者は何もする必要がない。表に新しいコンテキストが追加されるだけ。
屋内の floor plans(?)を使って正確性を改善できる。
geofence のときに言及したように多くのことをハードウェアでもできる。
すでにハードウェアベンダーと一緒に作業をしている。
Coverage も改善したい。本当に屋内で検出できるようにしたい。




QA

Q. geofence の制限の100個はアプリ全体に対してですか?それとも複数ユーザーがそれぞれ100個の geofence を使えますか?
A. ええと、ちょっとチェックします。
(Android の fireside chat で多分同じ人が質問していて、(なぜかというとセッションで質問したんだけど、、、と言っていたので)そこではアプリ毎に100個という回答だった)

Q. ADK の一部になる予定はありますか?
A. いろいろなオプションを考えているところないので、その質問には今すぐは答えられないです。

Q. デバイスをバックパックに入れている状態でもちゃんと Activity を認識しますか?
A. いろいろな異なるタイプのデータをテストしたので、パーフェクトではないですが、だいたいはうまく動きます。

Q. 現在のスピード、時速はとれますか?
A. ちょっとわからないや。チェックします。オフィスアワーにきてください。

Q. Activity が切り替わるときにラグタイムはどのくらいですか?
A. どういうトランジションをしているかによるけど、多くて30秒〜1分くらいかな。さっきここでデモしたときは歩きから自転車に変わるまで8秒だったよ。

Q. 車で移動しているときに運転しているのか、乗っているだけなのかわかりますか?
A. 今は出来ません。将来はできるかもね。





Android : Mobile Backend Starter のサンプルを試す

$
0
0
Mobile backend starter はすぐにデプロイして使うことができるサンプル。
これを使うと、Android クライアント用のクラウドバックエンドを簡単に構築することができる。
また、バックエンドとやり取りする Android クライアント用のフレームワークと、それを利用したサンプルアプリも提供されているので、バックエンドとのやりとりをすぐ試すことができるし、フレームワークをどう使えばいいかもわかるので、自分の Android アプリに組み込む方法もわかる。

バックエンド側のコードはいっさい書かなくていいし、バックエンドのアクセス部分のコードはフレームワークとして提供されているので、アプリの機能や UI に集中できるというのが利点。

クライアント側のサンプルアプリとして GuestBook と SocialTalk というのがある。
このサンプルでは、Google Cloud Messaging を使用して、同じアプリをインストールしている他のデバイスにメッセージを送信することができる。複数人チャットみたいなことができる。

Mobile Backend Starter - Google Cloud Platform — Google Developersに沿ってサンプルを試してみたのでそのメモ。





Getting Started

警告:Prospective Search 機能はこのサンプルであっても実験的であり、自身の責任で使用すること。

必要なもの
  • Google Account
  • Eclipse 3.8 or 4.2 で Android アプリを開発できる環境
  • 4.0.3 (API Level 15)以上の Android SDK と Google APIs
ドキュメントには以下の項目も必要とあるが無くても動いた
  • Google Plugin for Eclipse (GPE) 3.8/4.2 を Eclipse のリンク先の指示通りに、以下の項目をインストールする
    • Developer Tools > Android Developer Tools
    • Google App Engine Tools for Android
    • Google Plugin for Eclipse
    • SDKs > Google App Engine Java SDK
  • Extras の中の Google Cloud Messaging for Android




デプロイ・ダウンロード・サンプルの実行

やること
・サンプルのためのプロジェクトを作成する
・プロジェクトにバックエンドをデプロイする
・Android クライアントアプリのサンプルをダウンロードし、必要な ID をコードに記入しビルドして実行する


サンプルのためのプロジェクトを作成する

1. Google Account にサインインする

2. https://cloud.google.com/solutions/mobile に行く

3. Try it now をクリックする



New Project のフォームが開かない場合は Create Project ボタンを押す

4. プロジェクト名とプロジェクトIDを指定してプロジェクトを作成する

プロジェクトIDは全世界で一意である必要がある。プロジェクトIDのテキストボックス内の更新ボタンを押すと利用できるプロジェクトIDを適当に生成してくれる。
このプロジェクトIDは Google App Engine のコンソールGoogle APIs のコンソールでプロジェクトを識別するのに使われる。


プロジェクトにバックエンドをデプロイする

1. プロジェクトの Cloud Console 画面に行く

プロジェクトを新規作成したあとに遷移する画面 Google Cloud コンソールで作成したプロジェクトを選択しても行ける

2. 上側の2つのドットの2番目をクリックして Mobile Backend App を表示する



3. Deploy ボタンを押す



Deploy 中はポップアップが出るので、Details を押せばどこまで進んでいるのか見れる



Deploy が終わったら、Deploy ボタンの表示が "Deployed on [日付]" になる

4. 2の Settings ボタンを押す



5. Mobile Backend settings の Authentication/Authorization を Open にして Save をクリックする(この時点では他の値はいじらない)



保存できたら Done というポップアップが表示される

重要: Open 設定は開発時向けであり、プロジェクトIDを知っている人であればだれでもアクセスできる。アプリ開発時でもアクティブに使用しないときは Locked Down に変更すること!


Android クライアントアプリのサンプルをダウンロードし、必要な ID をコードに記入しビルドして実行する

1. zip ファイルをダウンロードする

2. zip ファイルを解凍する

3. Android プロジェクトを Eclipse に Import する

Eclipse を立ち上げて File > Import > Android > Existing Android Code Into Workspace を選択



Root Directory に展開したプロジェクトのディレクトリ(solutions-mobile-backend-starter-android-client-master)を指定して Finish をクリック



4. プロジェクトのプロパティの Android でビルドターゲットが API Level 15 以上の Google APIs になっていることを確認する



5. src/com.google.cloud.backend.android の Consts.java を開く



Google APIs Consoleに行って、Project ID を調べ Consts.java にセットする。(Project Number は GCM を利用する場合に必要になる、あとで説明する)



6. Android アプリをビルドし、Android 4.0.3 以上の実機、もしくはエミュレータで実行する

メッセージを送ると、タイムスタンプとともに戻ってきて表示される

7. バックエンドに保存されているデータを見る

Google Cloud Consoleのプロジェクトから App Engine をクリックして、Cloud Datastore をクリックする



左側の Query を選択し、Kind で GuestBook を選択して RunQuery ボタンを押すと、保存されているデータが表示される



チェックボックスでチェックしたデータを削除することもできる





サンプルで Google Cloud Messaging を使うようにする

1. Google API Consoleに行く

2. 左側の Services を選択する



Google Cloud Messaging for Android を ON にする。必要に応じて利用規約確認する。



3. 左側の API Access を選択する



Create new Server key... ボタンをクリックする



Create ボタンを押す



IP アドレスは空で OK(server key が IP アドレスのホワイトリストを使えるようにする場合に利用する機能)

Key for server apps という項目が追加される



4. Cloud Consoleの Mobile Backend App の Settings ボタンを押す



Google Cloud Messaging を Enable にする



Google Cloud Messaging API key の部分にさっきの Key for server apps の API key を入れて Save ボタンを押す

保存できたら Done というポップアップが表示される

5. src/com.google.cloud.backend.android の Consts.java を開く



Google APIs Consoleに行って、Project Number を調べ Consts.java にセットする。



6. Android アプリをビルドし、Android 4.0.3 以上の実機、もしくはエミュレータで実行する

エミュレータの場合は Target: を Google APIs にする



メッセージを送ると、タイムスタンプとともに戻ってきて表示され、別のデバイスの同じアプリにも自動でプッシュされる

7. Mobile Backend settings の Send Cloud Message で topicId を _breadcast のまま send ボタンを押すと、message= で指定したメッセージが Android アプリ側にトーストで表示される







サンプルに認証を追加する

ドキュメントには「認証を追加したサンプルはエミュレーターでは実行できず、実際のデバイスを使う必要がある」とあるが、Target に Google APIs を指定したエミュレータなら実行できます

モバイルバックエンドサンプルに認証を追加するには以下の手順が必要
  • 1. web client ID を作成する
  • 2. Android client ID を作成する
  • 3. web client ID と Android client ID をモバイルバックエンドの設定画面でセットする
  • 4. web client ID を Android クライアントアプリの Consts.java に記入する


Web Client ID を作る

1. Google API Consoleにアクセスし、左側の API Access を選択する



2. Create an OAuth 2.0 client ID ボタンもしくは Create another client ID ボタンをクリックする



3. Create Client ID ダイアログで
・Application type を web application にする
・Your site or hostname のスキーマ部分を http:// にする
・Your site or hostname のアドレス部分を www.[プロジェクト名].appspot.com にする



Create client ID ボタンをクリックする

Client ID for web applications という項目が作成される




Android client ID を作成する

Android Client ID を作成するには、署名キーの SHA1 fingerprint が必要
アプリをリリースするときはリリース用の署名キーで Android Client ID を作成する!
ここでは debug.keystore を使う

1. 署名キー の SHA1 fingerprint を取得する

$ keytool -exportcert -alias androiddebugkey -keystore ~/.android/debug.keystore | openssl sha1
パスワードは android

fingerprint が表示されるのでメモしておく

2. Google API Consoleにアクセスし、左側の API Access を選択する



3. Create an OAuth 2.0 client ID ボタンもしくは Create another client ID ボタンをクリックする



4. Create Client ID ダイアログで
・Application type を Installed application にする
・Installed application type を Android にする
・Package name: にアプリのパッケージ名(サンプルだと com.google.cloud.backend.android)を入力する
・Signing certificate fingerprint (SHA1): に 1. の fingerprint を入力する



Create client ID ボタンをクリックする

Client ID for installed applications という項目が作成される




web client ID と Android client ID をモバイルバックエンドの設定画面でセットする

1. Google Cloud Consoleでモバイルバックエンドプロジェクトを開く

2. Cloud Consoleの Mobile Backend App の Settings ボタンを押す



3. Authentication/Authorization を Secured by Client IDs に変更し、Web Client ID を Android Client ID を入力し、Save を押す




web client ID を Android クライアントアプリの Consts.java に記入する

1. src/com.google.cloud.backend.android の Consts.java を開く

Web Client ID を WEB_CLIENT_ID 定数にセットし、IS_AUTH_ENABLED 定数を true にする



アプリを実行すると、最初に使用する Google アカウントを聞かれるようになる
メッセージを送ると <anonymous> だった部分が Goole アカウント名になる





# おおお、チャットできるぞい!いやー、サーバー側のコード全く書いてないのにすごいねぇ!



Google I/O 2013 - Android : Structure in Android App Design

$
0
0
Structure in Android App Design

我々が"構造"について話すとき、それはどういう意味なのか。みなさんが最初に思いつくのは建築の図面ではないでしょうか?ここでの構造は、各スペースが何を意図しているものか決め、また行いたい活動をどうサポートするか提供します。構造は直接見えるものだけではありません。骨は私たちの体の構造を提供しています。骨により筋肉や内蔵を適切な場所に収めることができます。

"Does your app have good bones?"

我々のアプリケーションはこれらの例両方に似ています。空間を定義し、適切な場所に機能を取り付ける必要があります。

よく構造化されたアプリは少なくとも次の3つの特徴があります。
  • 何かを行うのが簡単かつ効果的
    家ではドアの幅は出入りするのに適切なサイズだし、床は硬いし、何かを行うのに適切な光量がある

  • 関連する項目は一緒にし、関連しない項目は離す
    近接の効果を利用する。台所とダイニングは繋がっていて、ガレージで区切られていたりはしない

  • 積極的に一貫性のあるナビゲーションを通して複雑さを管理する
    フロントの部屋はすべて互いに繋がっているが、客室は共通の廊下につながっている
"How do you find your app's natural structure?"

どうやってアプリケーションのための自然な構造を見つけるのか。その説明として Barkeeper という近くのバーを探せるアプリのデザインを例に説明します。

"What does my app do?"

形は機能に従うという何度も繰り返された教訓を聞いたことがあると思います。まず、"このアプリは何をするのか?"をはっきりさせましょう。これに答えることによって、アプリでの作業を支えるために必要な構造を考えやすくなります。

この答えはバズワードで飾られたエレベータピッチのようなものではありません。また、機能のリストでもありません。機能のリストは低レベルすぎてユーザーのゴールからかけ離れています。この2つのちょうど中間のものが必要です。幸いなことにモデリングテクニックがあります。

ユースケース : あるタスクを達成するためのシステム(= アプリ)とアクター(= ユーザー)間のインタラクションの流れ

物語の説明としてユースケースを記述するとこんな感じになります。



BarKeeper にはバーを見つけるというユースケースがあります。これに入る前に、このユースケースの主なアクターが誰で、どのようなことが起こらなければいけないのか識別しておきます。ここでのユースケースのコアはアクターとシステム間の対話です。 これをいろいろ拡張できます。このタスクのバリエーション、例えば近くのバーを見つける、を考えたり、成功や失敗などの事後条件を考えたり。とてもシンプルの保つのがいいと思います。これの価値はフォーマットではなくコンテンツにあります。


ユースケースダイアグラム

アプリケーションの構造にとても役立つのですが、しばしば見落とされることがあります。ダイアグラムのコンポーネントは、アクター、ユースケース、関係を表す矢印、の3つだけです。



BarKeeper のユースケースダイアグラムはこんな感じになります。



ユースケースダイアグラムはとても簡単に描けます。これを使うととても早く概要を把握することができます。 ユースケースダイアグラムを描くステップ
  • 1. Inventory
    必要なアクターとユースケースを用意する。物語としての説明はいらない。ユーザーの観点からアプリにさせたいことが必要。

  • 2. Prioritize
    大雑把な優先順位をつける。High, Medium, Low でいい。ユースケースの優先順位付けで重要なのは、どれが一番重要なユースケースで、どれがそれほどでもないのかを見つけること。アプリでもっとも頻繁にすることは何か、最も重要なことはなにか、という視点から始めるとよい。

  • 3. Sequence
    個別のアクターからどのユースケースに直接行きたいのか、どのユースケースは他のユースケースの拡張なのかを順序付ける。



  • 4. Decompose
    幅広い、漠然としている、多くの基礎をカバーしている、と感じるユースケースがあるなら、それを分解する。



優先度に応じてストロークを変えてみます。優先度の高いものは太く、優先度の低いものは点線にします。アプリのキーになるパスがわかるようになります。



階層については何か言えるだろうか?

"Breadth"

アクターがいくつもの同じ優先度のユースケースに繋がっていて、それらがすべて合理的な開始ポイントであるなら、ある開始ポイントから他の開始ポイントへ簡単に遷移できるようにしたいです。

"Focus"

一方、ユーザーケースの中でよりフォーカスされたもの、他のより重要なものが1つだけある場合、他のユースケースは少しアクセスしにくくします。

"Statefulness"

ユーザーによって、優先度の高いユースケースが異なる場合があります。例として Android の Phone アプリがあります。起動した画面に Dialer, Call Log, Favorite の3つのステートがあります。これらは異なるタイプのユーザーの振る舞いと紐付いています。

"Depth"

完全に分離されたユースケースの深いつながりがある場合、特に重要です。



ユーザーが他のセクションに簡単に移動したい場合があるなら、アプリはそれを考慮しないといけません。

"Interconnectedness"



ハブのような複数のスレッドから集まるユースケースがある場合、それはとても重要な機能です。

ユースケースダイアグラムを90度回転し、それぞれのユースケースを画面に見立てます。アプリの階層を考えるうえでいいスタートポイントですが完璧ではありません。ユースケースの詳細度によって、1画面にとっては詳細すぎたり、大雑把すぎたりします。


階層のパターンについて

一般的なブループリントの例



トップレベル



アプリのエントリーであり、アプリに応じて1スクリーンだったり、マルチスクリーンだったりします。アプリの主なユースケースが何か、いくつあるのかに依存します。重要なことは、ユーザはここでカバーされている主なユースケースを見つけ、あるタスクを完了するための次のステップに導かれるということです。

トップレベルは、アプリの広い機能について示し、アプリのアイデンティティを構築します。 例を見るとわかるように、ひと目で "このアプリが何なのか" 理解することができます。



カテゴリーは階層や機能により深く入るために、トップレベルとボトムレベルをつなぐ部分です。 アルバムやフォルダーなど組織的な階層です。





詳細はタスクを完了したり、データを消費したりする部分です。あつかうデータタイプによって様々な画面になります。





構造を決定する上でトップレベルが一番重要です。

ここでの決定によって、ユーザーがここでカバーされている主なユースケースをうまく見つけられるか、次に進もうと思うようにユーザーを満足させられるかが変わってきます。

トップレベルはユーザーとのコミュニケーションであり、アプリの機能についてユーザーにエレベータピッチするところです。このアプリの目的のみならず、機能の広がり(他に何ができるのか)も紹介します。

Phone アプリでは、電話をかけることができるという主な機能の他に、Call Log と Favorites があることをタブで示しています。

このような情報は可能な限り早く開示し、ここでのコミュニケーションは簡潔にする必要があります。 トップレベルはユーザーとの対話であるということを心に留めておいてください。



ユーザーがトップレベルの機能を探せるようにすることは、全てのユースケースがカバーされていることを確認するうえで決定的です。
Android では、このようなアプリの異なる面に切り替えるためのいくつかの確立したパターンを提供しています。


Six-pack



階層の一番上の単一画面で、本質的にはメニュー画面です。ユーザーはメニュー画面を通して深いレベルに入っていきます。常にこの画面を介さないといけないので、複数の面を探したり、自分のユースケースのためにはどこに行けばいいかわからないときに一つづつ試すことができます。

利点
・直線的でシンプル
・全ての面のトップレベルが並んで見えている

欠点
・見てもあまり面白くない
・データがない
・ナビゲーションの努力がユーザーに必要

時間がたてばユーザーは構造を理解するので、タスクに入るまでのジャンプが余分になります。また、ある面から別の面に移動するにはここまで戻ってくる必要があるので、ユーザーがたくさんナビゲーションを行わなければなりません。


Fixed-tabs



Android では一般的なパターンです。横に並んだ3つのトップ画面があります。素早く効果的に別の面に移動できます。



トップレベルを簡単に素早く切り替えられるように、サイドスワイプでタブを切り替えるようにしてください。

利点
・全てのトップレベルが並んで表示される=他の選択肢が見つけやすい
・ものすごく効率的

欠点
・3つまでに制限される
・縦の領域を専有する
・階層の下のレベルからアクセスできない


Spinner



スピナーはより多くのアクションがある場合にタブの代わりに利用します。タブでは2つか3つまでですが、それ以上の選択肢を与えられます。ActionBar の中にスピナーが入るのでタブが専有する部分がなくなります。 一方で、スピナーが閉じている状態ではアプリの構造があまり明らかになっていません。

利点
・3つより多くのトップレベルを持てる
・コンパクト

欠点
・トップレベルが並んで表示されない
・階層の下のレベルからアクセスできない


Navigation Drawer

いくつかのアプリはすでにこれを実装しており、これまではコミュニティドリブンのパターンでしたが、Navigation Drawer を正式に Android Design Guide の一部にしました。

Navigation Drawer : Android Design

Creating a Navigation Drawer : Android Training



Navigation Drawer はアプリの最も重要な画面へのメニューパネルです。左上の Navigation Drawer アイコンをタップするか左端からスワイプすると、スライドしてきてコンテンツの上にオーバーレイされます。Navigation Drawer は "navigation hubs" を含み、ユーザーは頻繁にここにくるでしょう。ハブだけでなく下位階層へのナビゲーションも含めることができます。



下の階層の画面からでもトップレベルにアクセスできますし、別の面の下位の階層にアクセスすることもできます。 トップレベルでは ActionBar のアプリケーションアイコンの左に、ーが縦に3つ並んだ Navigation Drawer Icon が表示され、下位の階層では UP アイコンが表示されます。



Navigation Drawer のいいところは、コンテンツを作るためのスペースがあることです。アイコンを表示したり、ディバイダーを使ってセグメントを分けたり、アップデートなどのカウンターを表示したり、下位レベルのコンテンツをグルーピングしたりできます。

利点
・多くのビューを表示できる
・コンパクトで閉じることができる
・下位の画面への遷移も入れることができる
・どの画面からでもアクセスできる

欠点
・トップレベルが並んで表示されない



どのパターンを使うべきか - 実例

例1 - Calendar

現在カレンダーはスピナーを使っています。日、週、月、予定の関係をユーザーはしっかり理解しています。コンパクトで、上部にきちんと収まっていて、タイトルに合っています。



Navigation Drawer も可能です。表示するための特定のタイトルも提供できます。ただし、4つ以上のオプションが増えることはなさそうなので、タブレットなどのデバイスでは空間が目立つでしょう。



タブはよくない選択です。 タブは頻繁に切り替えるようなアプリではいいのですが、カレンダーではある人はよく1日のビューを使い、ある人はよく1週間のビューを使っているのではないでしょうか。さらに、タブによってカレンダーの領域が狭くなってしまいます。一番重要なことは、望ましくないジェスチャーを紹介しないといけないことです。ユーザーはスワイプによって日を切り替えられると期待しますが、タブを使うと日の切り替えとタブの切り替えが衝突してしまいます。



six-pack も同じように良くない選択です。毎回データを見るまでトップ画面を介さなければならないですし、カテゴリーを変更するために毎回トップに戻らなければなりません。




例2 - Clock

Clock は他の機能面とうまくコミュニケーションするためにタブを利用しています。ただの時計だけでなく、タイマーとストップウォッチがあることをほのめかしています。すぐに時計だとわかるのでアプリの名前などを表示する ActionBar をなくすかわりに、時計画面では split action bar を使っています。縦の領域は問題ではないので split action bar は適切な選択です。



スピナーを使ってもいいでしょう。ここでは時計画面にあったアラームを4番目のアイテムとして置くことができます。他の面への移動は少し操作が増えます。タイトル部分が増えるので、もとのデザインのシンプルさが少し損なわれます。



Navigation Drawer はスピナーと同じような感じです。



ナビゲーションとしては3つのパターンが使えますが、ビジュアル的なシンプルさではタブが一番いいでしょう。


例3 - Gallery

ギャラリーはスピナーを使っています。カレンダーとは少し異なる方法です。ここでは"同じデータをどのような方法で表示するか"を選択するために使っています。



Navigation Drawer でもいいでしょう。例えば下位階層として個別のアルバムへの遷移を入れることもできるでしょう。ただし、ここにはアルバムのサムネイルが表示されないため、有効な手段かどうかは疑問が残ります。



タブはだめです。タブの多すぎるし、カレンダーと同じスワイプジェスチャーの問題があります。




例4 - Drive

Drive ではアカウントの切り替えようにスピナーを使っていました。アプリ内でアカウント切り替えが頻繁にある場合、スピナーで行うことはいい選択です。ただし、Drive ではトップレベル切り替えの問題があり、コンテンツ領域のトップレベルにファイル階層を持ってくることでそれを解決していました。正確にはファイル階層ではなくスタティックリンクですが。本質的にはカスタマイズした six-pack です。



アプリを開いたときに直接データにアクセスするために、例えばスピナーをカスタマイズするという方法があります。Gmail では同じような方法をとっています。この方法は、スピナーが本来どうあるべきかを薄めてしまいます。スピナーは本来ある1つの項目を選択するためのものです。



Navigation Drawer は Drive にとって素晴らしい選択です。すぐにデータを表示でき、アカウントの選択もでき、トップレベルの切り替えもできます。




Shifting structure

アプリの進化よって直面する挑戦があります。Play Store にアプリを公開しているなら、アプリのライフスパンに応じて多くのデータや機能を追加するでしょう。その場合、常にアプリの構造が適しているか気に留めてください。

例えば、スポーツの結果を表示するシンプルなアプリがあるとします。公開したときに開発者が興味をもっている US で人気のある3つのスポーツに特化しています。





人々はこのアプリのシンプルさを気に入りますが、変更も頼んできます。一度アプリを公開するとフィードバックが来ます。例えば「ホッケーはないの?」とか。

そこで、ホッケーとサッカーを入れたり、Notification を追加したり、お気に入りのスポーツを登録できたりするようにしようとします。



最初に考えるのが、すでにあるトップナビゲーションに追加することです。なぜなら、ユーザーはすでにその方法に慣れ親しんでいるからです。しかし、この場合タブにこれ以上アイテムを追加するのはいい方法ではありません。

Navigation Drawer を使うと、多くのスポーツへのトップレベルを提供でき、設定画面への Overflow メニューも用意できます。




まとめ
  • 適切な構造を選択することはアプリの体験にとって非常に重要
  • "このアプリは何なのか"を理解し、異なるユーザーにどのように提供するのか
  • ナビゲーションパターンを選択するときは全てのオプションを考える



Android : Navigation Drawer を使う

$
0
0
画面の左側にオーバーレイでアプリの主なオプションを表示するパネル。
通常は隠れていて、画面の左端からスワイプするか、トップレベルにいるならアクションバーのアイコンをタップすることで表示される。


http://developer.android.com/design/patterns/navigation-drawer.htmlより

Navigation Drawer を使う前に、Navigation Drawer デザインガイドにあるこのパターンのユースケースとデザイン原則をきちんと理解すること。


Drawer Layout を作成する

support package にある DrawerLayoutを利用する。

DrawerLayout をルートビューとし、その中にメインのコンンテンツを表示するビューと、NavigationDrawer として利用するビューを入れる。
<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/drawer_layout" android:layout_width="match_parent" android:layout_height="match_parent"> <!-- The main content view --> <FrameLayout android:id="@+id/content_frame" android:layout_width="match_parent"android:layout_height="match_parent" /> <!-- The navigation drawer --> <ListView android:id="@+id/left_drawer" android:layout_width="240dp" android:layout_height="match_parent" android:layout_gravity="start" android:choiceMode="singleChoice" android:divider="@android:color/transparent" android:dividerHeight="0dp" android:background="#111"/> </android.support.v4.widget.DrawerLayout>
DrawerLayout を利用するうえでいくつか注意点がある
  • メインのコンテンツ用のビュー(上の例だとFrameLayout)は、DrawerLayout の最初のビューでなければならない
  • メインのコンテンツ用のビュー の layout_width と layout_height は match_parent にする
  • NavigationDrawer 用のビュー(上の例だとListView)は、layout_gravity で horizontal gravity を指定しなければならない
    RTL言語をサポートするなら left ではなく start を使う
  • NavigationDrawer 用のビューは dp 単位で幅を指定し、縦は patch_parent にする
    横幅は 320dp 以上にはしない



Drawer List を初期化する

Activity で最初に行うことは、Navigation Drawer のアイテムの初期化。
例えば ListView を利用するなら Adapter をセットする。

public class MainActivity extends Activity { private String[] mPlanetTitles; private ListView mDrawerList; private DrawerLayout mDrawerLayout; ... @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); mPlanetTitles = getResources().getStringArray(R.array.planets_array); mDrawerList = (ListView) findViewById(R.id.left_drawer); // Set the adapter for the list view mDrawerList.setAdapter(new ArrayAdapter(this, R.layout.drawer_list_item, mPlanetTitles)); // Set the list's click listener mDrawerList.setOnItemClickListener(new DrawerItemClickListener()); ... } }


Navigation Drawer アイテムのクリックを処理する

Navigation Drawer アイテムがクリックされたときに何をするかはアプリの構造によるが、ここではメインコンテンツ用のビューにセットする Fragment を切り替えている。

private class DrawerItemClickListener implements ListView.OnItemClickListener { @Override public void onItemClick(AdapterView parent, View view, int position, long id) { selectItem(position); } } /** Swaps fragments in the main content view */ private void selectItem(int position) { // Create a new fragment and specify the planet to show based on position Fragment fragment = new PlanetFragment(); Bundle args = new Bundle(); args.putInt(PlanetFragment.ARG_PLANET_NUMBER, position); fragment.setArguments(args); // Insert the fragment by replacing any existing fragment FragmentManager fragmentManager = getFragmentManager(); fragmentManager.beginTransaction() .replace(R.id.content_frame, fragment) .commit(); // Highlight the selected item, update the title, and close the drawer mDrawerList.setItemChecked(position, true); setTitle(mPlanetTitles[position]); mDrawerLayout.closeDrawer(mDrawerList); } @Override public void setTitle(CharSequence title) { mTitle = title; getActionBar().setTitle(mTitle); }


オープン・クローズイベント時に処理を行う

Drawer が開いたり閉じたりしたのを検知するには、DrawerLayout の setDrawerListener()を使って DrawerLayout.DrawerListenerをセットする。
Drawer の開閉時にはそれぞれリスナーの onDrawerOpened() と onDrawerClosed() が呼ばれる。

アプリに ActionBar があるなら、DrawerLayout.DrawerListener よりもそれを implements した ActionBarDrawerToggleが便利。

Navigation Drawer デザインガイドにあるように、Drawer が開いている状態では、タイトルを変えたり、メインコンテンツに関係する Action Item を削除したりすべき。

以下では、ActionBarDrawerToggle を使って、タイトルと Action Item を編集している。
また、利用している R.drawable.ic_drawer 画像は Android_Navigation_Drawer_Icon_20130516.zipからダウンロードできる

public class MainActivity extends Activity { private DrawerLayout mDrawerLayout; private ActionBarDrawerToggle mDrawerToggle; private CharSequence mDrawerTitle; private CharSequence mTitle; ... @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ... mTitle = mDrawerTitle = getTitle(); mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout, R.drawable.ic_drawer, R.string.drawer_open, R.string.drawer_close) { /** Called when a drawer has settled in a completely closed state. */ public void onDrawerClosed(View view) { setTitle(mTitle); invalidateOptionsMenu(); // creates call to onPrepareOptionsMenu() } /** Called when a drawer has settled in a completely open state. */ public void onDrawerOpened(View drawerView) { setTitle(mDrawerTitle); invalidateOptionsMenu(); // creates call to onPrepareOptionsMenu() } }; // Set the drawer toggle as the DrawerListener mDrawerLayout.setDrawerListener(mDrawerToggle); } /* Called whenever we call invalidateOptionsMenu() */ @Override public boolean onPrepareOptionsMenu(Menu menu) { // If the nav drawer is open, hide action items related to the content view boolean drawerOpen = mDrawerLayout.isDrawerOpen(mDrawerList); menu.findItem(R.id.action_websearch).setVisible(!drawerOpen); return super.onPrepareOptionsMenu(menu); } }


アプリアイコンでオープン・クローズする

ActionBar のアプリアイコンをタッチして Navigation Drawer を開閉させるには ActionBarDrawerToggle を使えば簡単にできる。
ActionBarDrawerToggle のコンストラクタでは以下の5つが必要。
  • Drawer を持っている Activity
  • DrawerLayout
  • Drawer アイコン(UPアイコンの代わりに表示される)の drawable リソースID
  • Drawer を開くというアクションの説明(アクセシビリティ用)の string リソースID
  • Drawer を閉じるというアクションの説明(アクセシビリティ用)の string リソースID
public class MainActivity extends Activity { private DrawerLayout mDrawerLayout; private ActionBarDrawerToggle mDrawerToggle; ... public void onCreate(Bundle savedInstanceState) { ... mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout); mDrawerToggle = new ActionBarDrawerToggle( this, /* host Activity */ mDrawerLayout, /* DrawerLayout object */ R.drawable.ic_drawer, /* nav drawer icon to replace 'Up' caret */ R.string.drawer_open, /* "open drawer" description */ R.string.drawer_close /* "close drawer" description */ ) { /** Called when a drawer has settled in a completely closed state. */ public void onDrawerClosed(View view) { setTitle(mTitle); } /** Called when a drawer has settled in a completely open state. */ public void onDrawerOpened(View drawerView) { setTitle(mDrawerTitle); } }; // Set the drawer toggle as the DrawerListener mDrawerLayout.setDrawerListener(mDrawerToggle); getActionBar().setDisplayHomeAsUpEnabled(true); getActionBar().setHomeButtonEnabled(true); } @Override protected void onPostCreate(Bundle savedInstanceState) { super.onPostCreate(savedInstanceState); // Sync the toggle state after onRestoreInstanceState has occurred. mDrawerToggle.syncState(); } @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); mDrawerToggle.onConfigurationChanged(newConfig); } @Override public boolean onOptionsItemSelected(MenuItem item) { // Pass the event to ActionBarDrawerToggle, if it returns // true, then it has handled the app icon touch event if (mDrawerToggle.onOptionsItemSelected(item)) { return true; } // Handle your other action bar items... return super.onOptionsItemSelected(item); } ... }


Action Bar Sherlock で使う
  • extras/android/support/v4/src/java/android/support/v4/app/ActionBarDrawerToggle.java
  • extras/android/support/v4/src/honeycomb/android/support/v4/app/ActionBarDrawerToggleHoneycomb.java
をコピーして、

ActionBarDrawerToggle.java の変更
  • IMPL を常に ActionBarDrawerToggleImplHC を使うように変更
  • ActionBarDrawerToggleImpl の setActionBarUpIndicator() と setActionBarUpIndicator() の Activity を SherlockActivity や SherlockFragmentActivity に変更
  • ActionBarDrawerToggleImplHC の setActionBarUpIndicator() と setActionBarUpIndicator() も同様
  • ActionBarDrawerToggle のコンストラクタの Activity も同様
  • MenuItem のインポートを Sherlock のものに変更
  • public boolean onOptionsItemSelected(MenuItem item) { if (item != null && item.getItemId() == ID_HOME && mDrawerIndicatorEnabled) { if (mDrawerLayout.isDrawerVisible(GravityCompat.START)) { mDrawerLayout.closeDrawer(GravityCompat.START); } else { mDrawerLayout.openDrawer(GravityCompat.START); } } return false; } public boolean onOptionsItemSelected(MenuItem item) { if (item != null && item.getItemId() == ID_HOME && mDrawerIndicatorEnabled) { if (mDrawerLayout.isDrawerVisible(GravityCompat.START)) { mDrawerLayout.closeDrawer(GravityCompat.START); } else { mDrawerLayout.openDrawer(GravityCompat.START); } return true; } return false; } にする(最初の if に入ったら true を返す)

    # サンプルで @Override public boolean onOptionsItemSelected(MenuItem item) { // Pass the event to ActionBarDrawerToggle, if it returns // true, then it has handled the app icon touch event if (mDrawerToggle.onOptionsItemSelected(item)) { return true; } // Handle your other action bar items... return super.onOptionsItemSelected(item); } # としているのに1つ目の if は常に false とかひどいバグでござる。。。


ActionBarDrawerToggleHoneycomb.java の変更
  • setActionBarUpIndicator() と setActionBarUpIndicator() の Activity を SherlockActivity や SherlockFragmentActivity に変更
  • activity.getActionBar() を activity.getSupportActionBar() に変更





In-app billing V3 は singleInstance な Activity では使えない

$
0
0
「Android launchMode の違い」で解説したように、Activity には4つの launchMode が設定できます。
  • standard
  • singleTop
  • singleTask
  • singleInstance
それぞれの解説は上のエントリーに任せるとして、このなかの singleInstance を指定した Activity で In-app billing V3 の購入処理を行うとうまくいきません。

singleInstance はアプリでタスク1個、タスク内の Activity も1個というストイックな設定です。

In-app billing V3 の課金処理では、Activity の startIntentSenderForResult (IntentSender intent, int requestCode, Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags)を使って購入フローの Activity を起動し、結果を onActivityResult (int requestCode, int resultCode, Intent data)で受けとります。 Result を受けとるため、起動した Activity は同じタスクに入る必要があります。

singleInstance だとタスク内の Activity は1つだけなので、購入フローの Activity は別のタスクとして起動されます。別タスクだと結果が受けとれないので、購入フローの Activity を呼び出した直後に responseCode = RESULT_CANCELED, data = null で onActivityResult() が呼ばれます。

購入フローでは、購入処理が終わったあとに onActivityResult() が呼ばれ、data に購入処理の結果が入ります。このため、singleInstance だと購入情報が受けとれないということになってしまうのです。

Logcat でも下のように購入フロー Activity の呼び出し直後に「Activity is launching as a new task, so cancelling activity result.」と言われています。


06-04 11:59:57.619: I/ActivityManager(533): START u0 {cmp=com.android.vending/com.google.android.finsky.billing.lightpurchase.IabV3Activity (has extras)} from pid -1
06-04 11:59:57.619: W/ActivityManager(533): Activity is launching as a new task, so cancelling activity result.


購入フロー以外の処理、例えば、アイテムの詳細を取得(getSkuDetails())や購入済みアイテムの一覧を取得(getPurchases())やアイテムの消費(consumePurchase ())は普通にできます。



まぁ、singleInstance にするアプリなんてそうそうないと思うけど。



Android で mockito を使う : 準備編

$
0
0
mockitoはテストのためのモックフレームワークです。



1. mockito の準備

mockito のプロジェクトページ(https://code.google.com/p/mockito/)に行って、最新の(ここでは mockito-1.9.5.zip)をダウンロードします。





ダウンロードした zip を展開して、中に jar ファイル(ここでは mockito-all-1.9.5.jar)が入っていることを確認します。



2. dexmaker の準備

Android で mockito を使うには dexmakerも必要です。
dexmaker のプロジェクトページ(http://code.google.com/p/dexmaker/)に行って、dexmaker-1.0.jar と dexmaker-mockito-1.0.jar をダウンロードします。





3. テストプロジェクトの準備

MockitoSample というプロジェクトがあるとします。



このプロジェクト用のテストプロジェクトを作成するには

[File] → [New] → [Other...] → [Android] → [Android Test Project]

を選択し、



テストプロジェクトの名前を入力し(テスト対象のプロジェクト名 + Test とすることが多い)、



Test Target にテスト対象のプロジェクトを選択します。



テスト用のプロジェクトが作成されました。



テストプロジェクトのトップに libs フォルダを作成し、ダウンロードした mockito-all-1.9.5.jar, dexmaker-1.0.jara, dexmaker-mockito-1.0.jar を入れます。





4. static import の設定

設定の [Java] → [Editor] → [Content Assist] → [Favorites] に

org.mockito.Matchers.*
org.mockito.Mockito.*
を追加(入ってなかったら)



設定の [Java] → [Code Style] → [Organize Imports] の
Number of static imports needed for .* の数字を 1 に変更






Android で mockito を使う : メソッドの呼び出しをチェックする

$
0
0
JSON をパースして、リスナーの対応するメソッドを呼び出すユーティリティメソッドがあるとします。

public class Utils { public interface ResultListener { void onError(); void onHoge1(); void onHoge2(); } public static void handleJson(String json, ResultListener listener) { if (listener == null) { return; } try { JSONObject obj = new JSONObject(json); String status = obj.optString("Status"); if (status.equals("hoge1")) { listener.onHoge1(); } else if (status.equals("hoge2")) { listener.onHoge2(); } } catch (JSONException e) { listener.onError(); e.printStackTrace(); } } }

このメソッドをテストするには、JSON文字列を渡して、対応するリスナーのメソッドがちゃんと呼ばれるかどうか確認すればいいわけです。

まず、テストプロジェクトにテストしたいクラスと同じパッケージを作ります。

ここではテスト対象クラス(Utils)が com.example.mockitosample なので、テストプロジェクトの MockitoSampleTest にも com.example.mockitosample を作ります。



テストプロジェクトの com.example.mockitosample を選択して右クリックし、[New] → [JUnit Test Case] を選択します。
Name にテストクラス名を入れて Finish をクリックします。
テスト対象クラス名 + Test にすることが多いです。





このユーティリティメソッドの動作パターンとして以下があります。
  • 1. 引数で渡す文字列が JSON 文字列として正しくない場合、onError() が呼ばれ他のメソッドは呼ばれない
  • 2. (引数で渡す)JSON 文字列に Status キーが無い場合、onError() も他のメソッドも呼ばれない
  • 3. JSON 文字列の Status キーの値が hoge1 でも hoge2 でもない場合、onError() も他のメソッドも呼ばれない
  • 4. JSON 文字列の Status キーの値が hoge1 の場合、onHoge1() が呼ばれ他のメソッドは呼ばれない
  • 5. JSON 文字列の Status キーの値が hoge2 の場合、onHoge2() が呼ばれ他のメソッドは呼ばれない
ということでテストメソッドは5つです。
JUnit3 では test で始まるメソッドがテストメソッドとして認識されます。



まず、「1. 引数で渡す文字列が JSON 文字列として正しくない場合、onError() が呼ばれ他のメソッドは呼ばれない」 のテストメソッドを作ってみます。 /** * 引数で渡す文字列が JSON 文字列として正しくない場合、 onError() が呼ばれ他のメソッドは呼ばれないことを確認する */ public void testInvalidJson() { }

mockito を使わない場合「グローバル変数を用意してリスナーのメソッドで変数の値を変えて、どれが呼ばれたかチェックする」みたいなことをしますが、mockito を使うともっとスマートにできます。

まず、メソッドが呼ばれたかチェックしたいクラスをモック化します。
ここでは ResultListener のメソッドが呼ばれたかチェックしたいので ResultListener をモック化します。

モック化には Mockito.mock() を使います。
他にも Mockito 下の static method をたくさん使うので

import static org.mockito.Mockito.*;

を入れておくといいでしょう。

ResultListener mockResultListener = mock(ResultListener.class); あとはこのモックを使ってテストしたいメソッドを呼び出します。 ResultListener mockResultListener = mock(ResultListener.class); Utils.handleJson("", mockResultListener);

ここでは JSON 文字列として不適切な空文字を渡しているので onError() が呼ばれるはずです。
それをチェックするのが verify() メソッドです。 ResultListener mockResultListener = mock(ResultListener.class); Utils.handleJson("", mockResultListener); verify(mockResultListener).onError();

onHoge1() と onHoge2() は呼ばれないはずです。呼ばれなかったかどうかをチェックするには、verify() メソッドの第2引数に never() を指定します。 ResultListener mockResultListener = mock(ResultListener.class); Utils.handleJson("", mockResultListener); verify(mockResultListener).onError(); verify(mockResultListener, never()).onHoge1(); verify(mockResultListener, never()).onHoge2();

これだと例えば onHoge3() メソッドが増えたときに修正が必要になります。
verify() メソッドの第2引数に only() を付けることで onError() しか呼ばれていないということをチェックできます。 ResultListener mockResultListener = mock(ResultListener.class); Utils.handleJson("", mockResultListener); verify(mockResultListener, only()).onError();

テストメソッドの全体はこうなります。 /** * 引数で渡す文字列が JSON 文字列として正しくない場合、 onError() が呼ばれ他のメソッドは呼ばれないことを確認する */ public void testInvalidJson() { ResultListener mockResultListener = mock(ResultListener.class); Utils.handleJson("", mockResultListener); verify(mockResultListener, only()).onError(); }



「2. (引数で渡す)JSON 文字列に Status キーが無い場合、onError() も他のメソッドも呼ばれない」のテストを作ってみましょう。

これ以降になにも行われないことをチェックするメソッドとして verifyNoMoreInteractions() があります。
ここでは全てのメソッドが呼ばれないのでこれを使うことが出来ます。 /** * Status キーが無い場合、どのメソッドも呼ばれないことを確認する */ public void testNoStatusKey() { ResultListener mockResultListener = mock(ResultListener.class); Utils.handleJson("{\"State\":\"hoge1\"}", mockResultListener); verifyNoMoreInteractions(mockResultListener); }



「3. JSON 文字列の Status キーの値が hoge1 でも hoge2 でもない場合、onError() も他のメソッドも呼ばれない」のテストを作ってみましょう。

引数で渡す文字列が変わるだけで 2. と一緒ですね /** * Status キーの値が hoge1 でも hoge2 でも無い場合、どのメソッドも呼ばれないことを確認する */ public void testInvalidStatusValue() { ResultListener mockResultListener = mock(ResultListener.class); Utils.handleJson("{\"Status\":\"hoge3\"}", mockResultListener); verifyNoMoreInteractions(mockResultListener); }



「4. JSON 文字列の Status キーの値が hoge1 の場合、onHoge1() が呼ばれ他のメソッドは呼ばれない」のテストを作ってみましょう。 /** * Status キーの値が hoge1 の場合、onHoge1() が呼ばれ他のメソッドは呼ばれないことを確認する */ public void testHoge1() { ResultListener mockResultListener = mock(ResultListener.class); Utils.handleJson("{\"Status\":\"hoge1\"}", mockResultListener); verify(mockResultListener, only()).onHoge1(); }

「5. JSON 文字列の Status キーの値が hoge2 の場合、onHoge2() が呼ばれ他のメソッドは呼ばれない」のテストは 4. とほぼ同じなので省略します。



ResultListener をモック化する処理 ResultListener mockResultListener = mock(ResultListener.class); は各メソッドで行っているので、mockResultListener をフィールドとして保持するようにして setup() メソッドでモック化させると、このテストクラスがどのモックに依存しているかわかりやすくなります。 public class UtilsTest extends TestCase { ResultListener mockResultListener; @Override protected void setUp() throws Exception { super.setUp(); mockResultListener = mock(ResultListener.class); } /** * 引数で渡す文字列が JSON 文字列として正しくない場合、 onError() が呼ばれ他のメソッドは呼ばれないことを確認する */ public void testInvalidJson() { Utils.handleJson("", mockResultListener); verify(mockResultListener, only()).onError(); } ... }

5つともグリーンになりました。







verify() の第2引数に times(int) を渡すことで何回呼ばれたかをチェックすることができます。
第2引数を指定しない場合は times(1) を指定したのと同じことになります。

verify(mockResultListener, times(2).onHoge3();



Android で mockito を使う : メソッド呼び出し時の引数をチェックする

$
0
0
JSON をパースして、リスナーの対応するメソッドを呼び出すユーティリティメソッドがあるとします。
"Status" というキーの値(文字列)とそのときの時間(long)を handleStatus() の引数として渡すようになっています。 public class Utils { public interface ResultListener { void onError(); void handleStatus(String status, long time); } public static void handleJson(String json, ResultListener listener) { if (listener == null) { return; } try { JSONObject obj = new JSONObject(json); String status = obj.optString("Status"); listener.handleStatus(status, System.currentTimeMillis()); } catch (JSONException e) { listener.onError(); e.printStackTrace(); } } } まずメソッドが呼ばれるかどうかをテストしましょう。
前回紹介したように Mockito.verify() メソッドを使います。 /** * Status キーがある場合、handleJson() が呼ばれることを確認する */ public void testStatusKey() { Utils.handleJson("{\"Status\":\"hoge1\"}", mockResultListener); verify(mockResultListener, only()).handleStatus(anyString(), anyLong()); } handleStatus() は引数として String と long をとるので verify(mockResultListener, only()).handleStatus(); のように指定することはできません。
そこで、「引数の文字列がどんな値であっても気にしない、メソッドが呼び出されているかどうかだけ確かめたい」という場合、anyString() や anyLong() を指定することができます。
String, long 以外にも anyInt(), anyBoolean() などが用意されています。

引数の値をチェックするために、引数に直接文字列を指定することができます。

Status キーの値は正しい値を指定することができますが、long の引数は handleStatus() 内で System.currentTimeMillis() しているため、次のテストは失敗します。

失敗するテスト /** * Status キーがある場合 handleJson() が呼ばれ、引数が Status キーの値であることを確認する */ public void testStatusKey() { Utils.handleJson("{\"Status\":\"hoge1\"}", mockResultListener); verify(mockResultListener, only()).handleStatus("hoge1", System.currentTimeMillis()); } そこで long は値を検証しないことにして、anyLong() を使って次のように書くとまたまたテストに失敗します。

失敗するテスト /** * Status キーがある場合 handleJson() が呼ばれ、引数が Status キーの値であることを確認する */ public void testStatusKey() { Utils.handleJson("{\"Status\":\"hoge1\"}", mockResultListener); verify(mockResultListener, only()).handleStatus("hoge1", anyLong()); } 実は any〇〇() と実際の値を並記することはできません。any〇〇() を使う場合、実際の値の部分には eq() を使います。 /** * Status キーがある場合 handleJson() が呼ばれ、引数が Status キーの値であることを確認する */ public void testStatusKey() { Utils.handleJson("{\"Status\":\"hoge1\"}", mockResultListener); verify(mockResultListener, only()).handleStatus(eq("hoge1"), anyLong()); }


Status キーの値は optString() で取得しているため、Status キーが無い場合は空文字になります。

失敗するテスト /** * Status キーが無い場合、handleJson() が呼ばれ引数が空文字であることを確認する */ public void testNoStatusKey() { Utils.handleJson("{\"State\":\"hoge1\"}", mockResultListener); verify(mockResultListener, only()).handleStatus(eq(""), anyLong()); }



引数をログに出力したいということがあるでしょう。そのためには verify 時に引数をキャッチしておく必要があります。
これを行ってくれるのが ArgumentCaptor です。

ここでは String と long の引数をキャッチしたいので ArgumentCaptor<String> statusCaptor = ArgumentCaptor.forClass(String.class); ArgumentCaptor<Long> timeCaptor = ArgumentCaptor.forClass(Long.class); のようにして ArgumentCaptor のインスタンスを作ります。

あとはこの ArgumentCaptor の capture() を引数として渡します。 /** * Status キーがある場合 handleJson() が呼ばれ、引数が Status キーの値であることを確認する */ public void testStatusKey() { Utils.handleJson("{\"Status\":\"hoge1\"}", mockResultListener); ArgumentCaptor<String> statusCaptor = ArgumentCaptor .forClass(String.class); ArgumentCaptor<Long> timeCaptor = ArgumentCaptor.forClass(Long.class); verify(mockResultListener, only()).handleStatus(statusCaptor.capture(), timeCaptor.capture()); String status = statusCaptor.getValue(); long time = timeCaptor.getValue(); assertEquals("hoge1", status); System.out.println("Status = " + status + ", time = " + time); } verify() 後は ArgumentCaptor の getValue() で値を取ることが出来ます。


ArgumentCaptor.capture() と any〇〇() は並記することができます verify(mockResultListener, only()).handleStatus(anyString(), timeCaptor.capture()); 一方、直接引数を渡す場合とは並記できません。

失敗するテスト verify(mockResultListener, only()).handleStatus("hoge1", timeCaptor.capture()); eq() を使えば大丈夫です。 verify(mockResultListener, only()).handleStatus(eq("hoge1"), timeCaptor.capture());




mockito を使ったテストでの java.lang.NoClassDefFoundError: org.mockito.Mockito を修正する

$
0
0
mockito を使ったテストプロジェクトでは、bin/dexedLibs フォルダに mockito や dexmacker の jar ができている必要があります。

「Android で mockito を使う : 準備編」の手順では、libs フォルダに mockito や dexmaker の jar をコピーすると、次のテスト実行に bin/dexedLibs フォルダに mockito や dexmacker の jar ができます。

git リポジトリに Android プロジェクトを入れる場合、bin フォルダは入れないようにするでしょうから、git clone しただけだと次のテスト実行に bin/dexedLibs フォルダに mockito や dexmacker の jar ができず、テスト時に java.lang.NoClassDefFoundError: org.mockito.Mockito エラーが発生します。

この現象は mockito だけでなく、libs に jar を入れているプロジェクトなら起こりえます。

これを修正するには
  • テストプロジェクトの Properties で [Java Build Path] → [Order and Export] で Android Private Libraries のチェックボックスをチェック
  • テストプロジェクトを clean
(mockito と dexmacker の jar は libs フォルダに入ったときに Android Private Libraries に含まれるようになります)

をします。

こうすると次のテスト実行に bin/dexedLibs フォルダに mockito や dexmacker の jar ができ、java.lang.NoClassDefFoundError: org.mockito.Mockito エラーが起こらなくなります。



Android で mockito を使う : モックのメソッドが呼ばれたときの戻り値を指定する

$
0
0
public class MyClassA { MyClassB mMyClassB; public MyClassA(MyClassB b) { mMyClassB = b; } public void hoge() { if (mMyClassB.getPriority() > 0) { handleHighPriority(); } else { handleLowPriority(); } } public void handleHighPriority() { ... } public void handleLowPriority() { ... } } public class MyClassB { private int mPriority; public int getPriority() { return mPriority; } // 内部の処理で mPriority の値が決まる ... } MyClassA の hoge() が呼ばれたとき、MyClassB の priority に応じて対応するメソッドが呼ばれるかどうかテストします。

失敗するテスト public void testWhenHighPriority() { MyClassA mockMyClassA = mock(MyClassA.class); mockMyClassA.hoge(); verify(mockMyClassA, times(1)).handleHighPriority(); verify(mockMyClassA, never()).handleLowPriority(); } と書くと hoge() を呼んだ時点で NullPointerException が発生して handleHighPriority() が呼ばれずテストに失敗します。
mock() で作成した mockMyClassA では mClassB が null になっているからです。

そこで MyClassB をモックにし、それを MyClassA のコンストラクタに与えるようにします。

失敗するテスト public void testWhenHighPriority() { MyClassB mockMyClassB = mock(MyClassB.class); MyClassA myClassA = new MyClassA(mockMyClassB); myClassA.hoge(); verify(myClassA, times(1)).handleHighPriority(); verify(myClassA, never()).handleLowPriority(); } これで hoge() を呼んだ時点で NullPointerException が発生することは無くなりましたが、 org.mockito.exceptions.misusing.NotAMockException: Argument passed to verify() is of type MyClassA and is not a mock! が起こってテストに失敗します。 verify() に渡すインスタンスはモック化されていないといけないからです。 実際のインスタンスをモック化するには Mockito.spy() を使います。

失敗するテスト public void testWhenHighPriority() { MyClassB mockMyClassB = mock(MyClassB.class); MyClassA mockMyClassA = spy(new MyClassA(mockMyClassB)); mockMyClassA.hoge(); verify(mockMyClassA, times(1)).handleHighPriority(); verify(mockMyClassA, never()).handleLowPriority(); } これで NotAMockException が発生することは無くなりましたが、MyClassB の mPriority は初期値 = 0 のままなので、priority が 0 より大きいときのテストができません。

そこで mockito の機能を使います。
mockito では、「モックのあるメソッドが呼ばれたときにこの値を返す」という指定ができます。

"あるメソッドが呼ばれた" ということを指定するのが Mockito.when() メソッドです。
"そのときにこの値を返す" ということを指定するのが thenReturn() メソッドです。

MyClassB の getPriority() が呼ばれたときに 100 を返して欲しいなら when(mockMyClassB.getPriority()).thenReturn(100); のように書きます。 /** * MyClassA.hoge() を呼んだとき、MyClassB の priority が 100 なら * MyClassA.handleHighPriority() が呼ばれることを確認する */ public void testWhenHighPriority() { MyClassB mockMyClassB = mock(MyClassB.class); when(mockMyClassB.getPriority()).thenReturn(100); MyClassA myClassA = new MyClassA(mockMyClassB); myClassA.hoge(); verify(mockMyClassA, times(1)).handleHighPriority(); verify(mockMyClassA, never()).handleLowPriority(); } /** * MyClassA.hoge() を呼んだとき、MyClassB の priority が 0 なら * MyClassA.handleLowPriority() が呼ばれることを確認する */ public void testWhenLowPriority() { MyClassB mockMyClassB = mock(MyClassB.class); when(mockMyClassB.getPriority()).thenReturn(0); MyClassA myClassA = new MyClassA(mockMyClassB); myClassA.hoge(); verify(mockMyClassA, times(1)).handleLowPriority(); verify(mockMyClassA, never()).handleHighPriority(); } thenReturn(Integer value, Integer... values) のように第2引数が可変長引数になっているものも用意されています。
thenReturn(10, 20, 30, 100) のように指定すると、対応するメソッドが呼ばれるごとに返される値が後の引数になり、引数よりも呼ばれる回数が多くなったら一番最後の引数の値が返されます。



Volley を使って XML を処理する

$
0
0
Volley には JsonRequest とか JsonObjectRequest とか用意されているのですが、XML 用(?)のはありません。
残念ながら XML が返ってくる API を利用せねばならない場合もあります。JSON がいいよー。。。

Entity から InputStream を取得して、XmlPullParser を使って parse している処理があったとします。
これの通信部分を Volley を使うようにするには、Request<InputStream> を継承したクラスを作ればいいわけです。 public class InputStreamRequest extends Request<InputStream> { private final Listener mListener; /** * * @param method * @param url * @param listener * @param errorListener */ public InputStreamRequest(int method, String url, Listener<InputStream> listener, ErrorListener errorListener) { super(method, url, errorListener); mListener = listener; } /** * * @param url * @param listener * @param errorListener */ public InputStreamRequest(String url, Listener<InputStream> listener, ErrorListener errorListener) { this(Method.GET, url, listener, errorListener); } @Override protected void deliverResponse(InputStream response) { mListener.onResponse(response); } @Override protected Response<InputStream> parseNetworkResponse(NetworkResponse response) { InputStream is = new ByteArrayInputStream(response.data); return Response.success(is, HttpHeaderParser.parseCacheHeaders(response)); } } public void doRequest(String url) { InputStreamRequest request = new InputStreamRequest(url, new Listener<InputStream>() { @Override public void onResponse(InputStream in) { MyData data = parseXml(in); try { in.close(); } catch (IOException e) { e.printStackTrace(); } if (mListener != null) { mListener.onParseXml(data); } } }, new ErrorListener() { @Override public void onErrorResponse(VolleyError error) { // error } }); mQueue.add(request); }

Volley の使い方は adamrocker のブログがわかりやすいです 「throw Life : Volley(AndroidのHTTP通信ライブラリ)を使おう」



ViewPager の Fragment を画面回転後も保持する方法

$
0
0
FragmentPagerAdapter を使って各画面に Fragment を使う場合、ある画面の Fragment インスタンスを保持しておいてそれに対して処理を行うということがあります。 public class MainActivity extends SherlockFragmentActivity implements LibraryListener, DrawerListener { private MainFragment mMainFragment; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ViewPager viewPager = (ViewPager) findViewById(R.id.pager); viewPager.setAdapter(new MainPagerAdapter(getSupportFragmentManager())); } public class MainPagerAdapter extends FragmentPagerAdapter { public MainPagerAdapter(FragmentManager fm) { super(fm); } @Override public Fragment getItem(int position) { switch (position) { case 0: return (mMainFragment = new MainFragment()); case 1: return new SubFragment(); } return null; } @Override public int getCount() { return 2; } } private void hoge() { mMainFragment.fuga(); } } このような場合、画面回転後に hoge() を呼ぶと mMainFragment が null なので NPE になります。
なぜ mMainFragment が null になるかというと、FragmentPagerAdapter が一度 getItem() で生成した Fragment を再利用してくれるので、画面回転時に getItem() が呼ばれないからです。

画面回転後も mMainFragment に以前のインスタンスを保持させるには、次のように onSaveInstanceState() と onRestoreInstanceState() を使います。 @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); if (mMainFragment != null) { getSupportFragmentManager().putFragment(outState, "main_fragment", mMainFragment); } } @Override protected void onRestoreInstanceState(Bundle savedInstanceState) { super.onRestoreInstanceState(savedInstanceState); if (mMainFragment == null) { mMainFragment = (MainFragment) getSupportFragmentManager().getFragment(savedInstanceState, "main_fragment"); } } 実際に Fragment のインスタンスがこの処理で永続化されるわけではなく、画面回転後に以前のものと対応する Fragment を見つけてくるという処理です。


enchant MOON データ構造メモ

$
0
0
ただのメモです。
  • App/
    • MOONBlock
      • lib/
        • enchant.js
        • MOON.js
        • ...
      • js/
      • ...
  • Data/
    • MyNotebook1/
      • info.json
        ページID が配列になっている JSON { "pages":[ "wtV727cH1356966334559", "PLbZcQDz1356966337101", ... "m5cv1ak51375741607892" ] }
      • [ページID(ランダムな文字列)]/
        • info.json
          backing にストローク情報ID、stickers にシールID の配列がはいった JSON {"backing":"ストローク情報ID" ,"stickers":["シールID"]}
        • storage.json
          ローカルストレージの実体ぽい {"fav":0}
        • [ストローク情報ID(ランダムな文字列)]/
          • info.json
            ストローク情報の JSON

            "data" は x, y, 筆圧, x, y, 筆圧, ... のようにデータが入ってる { "version":"0.2", "x":0, "y":0, "width":768, "height":1024, "scale":1.0, "color":-16777216, "transparent":false, "strokes":[ { "width":2.5, "color":-1, "type":"pen", "data":[221.15625,273.7857360839844,0.09349840134382248,221.7083282470703,275.952392578125,0.0446123443543911] }, ... { "width":5.0, "color":-1, "type":"pen", "data":[223.125,285.3571472167969,0.002590776886790991,223.125,287.5535888671875,0.01993499882519245,223.21875,291.21429443359375,0.09266002476215363] } ] } 白背景(invert)にすると、color 部分が変わる { "version":"0.2", "x":0, "y":0, "width":768, "height":1024, "scale":1.0, "color":-1, "transparent":false, "strokes":[ { "width":2.5, "color":-16777216, "type":"pen", "data":[90.0,270.0714416503906,0.3157637417316437,90.75] } ] }
        • [シール(スティッカー)ID(ランダムな文字列)]/
          • info.json
            シールのストローク情報

            例)Evernote シールの info.json
            background.png を "clip" のストロークで切り取ることができるようだ { "version":"0.2", "x":509, "y":898, "width":258, "height":89, "scale":1.0, "color":0, "transparent":true, "strokes":[], "clip":{ "width":0.0, "color":-16777216, "type":"pen", "data":[242.09375,68.21429443359375, ... 0.9757569432258606] }, "image":"background.png" }
          • manifest.json
            シールのマニフェスト
            script に指定した js が実行されるようだ { "editor": {"name":"MOONBlock","version":"1"}, "linked_pages": [], "access_urls": [], "script": "hack.js" }
          • hack.js
            実行されるスクリプト
          • background.png(ないシールもある)
          • images/ (ないシールもある)
          • lib/ (ないシールもある)
            MOON.js など、hack.js で importJS() する js ファイルがおいてある


          Evernote シールでは
          - info.json
          - manifest.json
          - hack.js
          - hack.js.old ← 残すなよw
          - background.png
          - images/
          - lib/


          Web をクリップしたシールでは
          - info.json
          - manifest.json
          - hack.js
          - background.png(Web の切り抜いた部分が収まるように四角にクリップした画像)

          manifest.json { "editor":{"name":"Columbia","version":"1"}, "linked_pages":["https://maps.google.co.jp/"], "access_urls":[], "script":"hack.js" } hack.js location.replace("https://maps.google.co.jp/");

          ブロックのサンプルシールでは
          - info.json
          - manifest.json
          - hack.js
          - images/
          - libs/

          manifest.json { "version":"1", "editor":{"name":"MOONBlock","version":"1"}, "linked_pages":[], "access_urls":[], "script":"hack.js", "blocks":[ ... ], "block_images":[] }

          描いた絵を指で囲んで link を選択して他のページ遷移できるシールを作成した場合は
          - info.json
          - manifest.json
          - hack.js


          manifest.json { "editor":{"name":"Columbia","version":"1"}, "linked_pages":["JTwZfnMf1375585755864"], (JTwZfnMf1375585755864 はリンク先のページID) "access_urls":[], "script":"hack.js" }

          hack.js location.replace("page://JTwZfnMf1375585755864"); (JTwZfnMf1375585755864 はリンク先のページID)
  • DCIM/
    • 100MOON/
      • MyNotebook1_[ページID].jpg
        ページのサムネイルが格納されている
        (私の enchant MOON では?)白背景の場合になぜか背景が薄い黄緑色になる
        (私の enchant MOON では?)黒背景の線の色が白ではなく薄い黄緑色になる
  • Screenshots/
    • Screenshot_yyyy-MM-dd-hh-mm-ss.png
      スクリーンショット(ペンの上側のボタンを押すと撮れる)が格納されている
      両サイドのページの区切り線がスクショにも入っていたが、2.3.1 のマイナーアップデートで区切り線がなくなったため、スクショにも出なくなった
  • LOST.DIR





enchant MOON で Hello world

$
0
0
1. シールにする絵を適当にペンで描いて、指でくるっと囲む

2. 出てきた選択肢から Link を選択して適当なページをリンク先に選ぶ

3. PC と接続する

/Data/MyNotebook1/[シールを描いたページのID]/[シールID]/に以下のファイルが作成されている
  • info.json
  • manifest.json
  • hack.js

4. [シールID] フォルダに lib フォルダを作成し、/App/MOONBlock/lib/MOON.js をコピーする

5. manifest.json の "linked_pages" を空配列にする { "editor":{"name":"Columbia","version":"1"}, "linked_pages":[], "access_urls":[], "script":"hack.js" } 6. hack.js を以下のコードに書き換える importJS(["lib/MOON.js"], function() { MOON.alert('Hello MOON', function() { MOON.finish(); }); }); 7. PC との接続を外す

8. シールをタップする

9. 以下のようになったら OK





MOON.loadData() を使って外部 js のコードを実行する

$
0
0
ますいさんのブログ 「_development : MOONBlockのコードをネットワーク経由で実行する」 にも書いてありますが、毎回 enchant MOON を PC に接続してコードを転送するのがめんどいので MOON.loadData() と eval() を使って外部の js コードを実行させるといろいろ捗ります。

ベースのシールの作り方は「enchant MOON で Hello world」を見てください。

シールフォルダの構成は
  • info.json
  • manifest.json
  • hack.js
  • lib/MOON.js
hack.js importJS(["lib/MOON.js"], function() { MOON.loadData("PATH TO JS", function(code) { eval(code); MOON.finish(); }.bind(this)); }); とりあえず "PATH TO JS" には Dropbox の Public においた js ファイル(以下みたいな)を指定してます。便利。 MOON.alert(MOON.getCurrentPage(), function() { MOON.finish(); });

enchantMOON Sticker オブジェクトっているの?

$
0
0
他のページを開くシールの hack.js は次のようになっています。 location.replace("page://JTwZfnMf1375585755864"); また、web ページを開く hack.js は次のようになっています。 location.replace("https://maps.google.co.jp/"); どうもタップすると hack.js が実行されるようです。

そこで、hack.js を次のようにしてシールをタップすると問題なくアラートがでてきます。 importJS(["lib/MOON.js"], function() { MOON.alert('Hello MOON', function() { MOON.finish(); }); });
しかし、次のようにしてシールをタップすると、ペンの太さは変わるのですが「シールの実行に失敗しました InvalidScript」と言われます。 importJS(["lib/MOON.js"], function() { MOON.setPenWidth(5.0); MOON.finish(); }); 次のように Sticker オブジェクトを使うようにすると「InvalidScript」が出なくなります。 importJS(["lib/MOON.js"], function() { var sticker = Sticker.create(); sticker.ontap = function() { MOON.setPenWidth(5.0); MOON.finish(); }; sticker.onattach = function() { MOON.finish(); }; sticker.ondetach = function() { MOON.finish(); }; sticker.register(); });


enchant MOON の MOON.js を見てみる

$
0
0
めんどいので API だけ羅列します。 気になる人は MOON.js 読んでください。別にそんな長くない。
  • html5CanvasToSerializableObject(canvas)
    HTML5 の canvas オブジェクトを渡すと pixel データを文字列にしたオブジェクトが返ってくる

  • serializableObjectToHtml5Canvas(object)
    html5CanvasToSerializableObject() で作成したオブジェクトを渡すと、HTML5 の canvas オブジェクトが返ってくる

  • loadData(src, callback)
    src で指定した URL を XMLHttpRequest を使って GET アクセスし、結果の responseText を callback に渡す importJS(["lib/MOON.js"], function() { MOON.loadData(url, function(code) { eval(code); MOON.finish(); }.bind(this)); });

  • alert(message, callback)
    message を表示したアラートを出す
    callback を渡して内部で MOON.finish() を呼ばないといけないとかなんとか。。。 importJS(["lib/MOON.js"], function() { var sticker = Sticker.create(); sticker.ontap = function() { MOON.alert("Hello MOON", function() { MOON.finish(); }); MOON.finish(); }; sticker.onattach = function() { MOON.finish(); }; sticker.ondetach = function() { MOON.finish(); }; sticker.register(); });

  • penPrompt(message, callback)
    手書き入力用のプロンプトがついたアラートを出す importJS(["lib/MOON.js"], function() { var sticker = Sticker.create(); sticker.ontap = function() { MOON.penPrompt("Hello MOON", function(input) { MOON.alert(input, function() { MOON.finish(); }); }); MOON.finish(); }; sticker.onattach = function() { MOON.finish(); }; sticker.ondetach = function() { MOON.finish(); }; sticker.register(); }); こんなん。プロンプト上にペンで文字を書くと認識して変換してくれる。
    文字でかくて4文字しか書けんぞ。。。




  • openStickerPage(callback)
    シール台帳を開く
    (ちなみにページのどこかを長押しするとシール台帳が開くようになっている)
    callback には選択したシール画像のパスが返ってくるらしい

    どのシールを選択しても返ってくるパスが /nullimages/sticker1.png 固定でよくわからん。。。 importJS(["lib/MOON.js"], function() { var sticker = Sticker.create(); sticker.ontap = function() { MOON.openStickerPage(function(path) { MOON.alert(path, function() { MOON.finish(); }); }); MOON.finish(); }; sticker.onattach = function() { MOON.finish(); }; sticker.ondetach = function() { MOON.finish(); }; sticker.register(); });

  • openNotebook(callback)
    ページ一覧を開く
    (ちなみにページでピンチアウトするとページ一覧が開くようになっている)
    callback には選択したページのIDが返ってくるらしい

    Invalid Script もなくページ選択画面になるのだが、ページを選択すると画面が真っ暗になって うんともすんともいわなくなる。。。
    こうなると再起動するしかない。。。 importJS(["lib/MOON.js"], function() { var sticker = Sticker.create(); sticker.ontap = function() { MOON.openNotebook(function(pageId) { MOON.finish(); }); MOON.finish(); }; sticker.onattach = function() { MOON.finish(); }; sticker.ondetach = function() { MOON.finish(); }; sticker.register(); });

  • uploadCurrentPageToEvernote(onsuccess, onfailure)
    Evernote に現在のページをアップロードする

  • getPageThumbnail(pageId)
    指定したページIDのページのサムネイルが canvas オブジェクトとして返ってくる

  • getEditPaperThumbnail()
    現在のページ(たぶん)のサムネイルが canvas オブジェクトとして返ってくる

  • createAPIBind()
    以下の API を MOON 以下に割り当てるのに使っている





  • peel()
    そのシールをはがす

  • finish()
    MOON を終了するっぽいことだけはなんとなくわかる

  • openUrl(url)
    ブラウザで url を開く importJS(["lib/MOON.js"], function() { var sticker = Sticker.create(); sticker.ontap = function() { MOON.openUrl("http://www.google.co.jp"); MOON.finish(); }; sticker.onattach = function() { MOON.finish(); }; sticker.ondetach = function() { MOON.finish(); }; sticker.register(); });

  • openPage(pageId)
    ページを開く importJS(["lib/MOON.js"], function() { var sticker = Sticker.create(); sticker.ontap = function() { MOON.openPage("iZjSL9Ve1375584059045"); MOON.finish(); }; sticker.onattach = function() { MOON.finish(); }; sticker.ondetach = function() { MOON.finish(); }; sticker.register(); });

  • setPenColor(red, green, blue, alpha)
    ペンの色を変える
    red, green, blue, alpha は 0 〜 255 までの整数 importJS(["lib/MOON.js"], function() { var sticker = Sticker.create(); sticker.ontap = function() { var red = 255; var green = 102; var blue = 95; var alpha = 255; MOON.setPenColor(red, green, blue, alpha); MOON.finish(); }; sticker.onattach = function() { MOON.finish(); }; sticker.ondetach = function() { MOON.finish(); }; sticker.register(); }); 背景色や描画色は rgba 合わさった値が入っている var page = MOON.getCurrentPage(); var backing = page.backing; var paper = MOON.getPaperJSON(backing); // 背景色を取得する var color = paper.color; var transparent = paper.transparent; // color には #aarrggbb を10進数に直した値がはいっている 例)-16777216 = 0xff000000, -1 = 0xffffffff

  • setPenWidth(width)
    ペンの太さ(筆圧に対する倍率)を変える importJS(["lib/MOON.js"], function() { var sticker = Sticker.create(); sticker.ontap = function() { MOON.setPenWidth(5.0); MOON.finish(); }; sticker.onattach = function() { MOON.finish(); }; sticker.ondetach = function() { MOON.finish(); }; sticker.register(); });

  • getCurrentPage()
    現在のページのオブジェクトが返ってくる { backing:"DC9g1Jkj1375584059045", papers:"GOXracRB1375753891252" } backing にはストローク情報のID、papers にはシールIDが入っているもよう
    /Data/MyNotebook1/[現在のページのID]/info.json の内容と同じっぽいが、 info.json では "stickers" だったのが "papers" になってる importJS(["lib/MOON.js"], function() { var sticker = Sticker.create(); sticker.ontap = function() { var page = MOON.getCurrentPage(); MOON.finish(); }; sticker.onattach = function() { MOON.finish(); }; sticker.ondetach = function() { MOON.finish(); }; sticker.register(); });

  • setCurrentPage(page?)
    よくわからないけど、現在のページを変更する?



  • getPaperJSON(pageId)
    指定したストローク情報IDに対応するストローク情報が返ってくる

    /Data/MyNotebook1/[現在のページのID]/[ストローク情報ID]/info.json の内容っぽい { version:"0.2", x:0, y:0, width:768, height:1024, scale:1.0, color:-16777216, transparent:false, strokes:[{"width":2.5,"color":-1,"type":"pen","data":[...]}, ...] } importJS(["lib/MOON.js"], function() { var sticker = Sticker.create(); sticker.ontap = function() { var page = MOON.getCurrentPage(); var backing = page.backing; var paper = MOON.getPaperJSON(backing); var version = paper.version; var x = paper.x; var y = paper.y; var width = paper.width; var height = paper.height; var scale = paper.scale; var color = paper.color; var transparent = paper.transparent; var strokes = paper.strokes; MOON.finish(); }; sticker.onattach = function() { MOON.finish(); }; sticker.ondetach = function() { MOON.finish(); }; sticker.register(); });

  • setPagerJSON(backing, paperJSON)
    ストローク情報を書き換える importJS(["lib/MOON.js"], function() { var sticker = Sticker.create(); sticker.ontap = function() { var page = MOON.getCurrentPage(); var backing = page.backing; var paper = MOON.getPaperJSON(backing); // paper のプロパティを変更する paper.color = -6710887; // 背景色を #ff666666 にする MOON.setPaperJSON(backing, JSON.stringify(paper)); MOON.finish(); }; sticker.onattach = function() { MOON.finish(); }; sticker.ondetach = function() { MOON.finish(); }; sticker.register(); });

  • getImagePath(?)
    よくわからない



  • searchWeb(query)
    ブラウザを開いて検索する importJS(["lib/MOON.js"], function() { var sticker = Sticker.create(); sticker.ontap = function() { MOON.searchWeb("enchant MOON"); MOON.finish(); }; sticker.onattach = function() { MOON.finish(); }; sticker.ondetach = function() { MOON.finish(); }; sticker.register(); });

  • searchStorage(filter)
    各ノートの storage.json を検索する importJS(["lib/MOON.js"], function() { var sticker = Sticker.create(); sticker.ontap = function() { // storage.json に fav:1 が入っているノートをピックアップする // function の引数には各ノートの storage.json をオブジェクト化したものが入っている MOON.searchStorage("function matches(j){return j[\'fav\'] > 0;}"); MOON.finish(); }; sticker.onattach = function() { MOON.finish(); }; sticker.ondetach = function() { MOON.finish(); }; sticker.register(); }); storage.json にプロパティを追加するには localStorage オブジェクトを使う localStorage['fav'] = 1;

  • showParticle(particle?)
    showParticles() との違いがよくわからない

  • showParticles(particles)
    パーティクルを表示する
    particles は配列で x, y, vx, vy, ?, x, y, vx, vy, ?, ... のようにデータを入れる
    5番目がわからない。フレーム数?時間?距離? importJS(["lib/MOON.js"], function() { var sticker = Sticker.create(); sticker.ontap = function() { var page = MOON.getCurrentPage(); var backing = page.backing; var paper = MOON.getPaperJSON(backing); var x = paper.width / 2; var y = paper.height / 2; var particles = []; for (var i = 0; i < 10; i++) { particles.push(x + i, y + i, 0.1, 0.1, 40); } MOON.showParticles(particles); MOON.finish(); }; sticker.onattach = function() { MOON.finish(); }; sticker.ondetach = function() { MOON.finish(); }; sticker.register(); });

  • recognizeStrokes(?)
    よくわからない



現在のページのストロークを全消去するシール作った enchantMOON

$
0
0
1. 全消去シールにしたい絵を描く



2. 指で囲って Link を選択して適当なノートを選択

3. PC と接続する

/Data/MyNotebook1/[シールを描いたページのID]/[シールID]/ に以下のファイルが作成されている
  • info.json
  • manifest.json
  • hack.js
4. [シールID] フォルダに lib フォルダを作成し、/App/MOONBlock/lib/MOON.js をコピーする
  • info.json
  • manifest.json
  • hack.js
  • lib/
    • MOON.js
5. manifest.json の "linked_pages" を空配列にする { "editor":{"name":"Columbia","version":"1"}, "linked_pages":[], "access_urls":[], "script":"hack.js" } 6. hack.js を以下のコードに書き換える /** * 現在のページのストロークをすべて削除するシール * @auther yanzm */ importJS(["lib/MOON.js"], function() { var sticker = Sticker.create(); sticker.ontap = function() { MOON.finish(); }; sticker.onattach = function() { // 削除 var page = MOON.getCurrentPage(); var backing = page.backing; var paper = MOON.getPaperJSON(backing); // ストロークを空にする paper.strokes = []; MOON.setPaperJSON(backing, JSON.stringify(paper)); // シールをはがす MOON.peel(); MOON.finish(); }; sticker.ondetach = function() { MOON.finish(); }; sticker.register(); }); 7. PC との接続を外す

8. シールを囲んで Save を押す



9. ストークを消したいページで画面を長押ししてシール台帳を開く

10. 全消去シールをタップ





3回に1回くらいの成功率です。。。なんでだろー。。。



Viewing all 415 articles
Browse latest View live