カスタムNumberPickerの作成
AndroidのDatePickerやTimePickerは日付・時刻の入力を行うためのコンポーネントであるが、数値を1単位でインクリメント・デクリメントするための機能しかない。
例えば、時刻を入力するとき、1分間隔で入力することは少なく5分とか10分間隔で入力することが多いが、標準のTimePickerで実現できなかった。
そのため、TimePicker内部で使用しているNumberPickerを自作して使用していた。
このとき、下記の問題が発生していた。
- apkファイルの肥大化(NumberPickerが内部で使用するリソースファイルをapkファイルに持つため、apkファイルのサイズが大きくなる)
- Android SDKからコピーしたリソースファイルを流用しているので、カスタムUIを使用している端末(Xperia、Desire等)と同じにならない(背景色とか)。
この問題を解決するために、プライベートリソースの指定(例:res/layout/*.xmlでandroid:drawable="@*android:drawable/timepicker_down_normal" でカスタムUIが持っているシステムリソースを参照する方法を試したが、AndroidアプリのSDKバージョンとAndroid端末のバージョンが一致しないとリソースを取得できないため、使用を諦めていた。
しかし、KazzzさんのNumberPickerを再作成する - Kazzzの日記 のblogと yanzmさんのY.A.M の 雑記帳: Android Get color of status barのblogを参考にさせていただいて、リフレクションを使用してAndroid標準のNumberPickerからリソースを取得すればいいことが分かったので、早速適用してみた。
注意点は、NumberPickerのパッケージがAndroid 2.1以下とAndroid 2.2以上で違うので、SDKバージョンを見てリフレクションのターゲットclassを切り替えている。
リフレクションのターゲットclassの切替とカスタムNumberPickerへのリソースの適用ソースは、下記になる。
// source file: com.example.android.customnumberpicker.widget.NumberPicker public class NumberPicker extends LinearLayout implements OnClickListener, OnFocusChangeListener, OnLongClickListener { private static final String NUMBER_PICKER_CLASS_NAME; static { final int sdkVersion = Build.VERSION.SDK_INT; // 8=Build.VERSION_CODES.FROYO if (sdkVersion < 8) { NUMBER_PICKER_CLASS_NAME = "com.android.internal.widget.NumberPicker"; } else { // Android 2.1 or higher NUMBER_PICKER_CLASS_NAME = "android.widget.NumberPicker"; } } protected void setWidgetResource() { .... try { final Context context = getContext(); final ClassLoader cl = context.getClassLoader(); final Class<?> clazz = cl.loadClass(NUMBER_PICKER_CLASS_NAME); final Constructor<?> constructor = clazz.getConstructor(Context.class); final Object obj = constructor.newInstance(context); final Class<?> c = obj.getClass(); { final Field field = c.getDeclaredField("mIncrementButton"); if (!field.isAccessible()) { field.setAccessible(true); } final ImageButton internalIncrementButton = (ImageButton) field.get(obj); mIncrementButton.setBackgroundDrawable(internalIncrementButton.getBackground()); } { final Field field = c.getDeclaredField("mText"); if (!field.isAccessible()) { field.setAccessible(true); } final EditText internalText = (EditText) field.get(obj); mText.setBackgroundDrawable(internalText.getBackground()); mText.setTextColor(internalText.getTextColors()); // TextSizeを適用すると Android 2.2 800x480(hdpi) で、テキストが大きくなってしまうので、コメントアウト。 //mText.setTextSize(internalText.getTextSize()); } { final Field field = c.getDeclaredField("mDecrementButton"); if (!field.isAccessible()) { field.setAccessible(true); } final ImageButton internalDecrementButton = (ImageButton) field.get(obj); mDecrementButton.setBackgroundDrawable(internalDecrementButton.getBackground()); } mButtonsBackgroundInitilized = true; } catch (final Exception ex) { Log .e( TAG, "com.android.internal.widget.NumberPicker internal button background resource got not.", ex); } } .... }
サンプルとして、n分間隔でインクリメント・デクリメントするTimePickerを作成した。下記のURLからダウンロード可能。
http://www1.axfc.net/uploader/Sc/so/154123.zip
上記のサンプルを、各Adnroid端末で実行したときのスクリーンショットが下記になる。
左がカスタムTimePicker、右がTimePickerになる。
縦幅が若干違うのと日付アイコンが違うが、殆ど同じイメージになった。
- HTC Desire(Android 2.1, 800x480 (hdpi))