TextAppearance のプレビュー画面を作る

AndroidのリソースXMLを読み書きできない人に TextAppearance の定義内容を共有するためには、わかりやすく定義内容を一覧できる仕組みが必要です。 できれば、以下のように実際のアプリ内の表示とあわせて見られるのが一番良さそうです。

f:id:nein37:20170328151636p:plain

このプレビュー画面を作るためには「TextAppearane の一覧を取得する」「TextAppearane の定義内容を取得する」という処理が必要になるので、それぞれ方法を紹介します。

TextAppearane の一覧を取得する

TextAppearance や style は R.style. 以下に id が定数として定義されているので、これを利用してリフレクションで一覧を作ります。

今回は AppCompat の TextAppearance を対象にするので、TextAppearance_ の prefix を持つ style を対象にしました。

final List<Field> textAppearanceList = new ArrayList<>();
for (Field field : R.style.class.getFields()) {
    String fieldName = field.getName();
    if (fieldName.startsWith("TextAppearance_")) {
        textAppearanceList.add(field);
    }
}

これで R.style.TextAppearance_* の定数のリストができました。 ここで取得したリストではもともとxml に定義していた名前の ._ に変換されているので注意しましょう。

idの値は以下のどちらかの方法で取り出すことができます。

Field から直接値を取り出す

int styleId = 0;
try {
    styleId = field.getInt(null);
} catch (IllegalAccessException e) {
}

もとの名前から id を検索する

String styleName = field.getName().replace("_", ".");
int styleId = context.getResources().getIdentifier(styleName, "style", context.getContext().getPackageName());

TextAppearance の定義内容を知る

id がわかれば style の定義内容を取得することができます。

TypedArray typedArray = context.getTheme().obtainStyledAttributes(styleId, R.styleable.TextAppearance);

あとは TypedArray から値を取得するだけ… と思っていたら、TextView などが参照している com.android.internal.R.styleable.TextAppearance_textColor は外からアクセスできず、カスタムViewでもないので package.R.styleable にもなくてどうしよう、という感じでした。

悩んだ末、今回は作っていたのが preview ツールだったので TextViewに一度適用して TextView の setter から各デザイン情報を得るという乱暴な方法で実現しました。 TypedArray から直接属性を取る方法知りたい…

// 新しいAPIだが TextViewCompat 経由で setTextAppearance を呼べる
TextViewCompat.setTextAppearance(textView, styleId);

// textColor は ColorStateList だったりするが getCurrentTextColor が初期表示の色を取るのに便利
int textColor = (0xFFFFFFFF & textView.getCurrentTextColor());

// textSize は pixel で取れるので sp に変換する
// style定義時の値が dp か sp かを実行時に知る方法はない
float textSize = textView.getTextSize();
int textSizeSp = Math.round(textSize / context.getResources().getDisplayMetrics().scaledDensity);

// 太字/イタリック
boolean isBold = false;
boolean isItalic = false;
if (textView.getTypeface() != null) {
    isBold = textView,getTypeface().isBold();
    isItalic = textView.getTypeface().isItalic();
}

// ALLCAPS(後述)
boolean isAllCaps = false;
if (textView.getTransformationMethod() != null) {
    if (TextUtils.equals(textView.getTransformationMethod().getClass().getSimpleName(), "AllCapsTransformationMethod")) {
        isAllCaps = true;
    }
}

ALLCAPS 属性は set した瞬間に AllCapsTransformationMethod を TransformationMethod に set という乱暴な処理で扱われていて、isAllCaps() のような getter が存在しません。 しかも互換性のために AllCapsTransformationMethod が2種類(SDKとsupport)用意されていて、両方 @hide なので instanceof させることもできませんでした。 仕方がないのでクラス名だけの比較にしていますが、かなり厳しいですね…。

まとめ

かなり無理やりな処理ですが、一応なんとかなったのでサンプルとして公開しました。 アプリ内で TextAppearance に共通する prefix や suffix が決められていれば同じような処理で TextAppearance のプレビュー画面が作れると思います。

github.com

Androidソースコードレビューで指摘する事が多い項目まとめ2

