2017年7月7日金曜日

View のコンテキストに Application Context を渡すとテーマが適用されない

View のコンストラクタは Context を必要とします。
Context として以下のように Application Context を渡しているコードを見かけました。 public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Button textView = new Button(getApplicationContext()); } } これがなぜよくないのか説明します。

View に渡された Context はスタイル属性を取得するのに使われます。

View.java public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { this(context); final TypedArray a = context.obtainStyledAttributes( attrs, com.android.internal.R.styleable.View, defStyleAttr, defStyleRes); ... background = a.getDrawable(attr); ... View のスタイルは
  1. 1. XMLタグで指定された値(例 android:background="@drawable/button_bg")
  2. 2. style で指定された値(例 style="@style/MyButtonStyle)
  3. 3. テーマで指定された値(例 テーマの中で @style/MyButtonStyle
の優先度で適用されます。

このことを踏まえて obtainStyledAttributes() の中身をみると

Context.java public final TypedArray obtainStyledAttributes( AttributeSet set, @StyleableRes int[] attrs, @AttrRes int defStyleAttr, @StyleRes int defStyleRes) { return getTheme().obtainStyledAttributes( set, attrs, defStyleAttr, defStyleRes); } getTheme() で Theme を取得し、Theme の obtainStyledAttributes() を呼んでいます。
つまり、3. では Context が持っているテーマ情報を使っているのです。

Application Context を渡してはいけない理由がわかったでしょうか。

Activity ごとにテーマを指定できるのに、Application Context を渡してしまうと、Activity のテーマが全く利用されません。
実際にやってみましょう。

Application には Theme.AppCompat(黒系)、MainActivity には Theme.AppCompat.Light(白系)を指定します。 <manifest ...> <application ... android:theme="@style/Theme.AppCompat"> <activity android:name=".MainActivity" android:theme="@style/Theme.AppCompat.Light"> ... </activity> </application> </manifest> public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Button button1 = new Button(getApplicationContext()); button1.setText("Application Context"); Button button2 = new Button(this); button1.setText("Activity Context"); LinearLayout ll = (LinearLayout) findViewById(R.id.container); ll.addView(button1); ll.addView(button2); } }



画面のテーマは Theme.AppCompat.Light (白系)なのに、Application Context を渡した上のボタンは Theme.AppCompat(黒系)の色になってしまっています。



Context が持つテーマが利用されるという仕組みは、v7 AppCompat Support Library でも使われています。 AppCompatTextView を見てみましょう。 public class AppCompatTextView extends TextView implements TintableBackgroundView { ... public AppCompatTextView(Context context, AttributeSet attrs, int defStyleAttr) { super(TintContextWrapper.wrap(context), attrs, defStyleAttr); TintContextWrapper でラップした Context を TextView に渡しています。
これにより、colorAccent の指定色によって自動でテキストカーソルなどが tint されます。





0 件のコメント:

コメントを投稿