动态设置主题颜色

Set theme color dynamically

我在我的 android 应用程序中(动态地)使用主题,如下所示:

my_layout.xml(摘录):

<TextView
    android:id="@+id/myItem"
    style="?my_item_style" />

attrs.xml(摘录):

<attr name="my_item_style" format="reference" />

themes.xml(摘录):

<style name="MainTheme.Blue">
      <item name="my_item_style">@style/my_item_style_blue</item>
</style>

<style name="MainTheme.Green">
      <item name="my_item_style">@style/my_item_style_green<item>
</style>

styles.xml(摘录):

<style name="my_item_style_blue">
      <item name="android:textColor">@color/my_blue</item>
</style>

<style name="my_item_style_green">
      <item name="android:textColor">@color/my_blue</item>
</style>

因此,如您所见,我正在动态设置主题。我正在使用这个 class:

public class ThemeUtils {

  private static int sTheme;
  public final static int THEME_BLUE = 1;
  public final static int THEME_GREEN = 2;

  public static void changeToTheme(MainActivity activity, int theme) {
      sTheme = theme;
      activity.startActivity(new Intent(activity, MyActivity.class));
  }

  public static void onActivityCreateSetTheme(Activity activity)
  {
      switch (sTheme)
      {
          default:
          case THEME_DEFAULT:
          case THEME_BLUE:
              activity.setTheme(R.style.MainTheme_Blue);
              break;
          case THEME_GREEN:
              activity.setTheme(R.style.MainTheme_Green);
              break;
      }
  }

}

我想知道的是,有没有办法在代码中做到这一点(更改主题颜色)?例如,我有以下代码(摘录):

((TextView) findViewById(R.id.myItem)).setTextColor(R.color.blue);

这可以通过一些辅助方法来完成,该方法将对可用主题使用 switch 命令,对主题使用 return 正确颜色。但是我想知道有没有更好更好更快的方法

谢谢!

如果我正确理解你正在寻找一种方法来

  1. 从主题中提取样式,
  2. 从所述样式中提取一个值(文本颜色)。

让我们开始吧。

// Extract ?my_item_style from a context/activity.
final TypedArray a = context.obtainStyledAttributes(new int[] { R.attr.my_item_style });
@StyleRes final int styleResId = a.getResourceId(0, 0);
a.recycle();

// Extract values from ?my_item_style.
final TypedArray b = context.obtainStyledAttributes(styleResId, new int[] { android.R.attr.textColor });
final ColorStateList textColors = b.getColorStateList(0);
b.recycle();

// Apply extracted values.
if (textColors != null) {
    textView.setTextColor(textColors);
}

一些注意事项:

  1. TypedArray 不支持在较旧的 API 关卡的颜色状态列表中获取支持矢量可绘制对象和主题参考。如果您愿意使用 AppCompat internal API,您可能想尝试 TintTypedArray.
  2. 一直分配 int[] 成本很高,让它成为 static final
  3. 如果您想一次解析多个属性,则必须对属性数组进行排序!否则它有时会崩溃。 <declare-styleable> 为您生成这样的数组和相应的索引。

通过 Intent 传递主题 ID 怎么样?

Intent intent = new Intent(activity, MyActivity.class);
intent.putExtra("theme", R.style.MainTheme_Green);
activity.startActivity(intent);

然后在onCreate:

// assuming that MainTheme_Blue is default theme
setTheme(getIntent().getIntExtra("theme", R.style.MainTheme_Blue));

你检查过这个 MultipleThemeMaterialDesign 演示了吗?

设置活动:

@Override
protected void onCreate(Bundle savedInstanceState) {
    Preferences.applyTheme(this);
    getDelegate().installViewFactory();
    getDelegate().onCreate(savedInstanceState);
    super.onCreate(savedInstanceState);
    setToolbar();
    addPreferencesFromResource(R.xml.preferences);
    Preferences.sync(getPreferenceManager());
    mListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
        @Override
        public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
            Preferences.sync(getPreferenceManager(), key);
            if (key.equals(getString(R.string.pref_theme))) {
                finish();
                final Intent intent = IntentCompat.makeMainActivity(new ComponentName(
                        SettingsActivity.this, MainActivity.class));
                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | IntentCompat.FLAG_ACTIVITY_CLEAR_TASK);
                startActivity(intent);
            }
        }
    };
}

查看演示的完整示例。

我终于用下面的方法完成了:

public static int getColor(String colorName) {
    Context ctx = getContext();
    switch (sTheme) {
        default:
        case THEME_DEFAULT:
            return ctx.getResources().getIdentifier("BLUE_" + colorName, "color", ctx.getPackageName());
        case THEME_BLUE:
            return ctx.getResources().getIdentifier("BLUE_" + colorName, "color", ctx.getPackageName());
        case THEME_GREEN:
            return ctx.getResources().getIdentifier("GREEN_" + colorName, "color", ctx.getPackageName());
    }
}

这个 returns 颜色根据我的主题(我使用了前缀)。

鉴于每个资源都是 R class 中的一个字段,您可以使用反射查找它们。这很昂贵,但由于您将获得一个 int 值,因此您可以在获得它们后存储它们并避免性能下降。并且由于使用资源的方法采用任何int,您可以使用一个int变量作为占位符,然后将所需的颜色放入其中。

获得任何资源:

String awesomeColor = "blue";
int color = getResourceId(R.color, awesomeColor, false);
if(blue>0) ((TextView) findViewById(R.id.myItem)).setTextColor(color);

函数:

public static int getResourceId(Class rClass, String resourceText, boolean showExceptions){

        String key = rClass.getName()+"-"+resourceText;

        if(FailedResourceMap.containsKey(key)) return 0;
        if(ResourceMap.containsKey(key)) return ResourceMap.get(rClass.getName()+"-"+resourceText);

        try {

            String originalText = resourceText;
            if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.GINGERBREAD){
                resourceText = ValidationFunctions.normalizeText(resourceText);
            }
            resourceText = resourceText.replace("?", "").replace("  ", " ").replace(" ", "_").replace("(", "").replace(")", "");

            int resource = rClass.getDeclaredField(resourceText).getInt(null);
            ResourceMap.put(rClass.getName()+"-"+originalText, resource);

            return resource;
        } catch (IllegalAccessException | NullPointerException e) {
            FailedResourceMap.put(key, 0);
            if(showExceptions) e.printStackTrace();
        } catch (NoSuchFieldException e) {
            FailedResourceMap.put(key, 0);
            if(showExceptions) e.printStackTrace();
        }

        return 0;
    }

工作版本在这里:https://github.com/fcopardo/AndroidFunctions/blob/master/src/main/java/com/grizzly/functions/TextFunctions.java

此处理对任何 android 资源都有效。你也可以这样设置主题而不是使用中间变量:

public static void onActivityCreateSetTheme(Activity activity)
  {
    int theme = getResourceId(R.style, activity.getClass().getSimpleName(), false);
    if(theme > 0) activity.setTheme(theme);
  }