去年Androidソースコードレビューで指摘する事が多い項目まとめという記事を書いた時はアプリ全体を一度に見るような機会が多かったため、内容も大きめのものばかり書いていましたが、最近はプルリクエスト単位でレビューする機会が増えたので細かい項目についてまとめてみようと思います。

ミリ秒で時間を指定する時に自前で計算している

1000L * 60L * 60L * 24Lのようなコード。

TimeUnitを使いましょう。

24時間の場合は以下のように書けます。

TimeUnit.DAYS.toMillis(1L)

ある文字列がhttp/httpsで始まるかチェック

URLUtil.isNetworkUrl()を使いましょう。

ただしequalsIgnoreCaseで判定してます。

ベースURLにパラメータを付与していってURLを生成したい

StringBuilder#append("&key=").append(value);のようなコードをいっぱい書いているようなところ。

Uri.Builderを使って以下のように書けます。

Uri.Builder#appendQueryParameter("key", value);

?&=を意識しなくてよくなり見やすくなります。

LayoutInflater#inflate()のrootがnull

LayoutInflater#inflate()rootを指定しないと適当なLayoutParamsを用意されて見た目が変わる可能性があるので指定しましょう。

nullにすると警告が出るのですぐわかると思います。

ListViewのHeader/FooterのLayoutInflater#inflate()

Header/FooterはItemのViewと同じようにHeaderViewListAdapter#getViewで返されてListViewに追加されるのでListViewをrootとして渡せばOKです。

Viewの間隔を調整するために透明なViewを使っている

できるだけmarginやpaddingで調整しましょう。

どうしてもViewを置いて調整したいときはSpaceを使いましょう。 このViewは描画処理で何もしないので背景色などが指定出来ない代わりに低負荷です。

ListViewの上下端の要素にだけmarginを入れている

大抵はListViewにpaddingTop/paddingBottomを設定してclipToPaddingをfalseにすると解決します。

文字列のベタ書き

文字列リソースにできるものは文字列リソースにしましょう。

ただし、以下に当てはまるものは文字列リソースに向かないのでベタ書きで良いと思っています。

  • ローカライズしてはいけない文字列
    • ログや成果計上に使う文字列など
  • レイアウトファイルのtools:textなどアプリ内では使われない文字列
  • private static final Stringな文字列

最近Android TVアプリの開発で困ったこと

雑にメモ

Android TVを正確に判定するのが難しい

Android DevelopersのAndroid TV実装ガイドにはTV端末をチェックするという項目があり、 UI_MODE_TYPE_TELEVISIONで判定すればいいよと書いてある。 しかし実際にはUI_MODE_TYPE_TELEVISIONなのにAndroid TVではない端末いくつかあり、自称AndroidTV端末としてチェックをすり抜けてしまう。 特にOSが4.x系でUI_MODE_TYPE_TELEVISIONな端末が曲者で、Android TVだけを想定してコードを書いていると4.x系では存在しないAPIにアクセスしてクラッシュすることがあって非常に辛い。 手元にないので試せていないが、おそらくHDMIスティック系の端末にも自称TV端末があるのではなかろうか。 TV判定のロジックにAPIレベルのチェックを追加するか、TV向けの部分であってもminSdkVersionで動くようにしておいたほうが良さそう。

結局、Android TVってUI_MODE_TYPE_TELEVISIONができたAPI level 13からサポート対象なのか、leanbackライブラリのv17からサポート対象なのか、Android TVそのものができたLollipopからサポート対象なのかよくわからない。

音声検索のマイク権限は外せる

AndroidTVで検索画面を実装する場合、通常はleanbackライブラリのSearchFragment/SearchSupportFragmentを利用する。 このクラスは普通に実装するとRECORD_AUDIO権限が必要だが、SpeechRecognitionCallbackを設定して音声検索をACTION_RECOGNIZE_SPEECHに任せれば権限不要となる。 Android Developers Blogにも書いてあったので多分間違いない。

TVでRuntime permissionsに対応するのはしんどいので、どんどん権限を外していきたい。

Leanbackライブラリの更新で死ぬ

