Android Studio + Kotlinでカスタムビューを作りたい。
滅多にないけど既存ビューで不足だったり、
どうしても必要になってしまう場面はあります。
今回はKotlinでこんなビューを定義したかったです。
▼ ゴールはミリ秒まで取得できるTimePicker作成
- 時間を設定・取得できるTimePicker
- ただしミリ秒精度まで設定できる
- 時間/分/秒/ミリ秒を個別に設定可能
- カスタムレイアウト(XML)を使いたい
▼ 最終的なSmartTimePickerの見た目
このカスタムビューを例にしながら、
Kotlinでのカスタムビューの定義手順を解説します。
このページの目次
0.今回作成するカスタムビューの概要とか
名前は SmartTimePicker と名付けました。
これは次のパッケージ名を持ちます。
com.hoge.myapp.SmartTimePicker
このパッケージ名は各自で変わるので注意
そして冒頭で書いたように次の特長を持ちます。
- ミリ秒精度で時間を設定できる
- ミリ秒単位で時間を取得できる
- UI的には 時間/分/秒/ミリ秒 という並び
既存のTimePickerを使おうと思ったけど、それだと秒単位まで指定できない…なんならミリ秒精度まで必要なのにそれだと不便すぎました。
だから思い切ってカスタムビュークラスを定義し、レイアウトもカスタムレイアウト(XML)から作って独自ビュー作成しようと考えました。
動機としてはそんなところです。
1,カスタムビューのlayoutXMLを定義しよう
初めにカスタムビューのlayoutから定義します。
名前 : custom_layout_smart_time_picker.xml
▼ 以下がそのレイアウトXMLの内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
<?xml version="1.0" encoding="utf-8"?> <merge xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout" android:layout_width="match_parent" android:layout_height="match_parent"> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" android:gravity="center"> <NumberPicker android:id="@+id/hours_picker" android:layout_width="wrap_content" android:layout_height="wrap_content"/> <TextView android:id="@+id/textView3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="4dp" android:text=":" android:textSize="36sp" /> <NumberPicker android:id="@+id/minutes_picker" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="4dp" android:text=":" android:textSize="36sp" /> <NumberPicker android:id="@+id/seconds_picker" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:id="@+id/textView2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_margin="4dp" android:text="." android:textSize="36sp"/> <NumberPicker android:id="@+id/millis_picker" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </LinearLayout> </merge> |
このビューは 時間/分/秒/ミリ秒 が個別指定可能。
具体的にはNumberPickerで指定できるUIにしました。
あと地味に重要なのがルートビューをmergeタグを使って定義してることです。そうすることでレイアウト的な冗長性がなくなります。(パフォーマンス向上)
さすがにAndroid Studioでのレイアウト作成の手順は解説しません。カスタムビューを作ろうと考えられるレベルなら、そんな解説は不要なはずです。
レイアウトはこんな感じです。
2.カスタムビュークラスを拡張・作成する
お次はカスタムビュークラスの作成です。
ここではConstraintLayoutを拡張します。
▼ カスタムビュークラス : SmartTimePicker.kt
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
class SmartTimePicker @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : ConstraintLayout(context, attrs, defStyleAttr) { private lateinit var hoursPicker: NumberPicker private lateinit var minutesPicker: NumberPicker private lateinit var secondsPicker: NumberPicker private lateinit var millisPicker: NumberPicker init { init(attrs) } private fun init(attrs: AttributeSet?) { View.inflate(context, R.layout.custom_layout_smart_time_picker, this) /// レイアウト上の各ビューを取得 hoursPicker = findViewById(R.id.hours_picker) minutesPicker = findViewById(R.id.minutes_picker) secondsPicker = findViewById(R.id.seconds_picker) millisPicker = findViewById(R.id.millis_picker) /// 各時間単位の最大値を設定 hoursPicker.maxValue = 99 minutesPicker.maxValue = 59 secondsPicker.maxValue = 59 millisPicker.maxValue = 999 } /** * ミリ秒から時間UIを設定する **/ fun setTimeInMillis(millis: Int){ val l = millis % 1000 val s = millis / 1000 % 60 val m = millis / 1000 / 60 % 60 var h = millis / 1000 / (60 * 60) % 24 h += millis / 1000 / (60 * 60 * 24) * 24 hoursPicker.value = h minutesPicker.value = m secondsPicker.value = s millisPicker.value = l } /** * 現時点のUIが表す時間をミリ秒で返す **/ fun getTimeInMillis(): Int{ return hoursPicker.value*60*60*1000 + minutesPicker.value *60*1000 + secondsPicker.value*1000 + millisPicker.value } } |
やってることは至って単純です。
先ほど定義したレイアウトXMLから View.inflate(context, [レイアウトID], this) みたいにレイアウトを膨らませ、あとは各ビューに値を設定してるだけです。
3.カスタムビューをレイアウト上に設置
あとはActivityのレイアウト上に設置
普通にカスタムビューのタグを書くだけです。
▼ ConstraintLayout内にカスタムビュー設置例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent"> <com.hoge.myapp.SmartTimePicker android:id="@+id/time_picker" android:layout_width="match_parent" android:layout_height="wrap_content" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout> |
あとは普通のビューと同じように扱えます。
カスタムビュー(SmartTimePicker)の動作例
これでカスタムビュー(SmartTimePiacker)の完成です。
こんな見た目と動作になりました。
▼ SmartTimePicker表示時の見た目
▼ SmartTimePickerを動かしてる様子(Gif)
ほぼ思い通りの仕上がりになりました。
カスタムビューの作成はそんなに難しくない
以上、Android Kotlinでのカスタムビューの作り方でした。
基本手順を踏めば、どんなカスタムビューでも作れます。