Android多テーマ切替(theme+style)及びselector/drawableは引用できませんか?atr属性の問題

17490 ワード

必要:
最近は、アプリケーション内の多テーマの需要を実現する必要があります.10個ぐらいのテーマの配色を適用してください.ユーザーは必要に応じて切り替えられます.需要を得ると、これは簡単だと思います.Androidのtheme+styleで解決できます.ほどなく、atrはselectorに失敗します.drawableなどのxml資源の引用の大きな穴.テーマ色の切り替えの方案は中国語のネット上でいっぱい探していますが、どのブロガーの親切な話がここにありますか?ここで解決案を簡単に紹介します.
Androidプリセットの多テーマ解決策:
まずテーマの配色に関する属性を定義して、私はそれを単独でvalues/style_に書きます.themes_atrs.xmlに



    

    
    

    
    
    

    
    
    
    
    

    


これらの属性は大域的に適用され、参照を容易にするためにのラベルに書くべきではない.
そして、各テーマの配色の具体的な色の値を定義します.つまり、上記の属性に値を付けます.私はそれを単独でvalues/style_に書きます.themes.xmlに.



    

    

    


同前default継承のBaseApple Themeはこの需要を受け取る前にすでにAndroid dManifest.xmlにAppのthemeを割り当てました.これは重要ではありません.あなたもシステムが持っているテーマを継承してもいいです.どのテーマも引き継がないです.実現にはあまり関係がないです.どうやって利用すればいいですか?次の2つのテーマはtheme_0026 quot;skyとtheme_グレースはtheme_から継承されています.defaultは、このようにして色属性に繰り返し値を与えないために、例えば文字の色はこの3つのテーマの中で同じです.継承すれば、もう一回書くのは避けられます.注意:App全体でテーマの属性を使うには、各スタイル内の各属性は全部そろっていることを保証しなければなりません.例えば、theme_クラスにカラーがなければprimryという属性は、コードがこの属性を引用すると異常が発生します.運行時の異常に注意してください.コンパイルする時には間違いがないです.ですから、妥当なやり方はbase styleを書いて、他のスタイルを引き継ぎます.これは少なくとも崩壊しません.
最後にテーマの色の属性を各Viewに使います.


        ......

またはatr/を省略してもいいです.

最後に、テーマのコードを動的に切り替えることです.
package lx.af.demo.activity.test;

import android.app.Activity;
import android.content.res.Resources;
import android.os.Bundle;
import android.support.annotation.AttrRes;
import android.support.annotation.ColorInt;
import android.util.TypedValue;
import android.view.View;

import lx.af.demo.R;

/**
 * author: lx
 * date: 17-2-24
 */
public class ThemeChangeActivity extends Activity implements View.OnClickListener {

    private static int sCurrentTheme = R.style.theme_default;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        //        setContentView()   ,      .
        //                BaseActivity   .
        setTheme(sCurrentTheme);

        setContentView(R.layout.activity_change_theme);
        findViewById(R.id.btn_switch_theme_default).setOnClickListener(this);
        findViewById(R.id.btn_switch_theme_sky).setOnClickListener(this);
        findViewById(R.id.btn_switch_theme_grass).setOnClickListener(this);

        //           attr           
        View primaryColorPanel = findViewById(R.id.primary_color_panel);
        primaryColorPanel.setBackgroundColor(getCurrentPrimaryColor());
    }

    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.btn_switch_theme_sky:
                changeTheme(R.style.theme_sky);
                break;
            case R.id.btn_switch_theme_grass:
                changeTheme(R.style.theme_grass);
                break;
            case R.id.btn_switch_theme_default:
                changeTheme(R.style.theme_default);
                break;
        }
    }

    private void changeTheme(int theme) {
        //                   SharedPreferences   .
        //            101, 102, 103,        ,              .
        //           ,         .
        sCurrentTheme = theme;
        
        //    Activity.recreate()       Activity.onCreate()         .
        //              ,           .
        recreate();
    }

    //             
    public int getCurrentPrimaryColor() {
        return getColorByAttributeId(R.attr.color_primary);
    }

    //                 
    @ColorInt
    private int getColorByAttributeId(@AttrRes int attrIdForColor){
        TypedValue typedValue = new TypedValue();
        Resources.Theme theme = getTheme();
        theme.resolveAttribute(attrIdForColor, typedValue, true);
        return typedValue.data;
    }
}
このActivityに対応するレイアウトファイルは以下の通りです.



    

    

    

    

        

        

        

        
    

    