leanback-v17ライブラリを23系にあげたときに、HorizontalGridViewlayout_heightwrap_contentにしているとjava.lang.IllegalStateException: Must specify rowHeight or view heightみたいなエラーを吐いてクラッシュする。 おそらくVerticalGridViewlayout_widthでも似たようなエラーが発生してクラッシュしそう。 メッセージの通り高さ/幅を指定すればクラッシュしなくなる。

最近気になっているGoogle Play Servicesの機能

Google Play Services7.5、7.8で素敵な新機能がいっぱい追加されたけど全然追いつけていないのでメモだけ…。

Nearby

Nearbyは近くにあるAndroid/iOS端末同士でやりとりすることができる機能です。 メールやSMS、QRコードなどを介することなく接続できるので、アカウントを知る必要も別にアプリを入れる必要もなくやりとりすることができます。

Trelloというタスク管理アプリではNearbyを使って簡単にボード(タスクリストのようなもの)が共有できるようになってました。

以下はTrelloでNearby接続を開始した時に表示される確認ダイアログです。 耳に聞こえないペア設定コードと表現されていますが、端末によってはわりと大きめの音でジジジジ…と鳴り不快でした…。

f:id:nein37:20150901150332p:plain:w300

Smart Lock for Passwords on Android

Smart Lock for PasswordsはChromeでWebサイトにログインしたときにIDやパスワードを保存し、再ログイン時に入力補完してくれる機能です。 Google Play Services 7.5からAndroidアプリでも同じ機能が使えるようになりました

Smart Lock(略)を使ってアプリでのログイン時に認証情報をGoogleサーバーに保存しておくことで、次回のログイン時に自動ログインや入力補完させることができます。 また、Chrome側の認証情報管理と同じ仕組みで動いているため、アプリとWebサービスの紐付けをしておけばどちらか一方で保存した認証情報を他方で利用することもできるようになります。

NetflixAndroidアプリが完全対応していて、前もってWebで認証情報を保存しておくとアプリをインストールして起動した直後に自動的にログインという最高のユーザー体験!という感じでした。

サンプルアプリで認証情報を保存するときの確認ダイアログ

f:id:nein37:20150901151526p:plain:w300

認証情報を取得したときの表示(下部の青い帯。右端のgマークは消せない…)

f:id:nein37:20150901151550p:plain:w300

App Invites(Beta)

App Invitesはメールアプリなどを経由せずに直接アプリの中で送信先を選んで招待メッセージを送信することができる機能です。 招待メッセージを開くとアプリの説明とInstallボタンがあり、このボタンを押下すると対象アプリを未インストールの場合はPlayストア( iTunes App Store)に遷移、インストール済みの場合は対象アプリが起動します。 招待メッセージにディープリンクを埋め込むことができるので、「指定したページでアプリを起動」という挙動もできます。

Yummlyという海外のレシピアプリが導入していたので、動作確認しました。

レシピ詳細で共有先から「Share via Yummly」を選ぶとメッセージの送信先選択画面が表示されます。 タイトル部分は編集可能、複数の宛先が選択可能なようでした。

f:id:nein37:20150901150432p:plain

そのまま送信すると、相手にはこんな感じでアプリの紹介メッセージが届きます。 「Install」を押すとPlayストアへ遷移、またはアプリが起動して共有されたレシピを表示します。

f:id:nein37:20150901150439p:plain

Genymotionの有料アカウントについて

Genymotionについて

Genymotionは高速なAndroidエミュレータです。 基本的な機能についてはググればいっぱい出てくるので割愛します…。

有料アカウント

GenymotionのHPに無料アカウントと有料アカウントで使える機能の違いは書いてあるのですが、具体的にどのような機能なのか詳細な説明がありません。 調べてもわからなかったので有料アカウントに契約しました。

課金

通常の課金携帯はBusinessアカウント(€24.99 per month, per user)となっていますが、個人で契約する場合はIndieアカウント(€8.25 per month, per user)が使えます。 月€8.25ならいいかな、と思ってたら年契約しかないので€99取られます。気をつけましょう。 支払いはクレジットカードの他、Paypalが利用できました。

有料機能

Multi-touch

