search
LoginSignup
28
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

posted at

updated at

Kotlinでcustom viewを実装する時の注意点

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を実装します。

KotlinEditText.kt
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) 
        ...
}

実行すると以下のようになります。

Screenshot_1518936561.png

次に2の方法で実装します。

class KotlinEditText @JvmOverloads constructor(
        context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : EditText(context, attrs, defStyleAttr)

実行すると以下のようになります。

Screenshot_1518936315.png

このことから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)

Screenshot_1518936561.png

うまくいきました!!

改善策案③ constructorの引数を減らす。

②の方法だと、毎回調べる必要があるので面倒です。それならいっそdefStyleAttrを引数から外してみます。

class KotlinEditText @JvmOverloads constructor(
        context: Context, attrs: AttributeSet? = null
) : EditText(context, attrs)

こちらもうまくいきますが、どのような影響が出るのかはわからないです。

まとめ

正直一番無難な方法である。1の方法で実装した方が、頭とか使わなく実装できると思いますが、見栄えをよくするために@JvmOverloadsを使う場面が出てくると思います。
@JvmOverloads でのcustom viewは注意してください。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
28
Help us understand the problem. What are the problem?