Android Wearアプリを開発するときはversionCodeなどを一元管理すると便利

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の世界ではどれもプロジェクトと呼ぶそうです。勉強になりました。

Android Wearでwearとスマホ間でデータをやりとりする話

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に参加してきました

DroidKaigiに行ってきました。飛行機使って前泊での参加です。 満員でしたね。熱気がすごい。 今日ほど分身の術が使えたらと思うことはなかったでしょう。それくらい、全部のセッション聞きたかったです。 今後のアプリ開発に役立つ情報がいっぱいでした。現状動いてはいるけど、ちゃんとできてなかったところとか、「そうなんだ」っていう気付きもあって有意義でした。 とりあえず面倒臭がらずに少しずつテストを書くことから始めようかなと思います。 テストを動かす環境づくりがよくわからないとか、テストの書き方がよく分からないとか、個人でやってると仕様の変更で対応してしまってテストまで書き換える必要が出てきて面倒くさいとかで敬遠してたんですけど、つべこべ言わずにテスト書こうと思いました。 一方で、周りの空気に飲まれて受け身になりすぎたのが反省点です。なんかもうちょっと攻めの姿勢で聞けたら良かったのにと、振り返って思います。 質問とかできたら良かったんですけど、頭働かなくてそれどころではありませんでした。 言い訳ですけど、参加してる間はそれどころではなかったんですよね・・・。周りスゲーし、人はいっぱいだし、席の確保も大変だし、スケジュールは過密だしで話を聞くのが精一杯でした。 幸い皆さんスライドを公開してくださっている上に、スライド見るだけでも話の内容がある程度分かるようになってるので、後でじっくり復習したいと思います。 個人でアプリ作ってると、こういった周りの開発者さんたちの空気感とか全くわからないので、参加してよかったなと思います。わざわざ岡山から出張った甲斐はあったかな・・・。 運営の皆さん、発表者の皆さん、参加者の皆さん、お疲れ様でした。 Master of Fragmentの更新を首を長くして待ってます。

UndoBarを使った削除処理を考える

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

カスタムフォトウォッチ

Android Wearを使っていて、背景画像を自分の好きな画像に設定したいと思ったことはないでしょうか。そんな人にちょうどいいのがこのカスタムフォトウォッチです。 作成のきっかけはデフォルトで設定できるウォッチフェイスの中に、日付と曜日を表示するものがなかったことでした。下にスワイプすれば日付は表示されますが、曜日は出ないしいちいちスワイプするのも面倒くさいので、盤面に常に表示してあるのがほしいなと思ったのです。 今のところデジタル時計だけですが、アナログ形式は要望が多ければ追加するかもしれません。(個人的にアナログは見づらいので優先度低めです) 以下の説明はすべてVer1.0.1のものです。 インストールの仕方 アプリはGoogle Playにて公開中です。 カスタムフォトウォッチを使うためには、Android Wear端末とGoogleのAndroid Wearアプリが別途必要です。どちらが欠けていてもカスタムフォトウォッチは利用できません。 カスタムフォトウォッチをスマホにインストールすると、ペアリングしているAndroid Wear端末にも自動的にカスタムフォトウォッチがインストールされます。(この際、インストールにしばらく時間がかかります) Android Wear端末にもインストールが完了すると、スマホのAndroid Wearアプリを起動すればウォッチフェイスの中にカスタムフォトウォッチが追加されます。Android Wear端末でウォッチフェイスの変更からカスタムフォトウォッチを設定できますが、基本的な設定はスマホから行います。 カスタムフォトウォッチをウォッチフェイスに設定すると、カスタムフォトウォッチのアイコンに歯車マークが出ます。再度タップするとカスタムフォトウォッチの設定画面が起動します。 ウォッチフェイスの選択画面 カスタムフォトウォッチの設定画面を開くと、最初にこの画面が表示されます。 ここでは壁紙の追加と、既に追加した壁紙への切り替えが行えます。 右下の+ボタンを押すと次の画像の編集画面に移行します。 追加済みの画像を単にタップすると、ウォッチフェイスの設定(時刻の表示設定)に移行します。長押しするとポップアップメニューが開き、ウォッチフェイスの設定画面を経ることなく画像の切り替えのみを行ったり、不要になった画像の削除ができます。 画像の編集画面 この画面で実際にAndroid Wear端末に転送する画像を作成します。 左下のアイコンを押すことで、スマホ内に保存されている画像を読み込むことができます。SDカードやGoogleドライブにある画像を指定可能です。 カメラのアイコンを押せばその場で写真の撮影を行い、その画像を読み込むこともできます。 Android Wearに表示されるのは中心の明るい部分です。範囲外の部分は切り取られます。 画面内に表示した画像はスワイプすることで移動が、二本指でピンチイン・アウトすることによって縮小・拡大ができますので、Android Wearに表示させたい部分が中心になるように調整してください。 枠は四角と円形の切り替えが可能です。フレーム枠の変更は右上にあるアイコンをタップすることで行えます。(メニューからでも可) ちなみに枠はあくまで表示確認のためのものであり、枠を円形にしたとしても画像は四角で切り抜かれます。あくまで円形ディスプレイのAndroid Wear端末でどのように表示されるか確認するためのものだとお考えください。 枠の切り替えボタンの隣は、周りの半透明の部分を黒色で塗りつぶすボタンです。Android Wear端末に表示される部分が明確になるので、実際に表示されるとどう見えるのか確認したいときにお使いください。もう一度タップすれば元の半透明に戻ります。 表示位置が決まれば、右下のハサミのアイコンを押してください。画像が切り抜かれ、ウォッチフェイスの設定画面に移行します。 ウォッチフェイスの設定(時刻の表示設定) この画面ではAndroid Wearに表示される時刻や日付の表示設定が行えます。 変更できるのは文字の色、フォント、サイズです。日付に関しては表示形式も変更できます。 画像によっては色だけでは文字の識別が難しいと思います。そのような場合は縁取りを有効化してください。文字が黒で縁どりされるので視認性が向上します。 文字の設定は都度スマホの画面に反映されます。画像と文字の視認性のバランスをとりながら設定することができます。 文字の表示設定が決まればメニューのチェックマークを押せば設定が完了し、画像がAndroid Wear端末に転送されます。(端末のバックキーを押しても同様です) ただしアプリをインストールした直後やAndroid Wear端末を再起動した後など、スマホで選んだ画像がAndroid Wear端末に転送されないことがあります。その場合、1分程度間を開けてから再度画像の切り替えを行ってみてください。 Google Playにて公開中 カスタムフォトウォッチ