マウスとキーボードを使ってマルチタッチをエミュレートすることができます。 操作方法はキーボードショートカットの下の方にあります。 Macbookのトラックパッドでマルチタッチした結果がエミュレータに伝わるかと思っていたのですが、そんなことはありませんでした。このマルチタッチ操作はトラックパッドでは非常にやりづらいです…。

Accelerometer

加速度センサーが使えるという意味だと思うのですが、単体で設定できる画面が見当たりません。 いまのところ後述のRemote Control以外で利用する方法が不明です。

Java API

Androidアプリ内からエミュレータのセンサー値などを変更できるライブラリが利用できます。 テストコードなどを実行するときに便利そうですね。 ただし、ライブラリは1.0.0からアップデートされておらずjavadocを見てもあまり多機能とはいえない感じ。

Screencast

スクリーンショットが撮れる。動画も撮れます。それだけ。 動画のフォーマットはwebp。画質の調整とかはできなさそうです。 ちなみに無料アカウントでもDDMS経由でスクリーンショットは撮影できます。多分動画も撮れます。 DDMS経由で録画ができるようになったのは4.4からですが、この機能は2.3でも利用することができます。

f:id:nein37:20150128201512p:plain

Virtual device migration

この機能は一体なんだ?全然説明がないぞ? と思っていたらユーザーガイド(PDF)に書いてありました。 デバイスイメージが更新されたときにデータそのままアップデートできるらしいです。 データそのままでOSのバージョンが変えられる機能ではありません

IMEI and Android ID update

エミュレータの各種IDが変更できます。 すでにアプリでの利用が非推奨になっているので、あまり使い道がなさそう。

f:id:nein37:20150128201514p:plain

Pixel Perfect

通常、GenymotionはPCの画面サイズに応じてエミュレータを縮小表示していますが、この機能を使うと本来のピクセルに拡大して表示することができます。

通常表示

f:id:nein37:20150128201520p:plain

Pixel Perfect

f:id:nein37:20150128201518p:plain

Clone and Reset

デバイスイメージのクローンとリセットができます。 どちらも起動中はできないのが残念。

Network quality and performance emulation

エミュレータの通信速度と品質を設定できます。 Profileには3Gや4Gなどがありますが、どれを選んでもエミュレータ上ではWifi接続扱いなので注意。

f:id:nein37:20150128201522p:plain

Priority support (reply guaranteed within 1 working day)

問い合わせすると1営業日で返事くれるらしいです。 いまのところ利用してないので詳細不明。

Remote Control

なぜかPriceページに載っていない重要機能です。 実機のタッチパネルやセンサーの状態をエミュレータに反映することができます。 実機があるのにGenymotion使う意味あるのか?と思うかもしれませんが、OSバージョンや画面サイズの異なるエミュレータでも操作できるのはすばらしい!…かもしれない。

設定画面

f:id:nein37:20150128201525p:plain

操作中の実機

f:id:nein37:20150128201505p:plain

これはNexus5(実機)でNexus6のエミュレータを操作したものです。 実機のナビゲーションバーが表示されるため、同一機種のエミュレータであっても画面いっぱいには表示されません。

f:id:nein37:20150128201509p:plain

これはNexus5(実機)でXperia Tablet Zのエミュレータを操作したものです。 スマートフォン端末でもタブレットをRemote Controlすることができます。

Remote Controlで接続された状態ではタッチのほかマルチタッチも可能。 加速度センサーの値も実機のものになります。 GPSやカメラは残念ながら実機から共有できませんでした。

Remote Controlの操作は結構重いらしく、GalaxyNexus(実機)でRemote Controlしようとすると画面表示がカクカクになります。 またPreviewが真っ白になってしまう場合があり、こうなると一度切断して再接続しない限り表示されませんでした。

ちなみに、Remote Controlを初めて実行する際エミュレータの再起動が必要でした。

感想

年€99はちょっと割高かも…

端末情報を調べるときに見るページ

au

auの開発者向けページは他キャリアの同様のページとくらべて非常にわかりやすい。 他がひどいのでわかりやすいというだけで高評価だ。

Android(TM) 技術情報 | 開発者向け技術情報 | au

