2017年7月7日金曜日

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

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

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

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

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

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

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

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

Application には Theme.AppCompat(黒系)、MainActivity には Theme.AppCompat.Light(白系)を指定します。
  1. <manifest ...>  
  2.   
  3.     <application  
  4.         ...  
  5.         android:theme="@style/Theme.AppCompat">  
  6.   
  7.         <activity  
  8.             android:name=".MainActivity"  
  9.             android:theme="@style/Theme.AppCompat.Light">  
  10.             ...  
  11.         </activity>  
  12.     </application>  
  13.   
  14. </manifest>  
  1. public class MainActivity extends AppCompatActivity {  
  2.   
  3.     @Override  
  4.     protected void onCreate(Bundle savedInstanceState) {  
  5.         super.onCreate(savedInstanceState);  
  6.         setContentView(R.layout.activity_main);  
  7.   
  8.         Button button1 = new Button(getApplicationContext());  
  9.         button1.setText("Application Context");  
  10.   
  11.         Button button2 = new Button(this);  
  12.         button1.setText("Activity Context");  
  13.   
  14.         LinearLayout ll = (LinearLayout) findViewById(R.id.container);  
  15.         ll.addView(button1);  
  16.         ll.addView(button2);  
  17.     }  
  18. }  




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



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





0 件のコメント:

コメントを投稿