WatchFaceのサンプルを実行する

Android Wearでウォッチフェイスを自作するためには、Creating Watch Faces – Android Developersを見て勉強するといいです。 なんかややこしそうな気がするかもしれませんが、そこまで複雑でもないです。Traningにも書いてありますが、Android StudioでWatch Faceのサンプルを取り込んでやると、どういうことやればいいのかわかると思います。 取り込み方はAndroid StudioのFileメニューからImport Samplesを選び、Wearable > Watch Faceを選択すればOKです。 サンプルの実行方法 このサンプルを実行する場合、実行環境にバツ印がついています。これはデフォルトで起動するActivityが設定されていないからです。 そのまま実行しようとすると以下の様な警告メッセージが表示されますが、Continue Anywayを選べばAPKが端末へ転送されます。 毎回このメッセージが表示されるのはうっとおしいので、実行環境設定でDo not launch Activityを選んでおいた方がよいでしょう。 また、WatchFaceの設定画面を作るなど、デフォルトでは起動しないけど用意してあるActivityを起動したい場合は、「launch」を選んで起動させたいActivityを選んでおくと捗ると思います。(そうしないと、APKの転送→Android Wearアプリの起動→該当のWatchFaceを選択→設定画面を起動という手順を踏む必要があって面倒くさい) ただ、この手法で起動するとgetIntent().getStringExtra(WatchFaceCompanion.EXTRA_PEER_ID);で、ペアリングしている端末のPeerIdを取得しようとしてもnullになってしまうので、PeerId依存の処理が上手くいかないことに注意しましょう。 WatchFaceの動作確認 アプリ(WatchFace)をどうやって端末に転送するのかというと、mobile(サンプルではApplication)とwearモジュールを両方とも実行(デバッグ)してやります。 そうすればWatch Faceが端末に送信されるので、後はスマホもしくはWear端末からWatch Faceの変更をしてやれば動作確認できます。 デバッグ実行はwearとmobile片方ずつやらないといけない リリース用の署名をつけたAPKファイルであれば、mobile側の実行(端末へのインストール)さえ行えば、wear用のAPKがペアリングしている端末へ自動的にインストールされます。(mobile側のAPKの中にwear用のAPKが埋め込まれています) しかしdebug用のAPKはこの自動転送が働かないので、wear用のAPKはwear用のモジュールを選んで実行しないと端末にインストールされません。 開発にあたっては、スマホ側とWear側両方実行しないといけないので若干面倒くさいです。 サンプルで使われているLog.d()について サンプルでは様々なタイミングでLogにメッセージを流すようになっていますが、そのままではこれを確認することができません。 というのもLogを出力する前にisLoggableでログの出力レベルを設定を確認しているためです。 if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "onConnected: " + connectionHint); } これをLogcatで確認するためには、実行する端末に対してadb shell setprop log.tag.(TAGで指定されている文字列) DEBUGとターミナルから入力してやると、端末のLogcatにログが出力されるようになります。