端末スペックは細かく乗っていて一覧性が高い。 更新スピードも早くこの間発表された2015春モデルもすでに記載されている。 特にAndroidで重要な端末のdp設定(xhdpi,xxhdpi...)なども載っていて有用。

ディスプレイ | Android(TM) 技術情報 | au

docomo

docomoの開発者向けページは端末そのものの情報よりサービス推しが目立つ。

作ろうスマートフォンコンテンツ | サービス・機能 | NTTドコモ

端末情報は入り口が見つけにくい上に一度入ったら戻るためのリンクがない。 また、一覧表がない上に情報が機能別にページわけされていて階層が深いすぎる。 ピクセル密度という項目で実dpiは記載されているが、端末が使うdp設定は記載されていないのでdpの設定から推察する必要がある。

NTTドコモ 端末・ブラウザスペック

softbank

softbankの開発者ページにはコラムも連載されている。 ただしコンテンツ量はあまり多くなくページが細かく区切られていて読みづらい…。

Android[TM] 技術仕様情報 | SoftBank スマートフォン サービス開発支援サイト | ソフトバンク

端末情報は検索して一覧表にする形式だが、そもそもの情報量が少ない。更新も遅い。 wifiなどの機能は○/☓形式なのにグレーアウトされている機種もあったりして色々ひどい。 ディスプレイに至ってはdp設定どころか実dpiすら表示されないので自分で計算しなくてはいけない。

端末情報 | SoftBank スマートフォン サービス開発支援サイト | ソフトバンク

Android Develoeprsの歩き方

はじめに

この記事はAndroid Developersを便利に使うための方法が書いてあります。 前提としてAndroid Developersのページ構成などをある程度理解している必要があるので、まずは下記記事を参考にAndroid Developersの構造を把握してください。

Android Developersでよく読むページ

拡張機能の導入

Chromeを利用している場合、Android SDK Searchを導入することでAndroid Developersの使い勝手がかなり上がります。

主な機能

  • URL入力欄にad *keyword*と入れることでAndroid Developersを検索してくれる
  • Android Developersのクラスリファレンスページにソースコードへのリンクを表示してくれる

特に後者はとても強力な機能です。 UserManager#isUserAGoat()ってどうやってヤギかどうかチェックしてるんだっけ?と思った時にすぐ実装を見ることができます。

Blogを読む

Android Developers Blogを購読しましょう。 このブログは単なる技術系ブログではなく、Android Developersからも検索対象になっているコンテンツのひとつです。 一部の記事はGoogle Developer Japan Blogで翻訳されていますが、このブログはAndroidに限定されたものではなくAndroid Developersの検索にも引っかからないため、なるべく本家の更新をチェックするようにしましょう。

特定のタイミングで更新されるページ

Dashboardsは毎月OSバージョンや画面サイズごとのシェアが報告されるページです。 このページは毎月一度はチェックしたほうが良いでしょう。 OSのバージョンアップが行われた場合、主に以下のページが変更されます。

API levelごとの差分を見る

API Differencesで確認することができます。 URLの数字を変更することで過去の差分もみていくことができます。 引数などが変わらず、挙動のみが変わってしまうメソッドはこの方法ではわからないため、検索を活用することで探します。 あまり効率はよくありませんが、As of LOLLIPOPで検索し、左側メニューでReferencesを選択することでjavadoc内のLOLLIPOPでの動作について言及している箇所を探すことができます。 As of KITKATなども同様に探せます。

Supportライブラリにバックポートされた機能を探す

Supportライブラリの主な機能はSupport Library Featuresにまとめられています。過去にこのページをみており、新規差分だけ把握したい場合はRevisionsを参照すると早いです。 ただし、これらのページには主な機能しか記述されていないので、他のバックポートされた機能探すために検索機能を活用します。Compatで検索し、左側メニューからReferenceを検索することでjavadoc内の文字列検索になります。Support Library Featuresには記載されていないLinearLayoutCompatなどもこの方法で検索できますが、ノイズが多いため効率はあまりよくありません。

このような機能は以下のパッケージに含まれていることが多いため、パッケージ単位のReferenceでクラス一覧を眺めてみるのも良いでしょう。

早見表

アイコンサイズや代替リソースの設定の一覧が書かれているページは覚えておくと便利です。