ボタンのマークをテキストの右に置く
kotlinの話ではないですが...(^^;
チェックボックスもラジオボタンも、 普通に設置するとマークはテキストの左に表示されます。
これを右にするための設定です(必要な部分だけ)。
<!-- チェックボックスの場合 --> <CheckBox ・・・ android:button="@null" android:drawableRight="?android:attr/listChoiceIndicatorMultiple" /> <!-- ラジオボタンの場合 --> <RadioButton ・・・ android:button="@null" android:drawableRight="?android:attr/listChoiceIndicatorSingle" />
オプションメニューにボタンを付ける
オプションメニューに、チェックボックスとラジオボタンを付けます。 また、ラジオボタンはサブメニュー化します。
注意点としては、選択状態の切替えは、自分でプログラムを書くこと。 何も書かないと、クリックしても選択状態にはなりません。
res\menu\menu_main.xml
<menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <!-- チェックボックス --> <item android:id="@+id/action_check1" android:title="@string/action_check1" android:checkable="true" app:showAsAction="never" /> <!-- サブメニュー --> <item android:id="@+id/action_submenu" android:title="@string/action_submenu" app:showAsAction="never"> <menu> <!-- ラジオボタン --> <group android:checkableBehavior="single"> <item android:id="@+id/action_radio1" android:title="@string/action_radio1" /> <item android:id="@+id/action_radio2" android:title="@string/action_radio2" /> </group> </menu> </item> </menu>
menuフォルダが無ければ作ってください。
次にActivityのクラス内に、設置と押された時の処理を書きます。
class MainActivity : AppCompatActivity() { ・・・ //オプションメニューの表示 override fun onCreateOptionsMenu(menu: Menu?): Boolean { val inflater = menuInflater inflater.inflate(R.menu.menu_main, menu) //チェックボックスの初期化 menu?.findItem(R.id.action_check1)?.setChecked(true) //ラジオボタンの初期化 menu?.findItem(R.id.action_radio1)?.setChecked(true) return true } //押された時の処理 override fun onOptionsItemSelected(item: MenuItem): Boolean { return when (item.itemId) { //チェックボックスの切替 R.id.action_check1 -> { item.setChecked(!item.isChecked()) ・・・ true } //ラジオボタンの選択 R.id.action_radio1 -> { item.setChecked(true) ・・・ true } R.id.action_radio2 -> { item.setChecked(true) ・・・ true } else -> super.onOptionsItemSelected(item) } } }
オプションメニューを設置する
アプリバーとかアクションバー、ツールバーなどと呼ばれるタイトルバー部分に、 オプションメニューを付ける方法です。
まずは、リソースデータから。
res\menu\menu_main.xml
<menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <item android:id="@+id/action_item1" android:title="@string/action_item1" app:showAsAction="never" /> </menu>
※menuフォルダが無ければ作ってください。 (Markdown記法のファイル名表記には対応してないのかな?)
次にActivityのクラス内に、設置と押された時の処理を書きます。
class MainActivity : AppCompatActivity() { ・・・ //オプションメニューの表示 override fun onCreateOptionsMenu(menu: Menu?): Boolean { val inflater = menuInflater inflater.inflate(R.menu.menu_main, menu) return true } //押された時の処理 override fun onOptionsItemSelected(item: MenuItem): Boolean { return when (item.itemId) { R.id.action_item1 -> { ・・・ true } else -> super.onOptionsItemSelected(item) } } }
Error inflating class
コンパイルは通るのに、実行時に落ちる。
java.lang.RuntimeException: Unable to start activity ~: Binary XML file line ~: Error inflating class ~
デバッガで確認してみると、リソースが原因で落ちている模様。
ネットで調べてみると、リソースがみつからないか、 コンストラクタが不十分と言ったことが原因のようです。
リソースは、端末によって自動的に切り替えられるようになっているので、 例えば、drawable-v24フォルダのように、 特定のバージョンでなければ利用されない場所にあるリソースは、 バージョンが足りない環境で参照しようとすると落ちます。
また、カスタムビューをクラス名だけで指定している場合も、 コンパイルは通りますが実行時に落ちますので、 下のように完全修飾クラス名で指定すること。
<com.xxx.myapp.CustomView android:layout_width="match_parent" android:layout_height="match_parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" />
Viewのコンストラクタについては、 前にも書いたとおり、 @JvmOverloadsアノテーションを付けていれば大丈夫です。
イベントリスナの書き方でthisの意味が変わる
kotlinでは、SAM変換を利用してシンプルな形でイベントリスナを記述できますが、 正規の書き方で書いた時とシンプルな形で書いた時で、 thisが違うものを指します。
今回、イベント内でthisを使う必要が出て、気付きました。
kotlin流
button.setOnClickListener{ Log.e("debug","button SAM " + this.javaClass + " " + it.javaClass) } //以下のように表示される //E/debug: button SAM class ~.MainActivity class ~.AppCompatButton
イベントを登録したクラスのインスタンスが示されています。
Java風
button.setOnClickListener( object : View.OnClickListener { override fun onClick(p0: View?) { Log.e("debug","button " + this.javaClass + " " + p0?.javaClass) } } ) //以下のように表示される //E/debug: button class ~.MainActivity$initView$2 class ~.AppCompatButton
リスナのインスタンスが示されています。
thisが必要な時は?
Viewのイベントを上書きして、それを取り消す時に必要でした。
view.getViewTreeObserver().addOnPreDrawListener({ view.getViewTreeObserver().removeOnPreDrawListener(this) true })
ここではリスナのインスタンスが欲しいのに、 クラスのインスタンスが入ってるので、 コンパイルエラーになります。
逆に、クラス内に用意した変数にアクセスできるので、便利な 面もありますが(^^;
クラスの書き方
カスタムViewの書き方
class CustomView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : RelativeLayout(context, attrs, defStyleAttr) { ・・・ init { ・・・ } }
上記サンプルはRelativeLayoutですが、この部分は継承するViewに変えればOKです。 ポイントは@JvmOverloadsアノテーション。 これにより、おまじないのようなconstructorが省略できます。
初期化処理はinitブロックで。
データクラスの場合
data class SampleData( var value1: Int = 0, var value2: Int = 0, var value3: String = "" ) { ・・・ init { ・・・ } constructor(value4: Int, value3: String) : this(value4, 1, value3) { ・・・ } }
クラス定義の仮引数部分が、var(またはval)を用いた変数宣言になっているのがポイント。 これが、変数宣言とprimary constructorの役割を果たすようです。 また、初期値を設定しておくことで、引数を省略して呼び出すことも可能になります。
もちろん、サンプルに記載のとおり、別途constructorを追加することも可能。 注意としては、必ずthis()によってprimary constructorが呼び出されなければなりません。
この辺りが、Javaとは異なる部分のようです。
Activityの場合
class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { ・・・ } ・・・ }
Activityに関しては、カスタムViewとは違い、 onCreateメソッド内で初期化するようです。
イベントリスナ(コールバック)の書き方
Java風の書き方もできますが、
interface OnSampleListner { fun onSample(int: Int) } class Sample { var sampleListner: OnSampleListner? = null fun sampleMethod() { sampleListner?.onSample(1) } } fun main() { val sample1 = Sample() sample1.sampleListner = object: OnSampleListner { override fun onSample(int: Int) { println("Event!! $int") } } sample1.sampleMethod() //Event!! 1 }
kotlinでは、一部のリスナに対してはSAM変換によって、 とてもシンプルに定義できるようで、
button.setOnClickListener {
println("Event!!")
}
自前のコールバックでも、同様の書き方ができないか調べていたら、 interfaceの代わりにtypealiasを使う方法がありました。
typealias OnSampleListner2 = (Int) -> Unit class Sample2 { var sampleListner: OnSampleListner2? = null fun sampleMethod() { sampleListner?.invoke(2) } } fun main() { val sample = Sample2() sample.sampleListner = { int -> println("Event!! $int") } sample.sampleListner = { //引数がひとつ以下の時は、こうも書ける println("Event!! $it") } sample.sampleMethod() //Event!! 2 }
また、汎用性を求めなければ、typealiasすら省略できるようです。
class Sample3 { var sampleListner: ((int: Int) -> Unit)? = null fun sampleMethod() { sampleListner?.invoke(3) } } fun main() { val sample = Sample3() sample.sampleListner = { int -> println("Event!! $int") } sample.sampleListner = { //引数がひとつ以下の時は、こうも書ける println("Event!! $it") } sample.sampleMethod() //Event!! 3 }
書き方によって、thisの意味が変わることが判明したので、 注意としてまとめました。