このように複数のテーマの問題を解決しました.新たに一つのテーマを加えるのもとても簡単です.default、色の値を複写すればいいです.下から穴が落ちます.
穴を掘る
このピットは非常に乱れています.各リソースの対象は各バージョンで表現が異なります.しかし、ピットの原因は基本的に一致しています.大体、xml資源の中で、atr定義の色を引用したら、xml資源を引用する時に問題があるかもしれません.とりあえずこのような資源をatr-xml資源と呼びます.
注意:手元の最低バージョンの設備はAndroid 4.1.2(API level 16)であり、最高はAndroid 7.0(API level 24)であるため、この範囲を超えては実測がない.
AppComput
Android 6.0(API level 23)以下の機器では、Activityがv 23.0以上のAppCopComppatパッケージからAppCompatActivityを継承していないと、atr-xmlリソースを使用すると、様々な奇妙な問題が発生する可能性があります.したがって、これによって発生したさまざまな問題を具体的に検討しない.
解決方法:たとえば、以下のColorStatitリソースres/color/color_selector_primary.xmlがある:


    
    
    

TextViewのandroid:textColor=@color/color_selector_primaryに引用されると、赤いフォントにレンダリングされます.
方法1:Activity AppComppatActivityを継承すればいい(v 7互換ライブラリのバージョンはv 23.0より大きい).compile 'com.android.support:appcompat-v7:25.2.0' public class ThemeChangeActivity extends AppCompatActivity { ... }方法2:対応ライブラリコントロールの使用に変更しました.例えばTextViewをAppComppatTextViewに変更しました.
    
明らかに、第一の方法はもっと簡単です.dialogなどの少数のところではこの方法で解決できないことを除いて、他のところではAppComppatActivityを継承する方法で解決するのが一番便利です.第二の方法は一つずつ変えなければなりません.以下の議論はActivityが既にAppCompantActivityを継承した上で行います.
background
これこそ本当のピットです.上ではandroid:textColor属性のためにselector(ColorStteList)を設置していますが、実は私達がよく使うのはandroid:background属性のためにselectorを設置しています.backgroundはdrawableタイプのselectorしか受け付けられません.例えば、次のようなselectorタイプのdrawable res/drawable/selector_primary.xmlがあります.


    
        
            
        
    

    
        
            
        
    

Android 5.0(API level 21)以下のマシンでは、drawable xmlリソースからatrを引用し、layoutレイアウトにこのようなdrawable資源を引用すると、崩壊を誘発します.以下は切り取りの崩壊traceの中の断片です.
03-14 11:21:30.092  4920  4920 E AndroidRuntime: Caused by: java.lang.UnsupportedOperationException: Can't convert to color: type=0x2
03-14 11:21:30.092  4920  4920 E AndroidRuntime:    at android.content.res.TypedArray.getColor(TypedArray.java:326)
03-14 11:21:30.092  4920  4920 E AndroidRuntime:    at android.graphics.drawable.GradientDrawable.inflate(GradientDrawable.java:951)
03-14 11:21:30.092  4920  4920 E AndroidRuntime:    at android.graphics.drawable.Drawable.createFromXmlInner(Drawable.java:895)
03-14 11:21:30.092  4920  4920 E AndroidRuntime:    at android.graphics.drawable.StateListDrawable.inflate(StateListDrawable.java:183)
03-14 11:21:30.092  4920  4920 E AndroidRuntime:    at android.graphics.drawable.Drawable.createFromXmlInner(Drawable.java:895)
03-14 11:21:30.092  4920  4920 E AndroidRuntime:    at android.graphics.drawable.Drawable.createFromXml(Drawable.java:828)
03-14 11:21:30.092  4920  4920 E AndroidRuntime:    at android.content.res.Resources.loadDrawable(Resources.java:1933)
03-14 11:21:30.092  4920  4920 E AndroidRuntime:    ... 31 more
ブロガーはまだこの問題を解決する方法を見つけていません.いい方法があれば、教えてください.だから迂回するしかないです.コードの中に手動でColorStteListを組み立てたり、Drawableなどはbackground背景の資源を作ることができます.
ColorStatit Listはこのように組み立てられます.
private static ColorStateList createColorStateList(Context context) {
  return new ColorStateList(
      new int[][]{
          new int[]{android.R.attr.state_pressed}, // pressed state.
          StateSet.WILD_CARD,                      // other state.
      },
      new int[]{
          getThemeAttrColor(context, R.attr.color_primary_pressed),  // pressed state.
          getThemeAttrColor(context, R.attr.color_primary),          // other state.
      });
}
drawableはこのように組み立てられます.
public static Drawable createDrawableSelector(Context context) {
    StateListDrawable stateDrawable = new StateListDrawable();
    GradientDrawable normalDrawable = new GradientDrawable();
    GradientDrawable pressedDrawable = new GradientDrawable();
    GradientDrawable disabledDrawable = new GradientDrawable();

    int[][] states = new int[4][];
    states[0] = new int[]{android.R.attr.state_enabled, android.R.attr.state_pressed};
    states[1] = new int[]{android.R.attr.state_enabled, android.R.attr.state_focused};
    states[3] = new int[]{-android.R.attr.state_enabled}; // disabled state
    states[2] = new int[]{android.R.attr.state_enabled};

    //         drawable    attr    
    normalDrawable.setColor(getColorByAttrId(context, R.attr.color_primary));
    pressedDrawable.setColor(getColorByAttrId(context, R.attr.color_primary_pressed));
    disabledDrawable.setColor(getColorByAttrId(context, R.attr.color_primary_disabled));

    //         drawable        .     ,    .
    normalDrawable.setCornerRadius(5);
    pressedDrawable.setCornerRadius(5);
    disabledDrawable.setCornerRadius(5);

    stateDrawable.addState(states[0], pressedDrawable);
    stateDrawable.addState(states[1], pressedDrawable);
    stateDrawable.addState(states[3], disabledDrawable);
    stateDrawable.addState(states[2], normalDrawable);

    return stateDrawable;
}

