Viewの4つのコンストラクタ

View、および、そのサブクラスには以下の4つのコンストラクタがあります。
それぞれの使用方法などを調べてみました。

```java
1. View(Context context)
2. View(Context context, AttributeSet attrs)
3. View(Context context, AttributeSet attrs, int defStyleAttr)
4. View(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) // API Level 21から
```

#まずはReferenceから
[Reference](http://developer.android.com/intl/ja/reference/android/view/View.html#pubctors)には、以下のように記載されています。

```
1. コードでViewを生成したときのコンストラクタ
2. XMLからViewをinflateした際のコンストラクタ
3. XMLからinflateして、Themeからクラス固有の基本スタイルを適用します
4. XMLからinflateして、Themeからクラス固有の基本スタイルまたは指定のスタイルを適用します
```

1はいいとして、2, 3, 4がどう区別されているのかと`LayoutInflater`のソース(API Level 23)を見ても、
以下のように2のコンストラクタしか呼んでいません。

```java:LayoutInflater
public abstract LayoutInflater {
  ...
  static final Class<?>[] mConstructorSignature = new Class[] {Context.class, AttributeSet.class};
  ...
  public final View createView(String name, String prefix, AttributeSet attrs) throws ClassNotFoundException, InflateException {
    Constructor<? extends View> constructor = sConstructorMap.get(name);
    ...
    if (constructor == null) {
      ...
      constructor = clazz.getConstructor(mConstructorSignature);
      ...
    } else {
      ...
    }

    Object[] args = mConstructorArgs;
    args[1] = attrs;

    final View view = constructor.newInstance(args);
    ...
  }
```

Androidのソースを検索しても、以下の`TextView`のような例外もありますが、基本的には3, 4のコンストラクタは呼ばれていません。

```java:TextView
public TextView(Context context, @Nullable AttributeSet attrs) {
  this(context, attrs, com.android.internal.R.attr.textViewStyle);
}
```

#3, 4のコンストラクタの動作
Android内部では使われていないので、変化のわかりやすい`TextView`で実際に使ってみました。

```xml
<attr name="customAttr" format="reference"/>

<style name="AppTheme.Custom">
  <item name="android:textColor">#cc0000</item>
  <item name="customAttr">@style/CustomStyle1</item>
</style>

<style name="CustomStyle1">
  <item name="android:textColor">#00cc00</item>
</style>

<style name="CustomStyle2">
  <item name="android:textColor">#0000cc</item>
  <item name="android:textStyle">italic</item>
</style>
```

`styles.xml`などに上記のように各値を定義します。

`customAttr`は第3引数、`defStyleAttr`に渡す値です。
`format`は`reference`として、実際の値は`Style`として別途定義します。

`AppTheme.Custom`は`Activity`に指定する`Theme`で`android:textColor`と`customAttr`を設定しています。それぞれ赤と緑です。

`CustomStyle2`は第4引数、`defStyleRes`に渡す値です。
青以外にイタリック体にしている理由は後ほどわかります。

これらを`TextView`の各コンストラクタに指定して表示したのが下の図です。

![ss.png](https://qiita-image-store.s3.amazonaws.com/0/22848/7b50d8d5-bb21-2913-383a-65fb2c869d6e.png)

1, 2のコンストラクタは、`AppTheme.Custom`に指定している`android:textColor`の赤が反映されています。
3のコンストラクタは、`AppTheme.Custom`の`customAttr`に指定している`CustomStyle1`の`android:textColor`の緑が反映されています。
4のコンストラクタの1番目(`R.attr.customAttr`, `R.style.CustomStyle2`)は、緑で、青でもイタリック体でもないので、`R.style.CustomStyle2`の指定は実質無視されています。
4のコンストラクタの2番目(`0`, `R.style.CustomStyle2`)は、青でイタリック体なので`CustomStyle2`が正しく反映されています。

まとめると、以下の通りです。

```
1, 2のコンストラクタは、その View を持つ Activity などの Theme を参照する。
3, 4のコンストラクタで defStyleAttr が0以外の場合は、 Theme に設定されているその attr を参照する。
4のコンストラクタで defStyleAttr が0の場合は、その style を参照する。

つまり、 Theme に指定されているスタイルを適用したい場合は defStyleAttr 、
Theme に関係なくスタイルを適用したい場合は defStyleRes でしょうか。
```

まぁ、わかってしまえば、Referenceの通りですね。

```
1. コードでViewを生成したときのコンストラクタ
2. XMLからViewをinflateした際のコンストラクタ
3. XMLからinflateして、Themeからクラス固有の基本スタイルを適用します
4. XMLからinflateして、Themeからクラス固有の基本スタイルまたは指定のスタイルを適用します
```

しかし、4のコンストラクタの第3引数は、オーバーロードの制約から消せなかっただけじゃないのか。。。

#実際、どうやって3, 4のコンストラクタを使うか
XMLを`inflate`しても呼ばれないため、呼ぶ方法は以下のいずれかでしょうか。

1.以下のようにコードでViewを生成する

```java
new View(context, null, R.attr.customAttr)
```

2.`TextView`のようにコンストラクタで指定する

```java
public TextView(Context context, @Nullable AttributeSet attrs) {
  this(context, attrs, com.android.internal.R.attr.textViewStyle);
}
```

1はXMLが使えない、2はカスタムViewでしか使えない。どちらもイマイチですね。

3番目として、`LayoutInflater#Factory`を使う方法があります。

`LayoutInflator`には、`Factory`, `Factory2`クラスがあり、それらに`View`の生成を委譲することができます。

```java:LayoutInflater
public abstract class LayoutInflater {

  public interface Factory {
    public View onCreateView(String name, Context context, AttributeSet attrs);
  }

  public interface Factory2 extends Factory {
    public View onCreateView(View parent, String name, Context context, AttributeSet attrs);
  }

  View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,　boolean ignoreThemeAttr) {
    ...
    View view;
    if (mFactory2 != null) {
      view = mFactory2.onCreateView(parent, name, context, attrs);
    } else if (mFactory != null) {
      view = mFactory.onCreateView(name, context, attrs);
    } else {
      view = null;
    }

    if (view == null && mPrivateFactory != null) {
      view = mPrivateFactory.onCreateView(parent, name, context, attrs);
    }

    if (view == null) {
      final Object lastContext = mConstructorArgs[0];
      mConstructorArgs[0] = context;
      try {
        if (-1 == name.indexOf('.')) {
          view = onCreateView(parent, name, attrs);
        } else {
          view = createView(name, null, attrs);
        }
      } finally {
        mConstructorArgs[0] = lastContext;
      }
    }

    return view;
    ...
  }
}
```

ただし、設定できるのは1回のみで、2回目以降は`IllegalStateException`がスローされます。

```java:LayoutInflater#setFactory
public void setFactory(Factory factory) {
  if (mFactorySet) {
    throw new IllegalStateException("A factory has already been set on this LayoutInflater");
  }
  if (factory == null) {
    throw new NullPointerException("Given factory can not be null");
  }
  mFactorySet = true;
  if (mFactory == null) {
    mFactory = factory;
  } else {
    mFactory = new FactoryMerger(factory, null, mFactory, mFactory2);
  }
}
```

そして、`AppCompatActivity`は`AppCompatTextView`などを生成するために`onCreate`内でファクトリーを設定しています。。。
対処法はなくはないですが、それなりに面倒です。
しかも、乱用するのは危険でしょう。

#というか、いつ使うのか
動的に画面全体の見た目を変えるなら、`Activity`の`setTheme`の方が良いと思います。
こんな苦労してまで使いたいときがあるのかどうか。。。
良い使い方知っていたら教えてください。