Android Studioでソースコードを読むのに覚えておくと便利なショートカット

最近になってようやくサンプルなどのソースコードを読むようになったのですが、「あ、こんな便利な機能あったのね」というのを紹介します。 ショートカットキーはMacのものを書いてます。メニューにショートカットキーも表示してくれているので、Windowsの方はそこで調べてください。 Find Usages 調べたい変数やメソッドにカーソルを合わせてOpt+F7 Edit > Find > Find Usages 変数やメソッドがどこでどう使われているか調べるのに使います。今まで右クリック→Find Usagesで表示させてました。もしくはIdeaVim使っているので「/文字列」で検索してました。 メンバ変数(フィールド変数)だとValue readでどこで参照されているかが、Value writeでどこで変更されているかが表示されます。 メソッドだと、どこで呼ばれているかが表示されます。 プライベートフィールドに対して特に効果を発揮する機能です。 ちなみにFind Usagesで使われ方を表示した後は、Cmd+Shift+↓(↑)で次に出てくるところに飛んでくれます。さらにいうとCmdとOptを押し間違えると、表示させてる行の位置が入れ替わったりするので注意してくださいね。 戻る Cmd+[ Navigate > Back 日本語キーボードだと[ではなく@になります。 Find Usagesなどを使ってジャンプした場合に、ジャンプ前に読んでた場所に戻ってくれます。今まで「前読んでたところどこだ・・・」とイライラしながら探してました。 戻りすぎたらCmd+](日本語キーボードなら[)で1つ先へ飛びます。 この機能を使わずしてソースコードは読めない。 とりあえずこの2つの存在を知っているだけで格段にソースコードを読む辛さが緩和されます。 Declaration Cmd+b もしくはCmd押しながらクリック Navigate > Declaration 私はDeclarationの意味をよく分かってないのですが、宣言先に飛ぶってことなんだと思います。 親クラスで定義されてるメソッドを呼んでる時に、そのソースコードに飛ぶときに使ってます。例えば何気なく使ってるfindViewById()はいったい何やってんだって調べたりする感じです。Cmdキー押しながらクリックするだけなんで、ショートカットキー覚えてなくても行けそうな気はします。 Android Support Libraryのクラスだとソースコードまで付属してないのでそのままでは見れないんですけどね(見れないわけではないようです)。 Android標準のクラスならソースコードもインストールしてあれば確認することができます。Android SDK ManagerでSources for Android SDKをインストールしておく必要がありますが、開発する上ではないと困ると思うのでインストールしておいた方がいいでしょう。 Javadoc表示 Javadocを確認したいメソッド等にカーソルを合わせてF1(Shift+F1でブラウザで見れます) View > Quick Documentation 私の場合、読む時より書くときによくお世話になってる機能です。 知らずに使っていると損 ソースコードを読むのはものすごく勉強になるのに、今まで避けてきていたのはひとえに読むのが大変だったからでした。こういった便利な機能を知らなければ、そりゃ億劫になるのも当然と言えます。 使いこなしている人にとっては当然の知識かもしれませんが、初心者にとってはこんなこともわからないことなのです・・・。 調べてみるともっと便利な機能があるのかもしれません。「こんな便利な機能もあるよ」というのがあったら教えていただけると助かります。

Androidアプリを開発する上で賢いLogの出力方法(とその確認の仕方)

今までずっと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()でも構わないのでしょうが、リリースを視野に入れているアプリならこういった方法でログを出力するように設定しておくとユーザに優しいと思います。

MatrixのpostScaleで画像を拡大縮小させる

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を使った変形の概念があるんですね。数式だらけでサッパリ分かりませんが。 日本語で解説してあるサイトもありますが、いずれにしても奥が深そうで難解です・・・。

Intentを発行して画像を選択orカメラで撮影して、画像を表示させる

端末内に保存されている画像を表示したり、もしくはその場でカメラで撮影した画像を表示させる方法です。 例えば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.
Read full post gblog_arrow_right