@ColorInt
public static int getColorByAttrId(Context context, @AttrRes int attrIdForColor) {
    TypedValue typedValue = new TypedValue();
    Resources.Theme theme = context.getTheme();
    theme.resolveAttribute(attrIdForColor, typedValue, true);
    return typedValue.data;
}
コードによって生成されたこのdrawableをView.setBackground(Drawable background)方法で設定すれば、効果は有効になります.これによって、いくつかのよく使われているコントロールをカプセル化して、例えばTextViewを使って、関連する仕事を簡略化します.
文末大神さんの文章では、6.0のベクトル図を使って、AppComppatを使って下に互換性があります.この問題の影響を受けずに、drawableがatrの色を引用することができます.これは実測がありません.この方法が使えても、所有するdrawable xmlをすべてベクトル図に変更することはありません.子供たちは興味があってもこの方法を試してみてもいいです.
理由:
原因に関心のない学生はこのセクションを省略することができます.簡単に言えば、この問題はAPI<21のシステムでは、Resource.getColor()メソッドはthemeパラメータを受信していません.Android 5.0以下で、コードの中で色の値を取得します.この方法でしか使えません.Resoueces.getColor(@ColorRes int id)上の文は5.0以上のSDKで、android studio(lint)と警告します.警告我々の方法はすでにDeprecatedであり、以下の方法を使用することを提案しています.Resources.getColor(@ColorRes int id, @Nullable Theme theme) Contactクラスにも互換性のある方法が提供されています.
この方法は最終的に次のようなコンパイルを呼び出します.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
  return context.getResources().getColor(id, context.getTheme());
} else {
  return context.getResources().getColor(id);
}
私たちが定義したatrの色の値は直接テーマと関連しています.したがって、5.0より早いバージョンでは、themeパラメータは層ごとに渡されていません.関連コントロールは必ず対応する色の値が取れません.drawableの問題は類似しています.
また一つ穴があります
これは小さいピットです.或いは、これは私達がプログラミングする時避けなければならない誤りです.ここで一緒に提出します.小さな仲間が穴を踏まないようにします.本稿で検討したatr資源はテーマと直接関連していますので、異なるContectで得られた資源は違っています.例えば、私たちは都合のいいように、アプリケーション全体でAppplicationの例を保存しています.このように、静的な方法で色などの資源を取ることができます.例えば、以下の簡単なヘルプがあります.
public final class ResourceUtils {

    private static Application sApp;
    private static Resources sRes;

    private ResourceUtils() {
    }

    public static void init(Application app) {
        sApp = app;
        sRes = app.getResources();
    }

    public static String getString(int resId) {
        return sRes.getString(resId);
    }

    public static int getColor(int resId) {
        return sRes.getColor(resId);
    }
}
上記の種類はApple.onCreate()の中で初期化して、静的な方法で資源を取得することができます.しかし、この方法では、Appplication Conteetで資源を取得します.その行為とActivityのContectで資源を取得すると違います.資源とテーマに関連している場合、その資源も違っています.具体的にはContectWrapperに関する知識を勉強してください.(ブロガーはまだ詳しく研究していないので、書くのをやめて、みんなを誤解させないようにします.).だからテーマに関する資源を取る時は、今のActivityのContextを使うようにしてください.
参考:google code issue stackover flowe question Style Colors&Drawables w/The me Attributes