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

KotlinでCustom Viewを実装する際コンストラクタの書き方として２つあります。

### 1. constructorを3つ並べる
こちらはJavaと同じ書き方の実装法です。

```kotlin
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`アノテーションを使うことでよりシンプルに実装が可能になります。

```kotlin
class KotlinView @JvmOverloads constructor(
        context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr)
```


1と2を比べると2の方がシンプルで使いやすく感じますが、Viewの種類によっては2の書き方で上手くいかない場合があります。

## EditText

EditTextを１と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](https://qiita-image-store.s3.amazonaws.com/0/217752/4dde4b77-caea-52f2-564d-8b841742ba60.png)



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

```kotlin
class KotlinEditText @JvmOverloads constructor(
        context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : EditText(context, attrs, defStyleAttr)
```
実行すると以下のようになります。

![Screenshot_1518936315.png](https://qiita-image-store.s3.amazonaws.com/0/217752/32fb62b0-6e4c-df83-dbfb-446a44af5a65.png)


このことから２の方法だと上手くいってないことがわかります。

## Why?

EditTextのソースコードは以下のようになっています。

```kotlin
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`の方法で呼び出しを行なった場合は

```kotlin
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`を初期値として設定します。

```kotlin
class KotlinEditText @JvmOverloads constructor(
        context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = android.R.attr.editTextStyle
) : EditText(context, attrs, defStyleAttr)
```

![Screenshot_1518936561.png](https://qiita-image-store.s3.amazonaws.com/0/217752/4dde4b77-caea-52f2-564d-8b841742ba60.png)

うまくいきました！！

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

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

```kotlin
class KotlinEditText @JvmOverloads constructor(
        context: Context, attrs: AttributeSet? = null
) : EditText(context, attrs)
```
こちらもうまくいきますが、どのような影響が出るのかはわからないです。

## まとめ

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