Gradle plugin を Plugin Portal で公開する (2016 年 2 月版)
背景 : Gradle プラグインの開発について
独自の Gradle プラグインの書き方は次のページに書かれています。
ここには公開の方法も書かれていて、次のように述べられています。
If you are publishing your plugin internally for use within your organization, you can publish it like any other code artifact. See the ivy and maven chapters on publishing artifacts.
If you are interested in publishing your plugin to be used by the wider Gradle community, you can publish it to the Gradle plugin portal. This site provides the ability to search for and gather information about plugins contributed by the Gradle community. See the instructions here on how to make your plugin available on this site.
39.5.2. Publishing your plugin (Chapter 39. Writing Custom Plugins)
広く使われうるプラグインを公開する場合には Gradle plugin portal が使えるようです。
Gradle Plugin Portal とは
「Introducing The New Gradle Plugin Portal l Gradle」 によると、Plugin Portal は 2014 年 6 月ごろに公開されたようですね。 最初は Gradle plugin の検索のインターフェイスを提供するものだったようで、Maven リポジトリとしては Bintray が使われてたようです。
その当時の、プラグインを Plugin Portal で公開する方法は下記ページに書かれています。
現在では Gradle Plugin Portal 自身が Maven リポジトリも持つようになっていて、Gradle Plugin を Gradle Plugin Portal にアップロードすることで公開することができます。
Gradle Plugin Portal で公開されると何が嬉しいか?
まずひとつは、Gradle Plugin Portal で検索できること。
あとは、build.gradle でプラグインを適用する際に以下のように書けること。 (Gradle 2.1 以降。)
plugins { id 'com.gradle.plugin-publish' version '0.9.3' }
Gradle Plugin Portal で公開されてないと以下のように書く必要があります。
buildscript { repositories { maven { url "https://plugins.gradle.org/m2/" } } dependencies { classpath "com.gradle.publish:plugin-publish-plugin:0.9.3" } }
他にもあるかもしれませんがとりあえずはそれぐらいでしょうか。
Gradle Plugin Portal への公開 (2016 年 2 月版)
公開方法は次のドキュメントに書かれています。
書かれているそのままですが、流れを紹介すると、
- Plugin Portal のアカウントを作成して、
- API key を生成して、
- API key を $HOME/.gradle/gradle.properties に書いて、
- Plugin Publishing Plugin を適用して build.gradle を書いて、
./gradlew publishPlugins
でアップロード。
という感じですね。
普通にやれば普通にできるのですが、いくつかややこしいことがあるので書いておきます。
- Gradle plugin ID は名前空間分けしたものにする必要がある。
- Java のパッケージのようにドメインの逆順みたいなのが推奨されてるぽい。 (
info.vividcode.sugoi-plugin
みたいな感じ。) - 「Chapter 39. Writing Custom Plugins」 の 「39.5.1. Creating a plugin id」 参照
- Java のパッケージのようにドメインの逆順みたいなのが推奨されてるぽい。 (
- 特に何も指定しなければ Artifact の group id の前には
gradle.plugin.
が追加される。- 上書きすることもできて、過去に Bintray にデプロイされたプラグインがあって、それの group id と artifact id を保ちたい場合は上書きする必要がある。
- group id を上書きした場合は、セキュリティ上の理由で Gradle チームが手動で受け付けることになるので、group id を上書きしないことが推奨されている。
- 「Gradle - How do I use the Plugin Publishing Plugin?」 の 「Full Example」 のコメント参照。
以上です!
Android の Drawable への着色について (Drawable tinting)
Android APIs を眺めていると “tint” って単語が良く出てきます (“android:backgroundTint” とか “android:progressTint” とか) が、tint についてあんまり日本語の情報がなかったのでまとめてみます。
ちなみに “tint” の英語としての意味は、名詞で 「色合い」 とか 「染料」、動詞だと 「色を付ける」 や 「染める」 などです。
このエントリの内容
Drawable への着色 (Drawable tinting)
API level 21 から、Drawable リソースをアルファマスクとして定義し、その Drawable に色を付けて表示する、ということができるようになりました。 同じ形で異なる色のアイコンを複数個用意したいというような場面で便利ですね。
Android 5.0(API レベル 21)以降では、アルファ マスクとして定義されたビットマップや 9-patch に色を付けることができます。 着色には、カラーリソースまたはカラーリソースに変換されるテーマ属性を使用できます(
ドローアブルの使用 | Android Developers?android:attr/colorPrimary
など)。
bitmapあるいはnine-patcheをアルファマスクとして定義しcolorリソースやテーマの属性で色づけする
L Developer Preview マテリアルデザイン - アニメーション - Firespeed
Drawable
クラスに、下記のメソッドが追加されています。
Drawable#setTint(int)
メソッドDrawable#setTintList(ColorStateList)
メソッドDrawable#setTintMode(PoterDuff.Mode)
メソッド
これらを使うことで、Drawable に着色することができます。
// 通常通り Drawable オブジェクトを取得し、#setTint メソッドや #setTintMode メソッドを呼ぶことで着色。 Drawable d = getDrawable(R.drawable.example); d.setTint(ContextCompat.getColor(this, R.color.exampleTint)); d.setTintMode(PorterDuff.Mode.SRC_IN); // 後はこの Drawable オブジェクトを使う。 imageView.setDrawable(d); // 例えば ImageView で表示する。
ちなみに、この機能が導入される前から、同様のことが Drawable#setColorFilter
メソッド で実現できていたようです。 setColorFilter
を呼んだ場合は、tint の設定は上書きされます。
Tint mode について
Drawable#setTintMode(PoterDuff.Mode)
メソッドでは、元の Drawable と Tint の色をどのように混ぜるかを Porter-Duff のルールで指定します。 元の Drawable が destination で、着色する色が source です。
デフォルトでは Source In Destination 規則 (PorterDuff.Mode.SRC_IN
) が使用されます。 これは、単純に tint が不透明な単色の場合、合成後の各ピクセルの不透明度は元の Drawable の不透明度になり、合成後の各ピクセルの色は tint の色になるというものです。 まさに Drawable がアルファマスクになるわけです。
Porter-Duff のルールについては以前このブログに書きました。 下記エントリを参照してください。
Support library による Drawable への着色のサポート
API level 21 から導入された Drawable への着色の機能ですが、v4 Support Library に含まれる DrawableCompat
クラスを使うことで、古い API level でも同様の機能を実現できます。
Drawable d = DrawableCompat.wrap(ContextCompat.getDrawable(this, R.drawable.example)); DrawableCompat.setTint(d, ContextCompat.getColor(this, R.color.exampleTint)); DrawableCompat.setTintMode(d, PorterDuff.Mode.SRC_IN);
対象の Drawable を DrawableCompat#wrap
メソッドに渡して tint 対応の Drawable に変換し、それを DrawableCompat#setTint
メソッドなどに渡す、という使い方になります。
Drawable への着色の応用
実際に Drawable への着色をしたい場面というのは、例えば View の背景に Drawable をセットしてその Drawable に着色するだとか、ImageView
で表示する Drawable に着色するだとか、あるいは ProgressBar
の (プログレスバーそのものを表す) Drawable に着色するだとか、そういう場面が多いでしょう。 そのような場面で簡単に Drawable への着色が行えるように、Java クラスのメソッドやレイアウトの XML で使える属性などが用意されています。
各 view の背景の Drawable への着色 (Background tint)
API level 21 で導入された下記のメソッドや XML 属性を使用して、簡単に背景の Drawable に着色できます。
View#setBackgroundTintList(ColorStateList)
メソッド- 対応するレイアウト XML の属性は
android:backgroundTint
- 対応するレイアウト XML の属性は
View#setBackgroundTintMode(PorterDuff.Mode)
メソッド- 対応するレイアウト XML の属性は
android:backgroundTintMode
- 対応するレイアウト XML の属性は
API level 21 未満の環境でこれらの機能が使えるように、v7 appcompat library が各種 compatible widget を提供しています。 (ただし、対応されている View のクラスは一部のみ。) よって、v7 appcompat library を使うことで、以下のようにして API level 21 未満の環境でも background tint を使用できます。
<!-- v7 appcompat library を使用していると、この TextView はインフレート時に AppCompatTextView に変換される。 --> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:background="@drawable/example_text_view_background" app:backgroundTint="@color/exampleTextViewBackgroundTint"/>
ただ、上のような書き方をすると Android Studio でエラーが表示されるのが悩ましいところです。 (AppCompatTextView
への変換まで認識してくれないため。)
v7 appcompat library の compatible widget については以前エントリを書きましたので、詳しくは下記エントリを参照してください。
その他の Drawable の着色
ImageView
には、表示する Drawable に着色するためのメソッドが存在します。
ImageView#setImageTintList(ColorStateList)
メソッド- 対応するレイアウト XML の属性は
android:tint
- 対応するレイアウト XML の属性は
ImageView#setImageTintMode(PorterDuff.Mode)
メソッド- 対応するレイアウト XML の属性は
android:tintMode
- 対応するレイアウト XML の属性は
ProgressBar
でも Drawable への着色が可能です。
ProgressBar#setProgressTint(ColorStateList)
メソッド- 対応するレイアウト XML の属性は
android:progressTint
- 対応するレイアウト XML の属性は
ProgressBar#setProgressTintMode(PorterDuff.Mode)
メソッド- 対応するレイアウト XML の属性は
android:progressTintMode
- 対応するレイアウト XML の属性は
- など、各 Drawable に対応したメソッド (およびそれに対応する XML の属性) が定義されている
調べられていませんが、Drawable を扱う他の View クラスにも似たような機能があるかもしれません。
API level 21 未満でこのような機能を使えるようにする互換クラスは (今のところ) Android Support Library には含まれていないようですので、API level 21 未満では直接 Drawable に着色したうえで ImageView
にセットしたり ProgressBar
にセットしたりする必要がありそうです。
まとめ
- API level 21 から Drawable への色付けがサポートされました。 (Drawable tinting)
- 基本的には、アルファマスクとして Drawable を定義し、表示時に色を指定するという使い方が想定されています。
- View の背景の Drawable への着色や、
ProgressBar
やImageView
などで表示される Drawable への着色も簡単に行えるようになっています。 - Android Support Library を使うことで、Drawable への着色の機能や View の背景の Drawable への着色が API level 21 未満でも利用できます。
関連ページ
- 【Android】Support LibraryのVectorDrawableCompatが適応されるまでの仕組み - Qiita
- Drawable への着色ではなく Vector Drawable のサポートについてですが、内部でどういう処理がされているのか説明されています。 このエントリの内容とかぶっている部分もありますのであわせて読むと良さそうです。
Android の Java で時刻を扱う (Date、Calendar、DateFormat クラス)
Java エンジニアの皆様は Java SE 8 で導入された Date-Time パッケージ (Date and Time API; JSR 310) を便利に使っていることと思います。 残念ながら Android プラットフォームにはそれらの API がありませんので、Android アプリ開発時に時刻を扱う場合は、古くからある API を使用することになります。
この記事では、Android アプリ開発時にお世話になる Date
クラス、Calendar
クラス、DateFormat
クラスについて、それぞれの役割や使い方、気を付けるべきことをまとめます。
追記
(2020-06-03) Android での Date-Time パッケージ
Android の API Level 26 からは java.time
パッケージが使えるようになっています。
Android 8.0 (API level 26) adds support for several additional OpenJDK Java APIs:
Android 8.0 Features and APIs | Android Developers
java.time
from OpenJDK 8.java.nio.file
andjava.lang.invoke
from OpenJDK 7.
また、Android Gradle Plugin 4 からは、Java 8+ API desugaring support により、API Level 26 未満のプラットフォームでも java.time
パッケージを使用するアプリをビルドできるようになったようです。
If you're building your app using Android Gradle plugin 4.0.0 or higher, the plugin extends support for using a number of Java 8 language APIs without requiring a minimum API level for your app.
(略)
The following set of APIs are supported when building your app using Android Gradle plugin 4.0.0 or higher:Use Java 8 language features and APIs | Android Developers
- (略)
- A subset of
java.time
(2016-02-18) JSR 310 の Android バックポート
JSR 310 の Android バックポートライブラリの存在を知りました。 Android ではこれを使うのがいいかもしれないですね。
- GitHub - JakeWharton/ThreeTenABP: An adaptation of the JSR-310 backport for Android.
- ThreeTenABP と ThreeTenBP の関係について (Android における JSR-310 バックポート) - ひだまりソケットは壊れない : ThreeTenABP がどういうライブラリなのかという説明を書いたので、こちらもあわせてご参照ください。
ところで Time
クラスは?
Android APIs には Time
クラスってやつが含まれています。 API level 22 で非推奨 (deprecated) になったのでそっとしておきましょう。 (これまでありがとうございました。)
気を付けるべきことまとめ
記事が長いので、気を付けるべきことを最初に書いておきます。
Date
オブジェクト自体はタイムゾーンを持ちません。DateFormat
を使って Web API などから取得した時刻の文字列を解析する際は、必要であれば適切なタイムゾーンを設定しましょう。 (解析対象の文字列にタイムゾーンが含まれているのであれば必要ない。) ロケールはLocale.US
が良さそうです。- Android *2 の
SimpleDateFormat
は、ISO 8601 や RFC 822 で定義されていて実際にしばしば使われる 「Z」 というタイムゾーン表記を解釈できない (Java SE 7 ではX
というフォーマット文字が導入されて解釈できるようになった) ので、気を付けましょう。 - ユーザーに表示するための時刻の文字列を生成する際は、
andorid.text.format.DateFormat
オブジェクトを使うことで端末の設定を反映させると良いでしょう。- あるいは
android.text.format.DateUtils
も便利に使えるでしょう。
- あるいは
Date
、Calendar
、DateFormat
、それぞれの役割
簡単にまとめると、Date
クラスが 「時間軸上の特定の瞬間」 を表すもので、Calendar
クラスが年月日や時、分、秒といった情報を扱うクラス、そして DateFormat
が時刻を表す文字列と 「時間軸上の特定の瞬間」 の相互変換を行う、という感じです。 詳細は以下に述べます。
Date
クラス
Date
クラスは、時間軸上の特定の瞬間をミリ秒の精度で表現するクラスです。
純粋に 「時間軸上のある瞬間」 を表現するためのものなので、タイムゾーンを持ちませんし、自身が表現する時刻が何月何日の何時何分なのかを計算したりもしません。 時刻を表現する文字列をパースしたり、逆に時刻を表現する文字列を出力したりもしません。 (歴史的経緯によりそのような機能のメソッドが Date
クラスに定義されていますが、非推奨です。)
Calendar
クラス
Calendar
クラスは、次の 2 つの機能を提供するクラスです。
- 「時間軸上の特定の瞬間」 と 「何月なのかや何日なのか、何時なのかといった数値情報・カレンダー情報」 の相互変換
- 何日なのかや何時なのかといった数値情報・カレンダー情報に対する計算。 (例えば、1 週間後の日時を取得する、など。)
Calendar
オブジェクトで時刻を表現させようと思えば実際上は可能ではありますが、そういう使い方には Date
オブジェクトを用いるべきみたいですね。 年月日などを扱うので、このクラスを使う際は当然 タイムゾーンを気にする必要があります。
GregorianCalendar
クラス
Calendar
クラスは抽象クラスで、変換や計算の処理は各種サブクラスが提供することになっています。 世界のほとんどの地域で使用される標準的な暦体系に対する実装として、GregorianCalendar
が提供されています。
DateFormat
クラス (java.text
パッケージ)
「時間軸上の特定の瞬間」 を表す値から日付・時刻を表す文字列を生成したり、逆にそれらの文字列を解析する機能を持つクラスです。
年月日などを扱うので、このクラスを使う際は当然 タイムゾーンを気にする必要があります。
SimpleDateFormat
クラス
DateFormat
のサブクラスです。 自分で日付・時刻の文字列のフォーマットを指定する場合はこのクラスを使用することになります。
DateFormat
クラス (android.text.format
パッケージ)
めちゃくちゃややこしいのですが、Android APIs には android.text.format.DateFormat
というクラスも含まれています。 このクラス自身もフォーマット機能を持っていますし、java.text.DateFormat
オブジェクトを返すファクトリメソッドも持っています。
API level 23 の実装だと、java.text.Date
クラスの getDateInstance
メソッドや getTimeInstance
メソッドも端末の設定を見てくれるようですが、API level 10 では android.text.format.DateFormat
の getDateFormat
メソッドなどを使わないと端末の設定が反映されないようです。 (どこで変更されたかは調べてない。)
なので、端末の設定を反映させて日時や時刻を表示したい場合は、android.text.format.DateFormat
クラスを使ってフォーマットを決める必要があるようです。
使い方
各種ユースケースにおいて、上で紹介したクラスをどのように使うかを簡単に紹介します。
Web API でやり取りする時刻の文字列と Date
オブジェクトを相互変換する
Web API のレスポンスで "2016-01-01 00:00:00+0900" みたいな時刻を表す文字列を受け取ることがあると思いますが、そういった文字列を Date
オブジェクトに変換するにはフォーマットを指定して SimpleDateFormat
オブジェクトを作成しましょう。
java.text.DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ssZ", Locale.US); // タイムゾーンが含まれていないような日付文字列を解析する場合は、適したタイムゾーンを指定しましょう。 // df.setTimeZone(TimeZone.getTimeZone("Asia/Tokyo")); Date d = df.parse("2015-01-01 00:00:00+0900");
ロケールはタイムゾーンの処理にも関わってるぽいです (まじか……) し、Locale.US
にしておくのが良さそうです。 例えば、パターン Z
を使用してタイムゾーンを解析する場合、Locale.US
ならば 「EDT」 を処理できますが、Locale.ROOT
や Locale.JAPAN
だとパースエラーになります。 (API level 23 で確認。 ロケールに関係なく RFC 822 で定義されているタイムゾーンは解析して欲しい……。 厳しい。) でも Locale.US
でも 「Z」 を処理できないから、数値でなく名前で表現されるタイムゾーンの解析周りについてはそもそも信用しないのがいいかもしれないですね。
Web API などに送るための時刻文字列を生成する場合も、同様に SimpleDateFormat
を使いましょう。 タイムゾーンは、受け取り側が期待するであろうものを設定しておくのが安全だと思います。
java.text.DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ssZ", Locale.US); // タイムゾーンを出力するにしても Web API 側が期待するタイムゾーンを設定しておいた方が安全でしょう。 df.setTimeZone(TimeZone.getTimeZone("Asia/Tokyo")); String currentTime = df.format(new Date());
ユーザーに表示するために Date
オブジェクトから時刻の文字列を生成する
ユーザーに表示するための時刻文字列は、ユーザーの端末のロケールやタイムゾーンを使用すべきでしょう。 Java では DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, Locale.getDefault())
という感じでデフォルトロケールの DateFormat
を取得できますが、Android アプリの場合は、端末の設定を反映したフォーマットの DateFormat
を取得するために android.text.format.DateFormat
を使用するのが良さそうです。
詳細は以下のページを見てください。
- http://qiita.com/glayash/items/508304558078203fe24b
- Y.A.M の 雑記帳: SimpleDateFormat ではなく android.text.format.DateFormat を使おう
(API level 23 では、DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, Locale.getDefault())
みたいな感じで取得した DateFormat
オブジェクトも端末の設定を反映しているようでしたが、API level 10 だと反映してなかったので、古い端末もサポートするなら android.text.format.DateFormat
を使うべきっぽいです。)
Date
オブジェクトが表す日の 1 年後、を計算する
例えば今日の 1 年後を表す日を計算したいとします。 そういうときは Calendar
を使います。 *3
// 操作対象の Date オブジェクト。 // 日本時間で 2016 年 2 月 29 日の 00:00:00。 Date d = new Date(145667160_0000L); // Calendar オブジェクトを使って 1 年後を計算して Date オブジェクトを取得。 Calendar c = Calendar.getInstance(TimeZone.getTimeZone("Asia/Tokyo"), Locale.JAPAN); c.setTime(d); c.add(Calendar.YEAR, 1); // 年に 1 加算。 Date dateOneYearAfter = c.getTime(); // 2017 年 2 月 28 日の 00:00:00 (日本時間)。
上の例では、タイムゾーンを Asia/Tokyo に設定しています。 タイムゾーンを適切に設定しないと、渡された Date
オブジェクトが表す日付が変わる可能性がありますので、適切にタイムゾーンを設定しましょう。 ロケールも週の開始曜日などに影響するので必要に応じて設定しましょう。 (デフォルトタイムゾーン・デフォルトロケールが良い場合はもちろんデフォルトの値を使えば良いです。)
*1:ドキュメントにも 『The main reason you'd create an instance this class directly is because you need to format/parse a specific machine-readable format, in which case you almost certainly want to explicitly ask for “US” to ensure that you get ASCII digits (rather than, say, Arabic digits).』 って書かれてる。
*2:少なくとも API level 23 までは。 それより後のバージョンは不明。
*3:単純に 365 日間に相当するミリ秒を加算する、みたいな方法だとうるう年などが考慮できないのでだめですよね。 「1 ヶ月後」 とかだと何日分加算すればいいのかわかんないし。
Java における byte 型について (あるいはバイナリデータを扱うためのキャスト)
この記事は、もともと次のページに書かれていた内容に、加筆・修正を行ったものです。
はじめに
この記事では、ビット列を角括弧で囲み、[00000000]
のように表現する事にします。
byte
型の表現範囲
Java のプリミティブ型 (基本データ型) の 1 つに byte
型というものがあります。 その名の通り 1 バイト (8 ビット) のサイズの整数値を表現できます。
つまり、[00000000]
から [11111111]
まで表現することができます。
数値でいうと 0 (0x00
) から 255 (0xFF
) まで表現できる、と思うところですが、実際には Java の byte
型は符号付きであり、表現できる範囲は -128 から 127 までとなっています。
The values of the integral types are integers in the following ranges:
Chapter 4. Types, Values, and Variables
- For
byte
, from -128 to 127, inclusive- (略)
バイナリデータを扱うための byte
型のキャスト
符号なしで 0 から 255 の整数を表現したい
上述のとおり byte
型には符号が付いているのですが、バイナリデータの各バイトを 0 (0x00
) から 255 (0xFF
) の整数値で扱いたい場面がたまにあります。 当然 byte
型のままでは 128 以上の整数値を扱えませんので、short
型や int
型にキャストする必要があります。 逆に、short
や int
で表現される 0 から 255 までの整数値を byte
にキャストしたい場面もあるでしょう。 (例えば InputStream#read()
メソッドは、バイトの情報を 0 から 255 までの整数値として返してきます。)
byte
型からの変換
Java SE 8 からは Byte
クラスに便利メソッドが用意されているので、それを使うといいでしょう。 (id:nowokay さんに教えていただきました! ありがとうございます!)
Java SE 7 以下では自分でキャストする必要があります。
キャストする場合 (Java SE 7 以下の場合)
byte
から int
や short
へのキャストは、widening primitive conversion です。 このキャストでは、キャストによって数値の情報が失われることがありません。 (キャスト前とキャスト後で数値として同じ値が表現される。)
バイトを非負整数で表現するという目的においては、単にキャストするだけでは MSB (Most Significant Bit) が 1 の場合 (すなわち、値が負の場合) に、期待する結果にはなりません。
byte valueByte = -1; // ビット列で表すと [11111111] short valueShort = (short) valueByte; // valueShort の値は -1 // ビット列で表すと [1111111111111111]
short
型にしたときにビット列としては [0000000011111111]
となって欲しいところですが、元が負値であるために上位ビットに全て 1 が入ってしまっているわけです。 byte
型をキャストして 0 から 255 の整数値の表現にするには、キャスト後に下位 8 ビット以外を 0 にすれば良いでしょう。
byte valueByte = -1; // ビット列で表すと [11111111] short valueShort = (short) valueByte; // valueShort の値は -1 // ビット列で表すと [1111111111111111] // 下位の 8 ビットだけ残し、残りを全て 0 にする。 valueShort &= 0xFF; // 期待通りの 255 という数値になる // ビット列で表すと [0000000011111111] // キャストと下位 8 ビット以外を削る処理を 1 行で行っても良い。 valueShort = (short) (valueByte & 0xFF);
byte
型へのキャスト
一方で、int
や short
から byte
へのキャストは、narrowing primitive conversion です。 このキャストで整数型を byte
に変換する場合、単純に元の数値を表すビット列の下位 8 ビットがキャスト後の byte
値となります。
なので、0 から 255 の整数値で表現される値を byte
型にする場合は、単純にキャストしてやればよいのです。
short valueShort = 0xFF; // 数値としては 255。 ビット列で表すと [0000000011111111] byte valueByte = (byte) short; // 数値としては -1。 ビット列で表すと [11111111]
byte
値を 16 進数表記の文字列にする
C 言語を使う人にはおなじみの printf
ですが、似たものが Java にもあります。 (Java SE 5 より導入された模様。)
これを使用すれば、簡単に byte
値を 16 進数表記の文字列にできます。 標準出力などに出力して目で確認したい時にはなかなか便利です。
byte b = (byte) 0xE8; // 数値としては -24 // String#format メソッドを使用して文字列取得。 String bStr = String.format("%02X", b); // => "E8" // PrintStream#printf メソッドを使用してそのまま出力する。 System.out.printf("%02X", b);
Java SE 1.4 以前は 「Javaのbyte型を16進数で表現」 みたいにまどろっこしいことをしないとダメだったと思います (もしかしたらもっと楽な方法はあるのかも知れませんが) けど、なかなか便利になったものです。
【v7 appcompat library を読む】 レイアウト XML のインフレート時に各種 view が compatible widget に変換される仕組み
Android アプリ開発時にお世話になる v7 appcompat library と LayoutInflater
の話です。
この記事の内容は、v7 appcompat library のバージョン 23.1.1 をもとに記述しました。
v7 appcompat library の compatible widget
v7 appcompat library には、AppCompatTextView
などの compatible widget *1 がいくつか含まれています。 いくつか下に挙げてみます。
AppCompatTextView
:TextView
の compatible widgetAppCompatButton
:Button
の compatible widgetAppCompatImageView
:ImageView
の compatible widgetAppCompatCheckBox
:CheckBox
の compatible widgetAppCompatSpinner
:Spinner
の compatible widget- などなど
これらのクラスは、新しい API level で導入された機能の一部を古いプラットフォームにも提供してくれます。 クラスによって提供される機能は違うものの、主には widget tinting 周りの機能 (+α) が提供されると考えて良さそうです。
appcompat ライブラリによる widget tinting については下のブログエントリを参照してください。
インフレート時に自動的に変換される
各 compatible widget のドキュメントを読むと、以下のような説明が書かれています。 (以下は AppCompatTextView
のもの。)
This will automatically be used when you use
AppCompatTextView | Android DevelopersTextView
in your layouts. You should only need to manually use this class when writing custom views.
つまり、レイアウト XML 中に <TextView ...>
って書いておけば、インフレート時に自動的に AppCompatTextView
になる、ってことですね。 便利です。
インフレート時に自動で変換される仕組み
便利なのはいいけどどういう仕組みなのかわかっておかないと嵌ったりすることもあるので、どういう仕組みなのか調べてみました。
LayoutInflater
に Factory をセットすることでインフレート時の挙動を変更できる
まずは LayoutInflater
について調べてみます。 インフレート時に XML ファイル中の要素名を各 view のクラスに変換する処理は、Factory (LayoutInflater.Factory
/LayoutInflater.Factory2
オブジェクト) が担っているようです。 下記メソッドを見てみると、ドキュメントにいろいろ書かれています。
AppCompatActivity
が LayoutInflater
に Factory をセットしている
AppComaptActivity#onCreate
メソッドの中を見ると、以下のように AppCompatDelegate#installViewFactory()
メソッドを呼んでいます。
getDelegate().installViewFactory();
ドキュメントには 『Installs AppCompat's LayoutInflater
Factory so that it can replace the framework widgets with compatible tinted versions』 と書かれていて、このメソッドが LayoutInflater
に独自の Factory をセットすることがわかります。 実装を追っていくと、このメソッドの中では LayoutInflaterCompat.setFactory
メソッドが呼ばれていました。
まとめ
上で調べたように、AppCompatActivity
が LayoutInflater
に独自の Factory をセットすることで、インフレート時の自動変換が実現されています。 よって、インフレーション時については以下のようにまとめられます。
AppCompatActivity
で使う限り、LayoutInflater#from(Context)
メソッドで取得できるLayoutInflater
でのインフレート時に compatible widget への自動変換がはたらくと考えて良い。AppCompatActivity
を使わないのであれば、AppCompatDelegate#installViewFactory
メソッドを使うことで使うことで同等の機能が実現される。
インフレーション時以外に何か良しなに変換してくれたりはしないので、以下のことにも気を付けましょう。 (appcompat ライブラリを使っている環境での話です。)