Lollipopの前身であるAndroid L Previewが登場してから、もうすぐ1年が経とうとしています。皆さんもLollipop触ってますか?
今回は、LollipopのAndroid SDKコードでたまたま見かけたアレな状況を紹介したいと思います。
TimePickerの新UI
TimePickerはAPI Level 1のときから存在する、最も古いUIコンポーネントの1つです。
Lollipopでは、従来のロール方式での時刻選択に加えて、新たに時計の文字盤を模したUIが追加されましたね。これらはモード選択によって、どちらも利用できるようになっています。
近年のNexusユーザーにとっては、そろそろ慣れ親しんだコントロールになってきていることでしょう。
<TimePicker
...
android:timePickerMode="spinner" />
<TimePicker
...
android:timePickerMode="clock" />
ハックしようと覗いてみたら
TimePickerを使っているとよくあるのが、「時計をN分刻みで選択できるようにしたい」という要件です。
これについては、StackOverflowを始めとして、いくつかの記事が公開されています。私はこちらの記事を参考にして、これまでTimePickerをハックしてきました。
簡単に言うと、「TimePickerはmMinuteSpinner
というNumberPicker型フィールドを持っているから、リフレクションでぶっこ抜いて制御しようぜ!」というやつです。
内部実装が従来とは違う
前述のとおり、TimePickerは必ずしもNumberPickerを並べただけのコントロールではなくなってしまったので、実装コードも大幅に変わっています。具体的には、SpinnerモードとClockモードのDelegateを作り、前述のandroid:timePickerMode
の値に応じて内部実装を切り替えているのです。
ということで、前述のハックはLollipopでは動きません。一工夫する必要がありそうです。(この辺の話はまた別の機会に)
名は体を表・・・さない!?
新しいTimePickerのコードを掘り返していったところ、奇妙なものを見つけました。
- https://github.com/android/platform_frameworks_base/blob/android-5.0.0_r1/core/java/android/widget/TimePickerClockDelegate.java
- https://github.com/android/platform_frameworks_base/blob/android-5.0.0_r1/core/java/android/widget/TimePickerSpinnerDelegate.java
あ・・・ありのまま、今、起こった事を話すぜ!
「Clock用DelegateがNumberPickerを持ち、Spinner用Delegateが文字盤ビューを持っていた」
な・・・何を言っているのか、わからねーと思うが
おれも、何を見たのか、わからなかった・・・
頭がどうにかなりそうだった・・・デザインパターンだとか命名規則だとか
そんなチャチなもんじゃあ、断じてねえ
もっと恐ろしいものの片鱗を、味わったぜ・・・
API Level 21 の闇
というわけで、名前と実装が逆転しているのを見つけてしまったわけですが、実際問題、レイアウトXMLに対して timePickerMode
を指定する分には、ちゃんと動作しています。クラス名が逆なのだから、動作も逆になってしまいそうなものですが・・・
と不思議に思いながら、TimePickerがDelegateを切り替えている部分に何となく目を向けてみました。
https://github.com/android/platform_frameworks_base/blob/android-5.0.0_r1/core/java/android/widget/TimePicker.java#L87-L97
・・・
ん!?
こらこらこらこらー!!?
命名ミスったまま進んでしまったのか、何かのタイミングでRename系リファクタリングのミスがあったのかは知りませんが、宣言のたすき掛けによって、見事に前述の矛盾を誤魔化しています。
API Level 22 で直ってるよ
流石にこのままで放置されるはずもなく、現行最新版のAPI Level 22では、この問題は修正されています。
switch (mode) {
case MODE_CLOCK:
mDelegate = new TimePickerClockDelegate(
this, context, attrs, defStyleAttr, defStyleRes);
break;
case MODE_SPINNER:
default:
mDelegate = new TimePickerSpinnerDelegate(
this, context, attrs, defStyleAttr, defStyleRes);
break;
}
ファイル名を取り替えるだけといえばだけなんですが、Git的には実装が丸々消されて書き換えられた感じになるので、修正コミットが大惨事になってました。
"Swap names for clock delegates so they are correct"
3 changed files with 1,444 additions and 1,444 deletions.
https://github.com/android/platform_frameworks_base/commit/daf33ed85353ab7d7a7668dd0e3f9a66f0d5583f
大変だなあ(他人事)
まとめ
面白いもの見つけちゃったなあと思いました(小並感)