Android Wearアプリ(WatchFaceも)をGoogle Playで公開するときにbuild.gradleの共通化をやっておいた方がいいと思います。
Android Wearアプリプロジェクトを作成すると、標準ではmobileモジュールとwearモジュールが作成され、それぞれのモジュールにbuild.gradleが作成されます。
Google Playにアプリを公開する場合、build.gradleで指定するversionCodeとversionNameはmobile,wearモジュールで共通にしなければなりません。
初回アップロード時は両方同じ値なので問題ありませんが、アプリをバージョンアップする際に2つのファイルをいじらないといけないのは面倒くさいと思います。(というか絶対に忘れる)
そのためversionCodeなどは、一箇所直せばmobileとwearのどちらにも適用されるようにしてやるといいと思います。
私はmobile,wearのbuild.gradleで共通して利用する部分を、別ファイルにして読み込ませるようにしてみました。
QiitaのAndroidの署名情報(signingConfigs)を外出しようを参考にさせていただきました。
/mobile/buildConfig.gradle
defaultConfig {
applicationId "jp.gcreate.product.customphotowatch" minSdkVersion 18
targetSdkVersion 21 versionCode 3 versionName "1.0.2" } /mobile/build.gradle
apply plugin: "com.android.application" android { apply from: "configBuild.gradle", to: android compileSdkVersion 21 buildToolsVersion "21.1.2" } 〜dependenciesは省略 /wear/build.gradle
apply plugin: "com.android.application" android { apply from: "../mobile/configBuild.gradle", to: android compileSdkVersion 21 buildToolsVersion "21.1.2" defaultConfig{ minSdkVersion 20 }
} 〜dependenciesは省略 上記では省略しましたが、buildTypeも外部ファイルに出して両者で同じ設定が適用されるようにしてます。
やってて未だに不安なのが、ちゃんと正しく設定できているのか、確認の仕方がいまいち分からず不安だということでしょうか・・・。
先日のDroidKaigiで発表のあった、つかえるGradleプロジェクトの作り方のやり方も参考になります。
こちらのスライドでの方法は、versionCode等の値を/build.gradleで定義し、各々のプロジェクトその値を参照することで共通化するやり方です。
こちらのやり方のほうが分かりやすいなぁって発表聞いてて思いました。
ちなみにAndroid Studioではルートのことをプロジェクト、mobileとかwearのことをモジュールと呼びますが、Gradleの世界ではどれもプロジェクトと呼ぶそうです。勉強になりました。
2019/05/18追記: この記事の情報は古いので公式ドキュメントを参照してください。
データのやりとりはWearable.DataApiを使うことでやりとりできます。Wearable.MessageApiを使うことでもできます。
両者の違いはこんな感じ。
DataApi 接続が切れていても送信できる データはonDataChangedで受け取る 送れるデータは100KBまでだが、Assetを使うことで大きいデータも送れる データを送信するというより、DataItemを更新して、その更新を通知するイメージ MessageApi 現在接続中のノードに対してデータを送信することができる データはonMessageReceivedで受け取る データを送る際にはノードを指定する必要がある ノードIDについて 当たり前ですが、ノードIDはAndroid Wear端末とスマホで異なります。
WatchFaceを作成して、その設定画面を用意している場合、WearのノードIDはスマホ側では簡単に取得できます。
mobile側の設定画面となるActivityでgetIntent().getStringExtra(WatchFaceCompanion.EXTRA_PEER_ID)とするとWearのノードIDが取得できます。これは設定画面の起動がAndroid Wearアプリ経由で行われるためです。
一方Wearから、もしくはmobileからでもAndroid Wearアプリを経由しない起動の仕方をするアプリの場合はこの方法では取得できません。
その場合はNodeApi.getConnectedNodesを使うことで、接続されている端末のノードリストを取得することができます。現状スマホとAndroid Wearは1:1でペアリングされるはずなので、これで相手方のノードIDを取得できるでしょう。(将来的に複数ペアリングできるようになったらどうやればいいんでしょうね?)
DataApiを使ったデータ送信の注意点 [DataApi – Android Developers]のConcurrencyにありますが、DataApiを使ったからといってmobileとwearでDataItemが全く同じになるわけではありません。
onDataChanged()内であれば正しいデータが参照できます。これは変更があったDataItemが通知されてきているからです。
しかしAndroid Wear端末を再起動した後に、DataApiを使って送信された設定データを読み込もうとした時に問題が生じます。
wear側からDataItemを識別するUriでデータを取りに行っても、mobile側の設定と齟齬が生じている可能性があります。
その理由はDataItemが以下のように識別されているからです。
wear://ノードID/パス
mobileで作ったDataItemはmobileのノードIDで識別されます。同じパス文字列で識別しているからといって、勝手にwearのノードIDのものが変更されるわけではありません。
これはWatchFaceのサンプルコード(com.example.android.wearable.watchface)を見ると何となく分かると思います。サンプルコードでは、mobileからのDataItemの変更を受け取ると、wear側で同じデータを上書きする処理を行っています。
サンプルのようにmobileからもwearからもデータを送り合うようなアプリの場合、どちらのノードのDataItemも常に同じ状態にするように配慮しないと齟齬が生じて困ることになると思います。
片側からしか送らないというのであれば、ノードIDを指定してDataItemを取りに行くのもありかもしれません。
DroidKaigiに行ってきました。飛行機使って前泊での参加です。
満員でしたね。熱気がすごい。
今日ほど分身の術が使えたらと思うことはなかったでしょう。それくらい、全部のセッション聞きたかったです。
今後のアプリ開発に役立つ情報がいっぱいでした。現状動いてはいるけど、ちゃんとできてなかったところとか、「そうなんだ」っていう気付きもあって有意義でした。
とりあえず面倒臭がらずに少しずつテストを書くことから始めようかなと思います。
テストを動かす環境づくりがよくわからないとか、テストの書き方がよく分からないとか、個人でやってると仕様の変更で対応してしまってテストまで書き換える必要が出てきて面倒くさいとかで敬遠してたんですけど、つべこべ言わずにテスト書こうと思いました。
一方で、周りの空気に飲まれて受け身になりすぎたのが反省点です。なんかもうちょっと攻めの姿勢で聞けたら良かったのにと、振り返って思います。
質問とかできたら良かったんですけど、頭働かなくてそれどころではありませんでした。
言い訳ですけど、参加してる間はそれどころではなかったんですよね・・・。周りスゲーし、人はいっぱいだし、席の確保も大変だし、スケジュールは過密だしで話を聞くのが精一杯でした。
幸い皆さんスライドを公開してくださっている上に、スライド見るだけでも話の内容がある程度分かるようになってるので、後でじっくり復習したいと思います。
個人でアプリ作ってると、こういった周りの開発者さんたちの空気感とか全くわからないので、参加してよかったなと思います。わざわざ岡山から出張った甲斐はあったかな・・・。
運営の皆さん、発表者の皆さん、参加者の皆さん、お疲れ様でした。
Master of Fragmentの更新を首を長くして待ってます。
UndoBar – GitHub
削除処理を行って、それを取り消し可能なようにする実装を考えます。例えばGmailのアプリでメッセージをアーカイブしたときなどに表示されるあれです。
削除する際に「本当に消しますか?」みたいな確認ダイアログを表示するのはナンセンスっぽいです。
なぜならその確認ダイアログは、データが消えてアクセスできなくなるという責任をユーザーに転嫁してるだけだということです。そもそも削除したいだけなのにいちいち確認されるのもうっとおしいだけではないかという理由もあります。
それならば、もし消したくないデータを誤って削除してしまったとしても、それを復元できるようにするのがユーザー目線でいいだろって話です。
この辺りの話はSmashing Android UI レスポンシブUIとデザインパターンという本で知りました。
Smashing Android UI レスポンシブUIとデザインパターン では実際に取消可能な削除機能を実装するためにはどうすればいいかというと、UndoBarを利用するのが簡単そうな気がします。Crouton使おうかと思ったけども、いざ使おうと思ったらレイアウトを実装したりするのが~~面倒~~大変だったので、シンプルなUndoBarを使いました。ソースコードはCroutonの方が読みやすかったけど。 UndoBarの表示 今回私がやりたかったのは、データベースに保存してあるデータを消すという例です。データベースに保存されているデータをListViewで表示している。そのListViewのうちの1つのアイテムを選んで削除をするというパターン。
実装方法としては削除の操作を行った時(UndoBarを表示させる時点)でどうするかによって、2パターンに分岐するかと思います。
この時点で実際にDB上のデータを消してしまう この時点ではDBは触らず、見た目上のデータを消す どちらのパターンにせよ、この時点でListViewに表示されてるデータを消すのは共通です。データベースのデータを消すタイミングがここかどうかです。 1.のパターンは見た目と実装がそのままリンクしています。Listから消えたらデータベースからも消える、シンプルです。
このパターンの場合は操作の取消を選んだら、消したデータを復元する必要があります。そのためどうやってデータをロールバックするのかに頭を悩ませることになります。データベースの構造が複雑だと元に戻すのが大変かもしれません。
2.の場合はUndoといいつつ、実際には削除処理を猶予しているだけです。このパターンの場合、取消操作はデータベースからデータを削除するのを取り消す処理(ややこしい)になります。
こちらは復元処理を考える必要がありません。一方でUndoBarが消える前にアプリを終了したり、画面が回転したときどうすんのっていう新たな問題が生じます。
UndoBarからのコールバック UndoBarController.AdvancedUndoListenerを使うことで3つのタイミングによるコールバックを受け取ることができます。
この3つのコールバックをうまいこと利用して削除機能を実装していくことになります。
onUndo() 取消ボタンを押した際に呼ばれます。Undo時の処理をここで行うと良いです。
パターン1のときならここで復元処理を行うことになります。
onHide() 取消ボタンが押されず、UndoBarが消える際に呼ばれます。UndoBarに設定した表示時間が経過した後でコールされるわけです。UndoBar表示中に画面回転した場合には呼ばれません。
ちなみに画面回転に対応するには、自分でハンドリングしてやらないといけないそうです。今回私はそこまで踏み込みませんでした。
onClear() UndoBar.clear()を明示的にコールした際に呼ばれます。
単に画面回転させたら呼ばれるのかというとそうではありません。画面回転時にコールバックを受け取りたいなら、Activity#onDestroy()(もしくはonStop(),onPause())でclear()してやる必要があります。
そのためにはUndoBarのインスタンスをフィールド変数として保持しておかなりといけません。サンプルのように、単にnew UndoBar()して表示させていると、表示したUndoBarを識別できないので、clear()することができません。
ただし単にUndoBarを消したいときとごっちゃになってややこしくなるので、画面回転時にclearするのはよくないかもしれません。回転時は自分でハンドリングして再表示してやるのが一番いいと思います。
削除機能を実装する 1.実際に削除するパターン onUndo()で削除した対象のデータを元に戻します。このパターンだとここがややこしいです。
消すデータをメモリ上に確保しておいて、Undoされたら消したデータを再インサートする。データベースに削除フラグ持たせてそれを立てる。退避用テーブルにデータを移しておく。・・・どういう方法を取るのがベストなんでしょうかね?
ちなみに私はメモリ上にデータを持たせて、Undoされた際に再登録する方法を選びました。
onHide()は何もしないでいいです。UndoBar表示時点でデータは消えてますので。
onClear()はonHide()と同じ処理でいいでしょう。
2.Undoされなかったら消すパターン onUndo()はListViewを元に戻すだけでいいです。データベースは変更されていないので、DBから読みなおすなり、消したデータだけAdapterに追加するでもいいと思います。
onHide()で実際にデータベースからデータを消します。
onClear()はonHide()と同様にデータベースからデータを消します。でないとUndoBar表示中に追加で削除処理が実行された際に困ります。
UndoBar表示中に追加の削除処理を行ったとき UndoBar表示中に更に削除処理を行い新しいUndoBarを表示させたらどうなるか。表示されているUndoBarは以前のもののままです。新しいUndoBarは古いUndoBarの表示時間が過ぎた後で表示されます。
これでは削除対象と表示されてるUndoBarがリンクしないのでよろしくありません。今削除したものを取り消したつもりが、以前処理した奴が取り消されてしまったということになります。
そのため削除処理を行ったら、現在表示中のUndoBarはclear()で消すべきでしょう。
簡単そうでいざ実装してみると大変だった UndoBarを使って取消可能な削除機能を実装してみましたが、やってみると考えることが多くて意外と大変でした。
それでも自力で実装しようと思うともっと大変でしょうから、ライブラリ作者様には頭が上がりません。
UndoBarはbuild.gradleに1行追加するだけで使えるようになるので、削除処理を実装するときには仕様を検討してみてください。
UndoBar – GitHub
今までずっとLog.d("test",”デバッグメッセージだよ”);みたいな感じでLogを出力し、Logcatで確認しながらプログラミングしていたのですが、とあるサンプルを見ていた時にこんなコードに出くわしました。
private static final String TAG = "DigitalWatchFaceConfig"; if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "onConnected: " + connectionHint); } プログラムを実行しても、このログはlogcatに出力されません。
「なんでだ?」と思って調べているうちに、この方法はAndroidアプリ開発していく上で賢い選択なのだなということが分かってきました。
ベストプラクティスなのかどうかまでは分かりませんが、少なくともいきなりLog.d()で出力したり、アプリ内でprivate boolean isDebug = true;みたいにしてデバッグログを出力させるよりは賢いなと思いました。
参考サイト
[Log.isLoggable – API Refference](https://developer.android.com/reference/android/util/Log.html#isLoggable(java.lang.String, int))
ログレベルを制御する – TechBooster
ログの出力はアプリのパフォーマンスを下げる ログの出力はアプリのパフォーマンスに影響します。少なくともStringオブジェクトを作ってそれを出力するわけですからね。
リリース時にはLog出力する部分を全部削除するのが一番いいのでしょうが、さすがにそれは手間が大きすぎます。それにリリースしたからといって開発が終わるわけでもなく、メンテのためにまた1からLogを出力するように直すのはあまりにも馬鹿らしいです。
Logを使わず開発するのはそもそも無理です。
Log.isLoggableによるチェック Log.isLoggableによるチェックは、端末に設定されているログ出力レベルを判定しています。デフォルトでは全てのタグについてINFOが設定されています。
つまり最初のコードのようなLog.DEBUGでチェックをかけるとfalseが返ってくるのでログが出力されません。
ログ出力レベルの変更 ではどうやってログが出力されるようにすればいいのかというと、ターミナルでadb shell setpropコマンドを使います。
adb shell stop adb shell setprop log.tag.設定したいタグ名 ログレベル adb shell start 最初の例のログを出力させようと思ったらadb shell setprop log.tag.DigitalWatchFaceService DEBUGとターミナルから打ち込んでやればOKです。(ちなみにadb shellで端末にログインしてからであれば、いきなりsetpropから初めてOKです)
ログ出力レベルの確認 タグごとのログ出力レベルを確認するには、adb shell getprop log.tag.タグ名を使います。何も設定していない状態であれば空白が返って来ます。setpropで設定してやると、現在設定されているログ出力レベルが返って来ます。
ログレベルの優先度 VERBOSE > DEBUG > INFO > WARN > ERROR > ASSERTとなっています。デフォルトではINFOになっているので、isLoggable()はINFO,WARN,ERROR,ASSERTでtrueを返します。
setpropでVERBOSEを設定するとあらゆるレベルでtrueが返るようになります。setprop SUPPRESSとすると逆にあらゆるレベルでfalseが返るようになります。
再起動したら元に戻る ちなみに設定する端末が開発専用であれば問題ないでしょうが、日常的に使っている端末の場合はログ出力レベルをINFOに戻すのを忘れないようにしましょう。(パフォーマンスに影響するため)
別に忘れても再起動すれば元に戻る(設定が消える)ので気にしなくてもいいかもしれません。
ちなみに「再起動の度に設定するのは面倒くさい」という場合には、/data/local.propで設定することもできるようですが、どうやって設定するのかは分かりません。多分root権限ないとできないんじゃないかと思います。(やってみたけどPermission deniedって言われました)
リリース予定がないならいきなりLog.d()でも構わないのでしょうが、リリースを視野に入れているアプリならこういった方法でログを出力するように設定しておくとユーザに優しいと思います。
ImageViewなり、自分で作ったCustom Viewなりで表示させる画像を、動かしたり拡大縮小させたりするのに使えるMatrixをいじって学んだことのメモです。
特にpostScaleを使った拡大縮小がイメージ通りに動かなくてハマってしまいました。
ちなみにMatrixクラスを使ってBitmapを加工する – Techoboosterを参考に始めました。
使い方 ImageViewに設定するには、setImageMatrixメソッドでMatrixを渡してやるといいです。
Matrix matrix = new Matrix(); ImageView.setImageMatrix(Matrix); CustomViewで使う場合は、オーバーライドしたonDraw()で描画するときにMatrixを渡せばいいです。
@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); canvas.drawBitmap(mImage, mMatrix, mPaint); } こんな感じ。mImageはBitmapオブジェクトで、mMatrixとmPaintはそれぞれnew Matrix(),new Paint()したものを渡してます。
今回は下のCustom ViewでMatrixを操作していて分かったことを書きます。
タッチ操作で動かす 画面上を指でなぞると、その動きに応じて画像も移動するようにする場合はこうすればOK。
mMatrix.postTranslate(float X移動量,float Y移動量);
移動量を取得するにはTouchEventを自分で判定するなり、GestureDetectorを使うなりして取得します。
GestureDetectorの使い方はDetecting Common Gestures – Android Developers参照。また別途記事書こうと思います。
移動に関しては特に難しくはありませんでした。ただし、移動制限を設けようとするとこれはこれでまた大変そうです。
こちらの記事が移動制限を実装するのに非常に役立ちそうな予感です。実装できたらまた記事書きたいと思います(そればっか)。
拡大縮小 ピンチイン・アウトで画像を拡大縮小させる場合がクセモノでした。
mMatrix.postScale(float X拡大率, float Y拡大率, float 拡大の起点X, float 拡大の起点Y);
ハマったポイントはここで渡す拡大率の扱いです。
postScaleに渡す拡大率は、Matrixを指定した拡大率に変形させるのではありません。現在のMatrixを渡した拡大率で拡大縮小させます。Matrixの拡大率が0.1のときにpostScale(0.1f,0.1f)するとMatrixの拡大率は0.01になります。
画像が過剰に縮小・拡大されないように渡す拡大率の値を制限したとしても、制限した値をそのまま渡してしまったら制限が効きません。
指定した拡大率に画像を変形させたい場合は変化量を計算して渡すようにします。 ScaleGestureDetectorを使って拡大縮小させていて、頭の中がこんがらがっていました(現在進行形ですけど)。ScaleGestureDetectorを使うと、onScaleメソッド内でdetector.getScaleFactor()を使うことでピンチイン・アウトによる拡大率を取得することができます。
この拡大率は、ピンチ操作が始まった段階では1.0から始まります。そのためこの値をそのままMatrixのpostScaleに渡すと、拡大縮小の開始時に一旦元の縮尺に戻ってしまいます。そのこととごっちゃになっていて間違ったこと書いてました。
float deltaScale = targetScale / nowScale; mMatrix.postScale(deltaScale, deltaScale); postScaleではなくsetScaleを使う方法もあるのかもしれませんが、動き始めに画像が元のサイズに戻ってしまうため、この方法がスマートな気がします。
ちなみにピンチイン・アウトを検出するにはScaleGestureDetectorが使えます。Dragging and Scaling – Android Developers
拡大縮小の起点を指定する場合、detector.getFocusX()を使うとイメージに近い動きになりました。ただし画像の範囲外でやると当然ながら動きがおかしくなるので、画像が画面の範囲外に移動できないように制限を実装しないとダメそうです。
現在の拡大率を取得するには? Matrixに設定された現在の拡大率を取得するのも一工夫必要です。getScaleXというような、Marixの現在の拡大率を直接取得するメソッドはありません。
Matrixは9つの値を保持していますが、その値を参照するためにはfloatの配列を渡してコピーしてもらうしか方法がありません。
float[] values = new float[9]; mMatrix.getValues(values); nowScale = values[Matrix.MSCALE_X]; ちなみにAPIリファレンスにある並びで並んでいるわけではないので、定数を使ってアクセスしましょう。
ちなみに0から順にMSCALE_X,MSKEW_X,MTRANS_X,MSCALE_Y,MSKEW_Y,MTRANS_Y,MPERSP_0,MPERSP_1,MPERSP_2の順に並んでます。
なんで変な並びになってるのかと思ったら、英語のWikipedia見たらその意味が分かるかもしれません。Transformation matrix – Wikipedia
Android特有の概念ではなく、Matrixを使った変形の概念があるんですね。数式だらけでサッパリ分かりませんが。
日本語で解説してあるサイトもありますが、いずれにしても奥が深そうで難解です・・・。
端末内に保存されている画像を表示したり、もしくはその場でカメラで撮影した画像を表示させる方法です。
例えばSNSへ投稿する画像を選択したりするのに使うことが考えられますかね。
やり方としてはIntentを発行して、startActivityResult()で結果を受け取って表示させるようになります。
画像の選択とカメラでの撮影は異なるアクションなので、1つのIntentで表現するにはIntent.createChooser()で複数のIntentをひとまとめにして発行することになります。
やってみると、カメラで撮影した画像を受け取るのにちょっと工夫が必要なだけで、割と簡単に実装できました。
Getting a Result from an Activity – Android Developers
Intentの発行 画像を選択するIntent Intent pickPhotoIntent = new Intent() .setType("image/*") .setAction(Intent.ACTION_GET_CONTENT); カメラで撮影するIntent カメラで撮影する場合、以下のIntentでも撮影→その画像を受取ることができますが、そのままでは画像サイズがとても小さくなってしまいます。(サムネイルサイズの小さな画像が返ってくる)
Intent takePhotoIntent = new Intent() .setAction(MediaStore.ACTION_IMAGE_CAPTURE); 複数のIntentを埋め込む Intent chooserIntent = Intent.createChooser(pickPhotoIntent, "画像を選択"); chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS,new Intent[]{takePhotoIntent}); 作成したIntentの1つを元にしてcreateChooser()を呼び出して作成したIntentに、Intentの配列を埋め込みます。
画像を受け取る @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if(requestCode == REQUEST_GET_IMAGE && resultCode == Activity.RESULT_OK) { if (data != null) { Bitmap image = null; if (data.getExtras() != null && data.getExtras().get("data") != null) { image = (Bitmap) data.getExtras().get("data"); mImageView.setImageBitmap(image); } else { try { InputStream stream = getContentResolver().openInputStream(data.getData()); image = BitmapFactory.decodeStream(stream); mImageView.setImageBitmap(image); } catch (FileNotFoundException e) { e.
Android Wearでアプリを起動するのに音声入力でアプリが起動できるととても便利です。Wear用アプリを作る上では外せない要素だと思います。
Android Developersのトレーニングを見ると、AndroidManifest.xmlでintent-filterかけておくだけでいいということです。activityに設定したlabelをキーワードとして、Activityが起動するようになります。
ぶっちゃけると、ラベルをしっかり設定さえすれば、初期状態で音声入力によってActivityが起動するということであります。
Adding Voice Capabilities – Android Developers
しかしいくらやってみてもうまくいかなくて、何がいけないのかサッパリ分かりませんでした。
単純すぎるのか調べてもなかなかピタリとくる情報もなくて困っていたら、Stack over Flowを見て謎が解けました。
Wear: Open my app with custom voice Start command, not working – Stack Over Flow
つまるところ、mobile側のActivityにintent-filterをつけてないとうまく動作しないのです。
私はデバッグのため、wear側のアプリしか動かしていませんでした。(開発中のサンプルアプリはwearにしかインストールされていない状態)
スマホ側に音声コマンドを受け取るintent-filterがなかったためにWear上のActivityも起動しなかったということなんだと思います。
Wearアプリを開発する場合は、Wearモジュールだけでなく、mobileモジュールも実行してスマホにインストールしておかないと、ちゃんとした動作確認ができないということが分かりました。
実験 構成 Android StudioのNew Projectウィザードで作成した初期状態のままです。
wearモジュールもmobileモジュールも、Hello Worldの文字列を表示するだけのMainActivityがあるだけの状態です。
mobileのラベルとwearのラベルを同じにする mobile側のActivityのラベルに「サンプル」と設定して、wear側のActivityのラベルにも「サンプル」と設定します。
この状態でOK Googleから「サンプル開始」と言うと、WearのMainActivityが起動します。スマホのMainActivityは起動しません。
mobileのラベルとwearのラベルを異なるものにする mobile側のActivityのラベルに「サンプル」と設定して、wear側のActivityのラベルに「テスト」と設定します。
この状態でOK Googleから「サンプル開始」というと、スマホ側のMainActivityが起動します。(Wear端末には「アプリを開いています・・・」というメッセージが表示され、スマホ側で指定したActivityが起動します)
一方で「テスト開始」というと「テスト開始」でWeb検索を行った結果がWear端末に表示されます。
どちらにせよWearのMainActivityは起動しませんでした。
スマホ側での音声入力 ちなみにスマホ側でOK Googleからの音声入力を行った場合は、全てWeb検索として扱われてしまい、MainActivityは起動しませんでした。
どういうことなんでしょう?
そもそもスマホとペアリングされていないと音声入力は使えない Wear端末はスマホとペアリングされている状態でなければ音声入力を使うことができません。
このことから、音声入力の処理を実際に行っているのはスマホ側であると考えられます。
おそらくスマホ側に入力結果に該当するActivityがあるかどうかが調べられ、なければWeb検索として処理されるのだと思います。
スマホ側に該当するActivityがあれば、そのActivityを開くぞという情報をwear端末へ送るのでしょう。その際にWear上にも同一のActivityがあれば、Wear上でActivityが開かれるんでしょう、多分。
この辺りの音声入力の結果、どのようなデータがどうやって送受信されているのかを調べる方法があればもっと分かりやすいとは思うのですが、私には調べる方法が分かりません。
Android Developers探したらどこかにあるんですかね・・・?
しかし一時期mobile側関係なく動いていたが・・・? 以前試した時は、mobile側のラベルに関係なくWearにインストールしたアプリのActivityが起動していました。
そのためいまひとつ腑に落ちません。
うまくいかなくなったのはWear端末のリセットを行ってからなので、何らかの設定が変わってしまったせいもあるかもしれません。OSのバージョンが5.0.2になったせいもあるかもしれません。
Nexus5(Android5.0.1)を購入したのですが、そのままではUSBデバッグができなくて困りました。
パソコンに繋いでもAndroid Studioから端末が認識されません。設定画面を探しまわってもそれらしい設定項目がありませんが、どうもデフォルトでは表示されないようになっているようです。
これを表示させるためには、Androidの設定画面を表示し、一番下にある端末情報を開きます。そして更にその画面の一番下にある「ビルド番号」を連続でタップします。
そうすることで開発者向けオプションが表示されるようになります。
後は開発者向けオプションの設定メニューから、USBデバッグを有効にするにチェックをつけ、端末をパソコンに接続すればAndroid Studioから認識されるようになります。
静止画像(pngなどの画像リソース)を用意してパラパラ漫画の要領でアニメーションさせるには、AnimationDrawableクラスを利用します。
Android APIs Reference – AnimationDrawable
文字が変わってるだけですが、3つの画像でアニメーションしてます。画像を準備するのが面倒くさかったので、文字だけの画像を使いました。
アニメーションに使う静止画像 画像は解像度に合わせてres/drawable/hdpiなどのディレクトリに用意します。
今回はanime_test1.png,anime_test2.png,anime_test3.pngの3つの画像ファイルを用意しました。画像と言いつつ数字の1,2,3が書かれているだけの画像です。
ちなみにファイル名として使えるのは小文字のアルファベット、数字、アンダースコア(_)とドット(.)のみです。それ以外の文字(大文字アルファベットなど)を使うと以下のようにコンパイルエラーとなります。
Invalid file name: must contain only lowercase letters and digits ([a-z0-9_.]) アニメーション設定のXMLファイル どの画像を何秒間表示させるのかという設定をXMLファイルに記述します。今回はres/drawable/test_animation.xmlというファイル名にしました。
<?xml version="1.0" encoding="utf-8"?> <animation-list xmlns:android="http://schemas.android.com/apk/res/android" android:oneshot="false"> <item android:drawable="@drawable/anime_test1" android:duration="500"/> <item android:drawable="@drawable/anime_test2" android:duration="500"/> <item android:drawable="@drawable/anime_test3" android:duration="500"/> </animation-list> android:oneshot=trueで、アニメーションを1回のみ再生する設定になります(最後の画像でアニメーションが止まる)。falseだとループ再生されます。
アニメーションを再生する test_animationは何もしなければ単なる静止画と同じで、Drawableとして扱うことができます。ImageViewのsrc属性に設定したり、TextViewのbackground属性に設定したりすることができます。
今回はImageButtonに上記で作成したdrawableを設定してやり、ボタンを押したらアニメーションが再生されるようにしてみます。
activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <ImageButton android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/image_button" android:src="@drawable/test_animation" /> </RelativeLayout> APIリファレンスではandroid:background属性に設定していますが、これはandroid:src属性に設定しても動きました。src属性にAnimationDrawableを設定した場合、getBackground()ではなくgetDrawable()でAnimationDrawableを取得します。
MainActivity.java(onCreateを抜粋)
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ImageButton imageButton = (ImageButton) findViewById(R.id.image_button); final AnimationDrawable animationDrawable = (AnimationDrawable) imageButton.getDrawable(); imageButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { animationDrawable.start(); } }); } AnimationDrawableを取得して、start()メソッドを呼び出せばアニメーションさせることができます。
ただしできるのは再生するか停止するかくらいで、逆再生したりはできないみたいです。