问题
我想要在运行时覆盖我的应用程序资源,如R.color.brand_color或R.drawable.ic_action_start。我的应用程序连接到将提供品牌色彩和图像的CMS系统。一旦应用程序下载了CMS数据,它需要能够重新设置自身外观。
我知道你马上就要说了 - 在运行时覆盖资源是不可能的。
但事实上,它有点可能。特别是我发现了这篇来自2012年的学士论文,它解释了基本概念 - Android中的Activity类扩展了ContextWrapper
,其中包含了attachBaseContext方法。您可以覆盖attachBaseContext以使用自己的自定义类包装Context,该自定义类覆盖了getColor和getDrawable等方法。您自己的getColor实现可以根据需要查找颜色。 Calligraphy库使用类似的方法注入一个自定义LayoutInflator,可以处理加载自定义字体。
代码
我创建了一个简单的Activity,使用此方法覆盖颜色的加载。
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
protected void attachBaseContext(Context newBase) {
super.attachBaseContext(new CmsThemeContextWrapper(newBase));
}
private class CmsThemeContextWrapper extends ContextWrapper{
private Resources resources;
public CmsThemeContextWrapper(Context base) {
super(base);
resources = new Resources(base.getAssets(), base.getResources().getDisplayMetrics(), base.getResources().getConfiguration()){
@Override
public void getValue(int id, TypedValue outValue, boolean resolveRefs) throws NotFoundException {
Log.i("ThemeTest", "Getting value for resource " + getResourceName(id));
super.getValue(id, outValue, resolveRefs);
if(id == R.color.theme_colour){
outValue.data = Color.GREEN;
}
}
@Override
public int getColor(int id) throws NotFoundException {
Log.i("ThemeTest", "Getting colour for resource " + getResourceName(id));
if(id == R.color.theme_colour){
return Color.GREEN;
}
else{
return super.getColor(id);
}
}
};
}
@Override
public Resources getResources() {
return resources;
}
}
}
问题是,它不起作用!日志显示调用了加载资源的函数,例如layout/activity_main和mipmap/ic_launcher,但color/theme_colour从未被加载。看起来上下文被用于创建窗口和操作栏,但不是活动内容视图。
我的问题是 - 如果不是活动的上下文,布局填充器从哪里加载资源?我还想知道 - 是否有可行的方法在运行时覆盖颜色和可绘制的加载?
关于替代方法的说明
我知道可以从CMS数据的其他方式主题化应用程序 - 例如,我们可以创建一个方法getCMSColour(String key)
,然后在我们的onCreate()
中编写以下代码:
myTextView.setTextColour(getCMSColour("heading_text_colour"))
对于可绘制对象、字符串等也可以采用类似的方法。然而,这将导致大量的样板代码,而且所有这些代码都需要维护。修改 UI 时很容易忘记设置特定视图的颜色。
封装 Context 以返回我们自己的自定义值更加“简洁”,而且不太容易出现问题。在探索替代方法之前,我想了解为什么它不起作用。
ContextWrapper
在膨胀其布局时有效,而您的ContextWrapper
则无效,则根据定义,Calligraphy的ContextWrapper
和您的ContextWrapper
之间存在某些差异。现在,我还没有使用过Calligraphy,它的ContextWrapper
可能与您的类似失败。 - CommonsWare