KotlinでCustom Viewを実装する際コンストラクタの書き方として2つあります。
1. constructorを3つ並べる
こちらはJavaと同じ書き方の実装法です。
class KotlinView : View {
constructor(context: Context) : super(context, null)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
...
}
2. @JvmOverloads
で短縮したconstructorを使う
@JvmOverloads
アノテーションを使うことでよりシンプルに実装が可能になります。
class KotlinView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr)
1と2を比べると2の方がシンプルで使いやすく感じますが、Viewの種類によっては2の書き方で上手くいかない場合があります。
EditText
EditTextを1と2それぞれの方法でCustom Viewを実装します。
class KotlinEditText : EditText {
constructor(context: Context) : super(context, null)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
...
}
実行すると以下のようになります。
次に2の方法で実装します。
class KotlinEditText @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : EditText(context, attrs, defStyleAttr)
実行すると以下のようになります。
このことから2の方法だと上手くいってないことがわかります。
Why?
EditTextのソースコードは以下のようになっています。
public class EditText extends TextView {
public EditText(Context context) {
this(context, null);
}
public EditText(Context context, AttributeSet attrs) {
this(context, attrs, com.android.internal.R.attr.editTextStyle);
}
public EditText(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
public EditText(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
引数が2つのコンストラクタが実行された時defStyleAttrの初期値がcom.android.internal.R.attr.editTextStyleになっています。
対して@JvmOverloads
の方法で呼び出しを行なった場合は
public EditText(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
が実行されてdefStyleAttr = 0
として処理されるため、先ほどの不具合が発生します。
改善策① @JvmOverloads
を諦める
諦めるとこのような不具合がおりません。
改善策案② defStyleAttrの初期値を変更する。
こいつが0のせいでうまくいかなかったので、思いっきり引数を変えてみます。
com.android.internal.R.attr.editTextStyle
と同等の値のandroid.R.attr.editTextStyle
を初期値として設定します。
class KotlinEditText @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = android.R.attr.editTextStyle
) : EditText(context, attrs, defStyleAttr)
うまくいきました!!
改善策案③ constructorの引数を減らす。
②の方法だと、毎回調べる必要があるので面倒です。それならいっそdefStyleAttr
を引数から外してみます。
class KotlinEditText @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null
) : EditText(context, attrs)
こちらもうまくいきますが、どのような影響が出るのかはわからないです。
まとめ
正直一番無難な方法である。1の方法で実装した方が、頭とか使わなく実装できると思いますが、見栄えをよくするために@JvmOverloads
を使う場面が出てくると思います。
@JvmOverloads
でのcustom viewは注意してください。