普通的开发时,如果要给一个View设置背景颜色,通常会这样
view.setBackgroundColor(context.getResources().getColor(R.color.black));
context.getResources()
返回一个Resources
对象,里面有getColor(...)
,getString(...)
等方法,可以通过这些方法返回颜色字符串等。那么,如果我们如果能从另一个地方获取Resources
对象,例如一个皮肤包,然后读取里面的颜色图片等,是不是就可以换肤了呢。
AssetManager提供了这么一个方法,通过path更改Asset路径.该方法是hide的。
public int addAssetPath(String path) {
return addAssetPathInternal(path, false /*overlay*/, false /*appAsLib*/);
}
然后Resources的构造方法是这样的:
public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config) {
this(null);
mResourcesImpl = new ResourcesImpl(assets, metrics, config, new DisplayAdjustments());
}
所以,想要通过皮肤包创建一个Resources,需要这样写:
private Resources getAsseResources(Context context, String skinApkPath) {
try {
Method method = AssetManager.class.getDeclaredMethod("addAssetPath", String.class);
AssetManager assetManager = AssetManager.class.newInstance();
method.invoke(assetManager, skinApkPath);
return new Resources(assetManager,
context.getResources().getDisplayMetrics(),
context.getResources().getConfiguration()
);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
首先,创建一个Hello World!工程,包名为MyAppliaction,这时MainActivity
应该有一个hello world的textview
。
然后再res/values文件下创建一个colors.xml文件,里面添加一个颜色,我这里取名text_color,颜色为红色
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="text_color">#f00</color>
</resources>
再在MainActivity
中这个textview
设置为text_color
的颜色
textView.setTextColor(getResources().getColor(R.color.text_color));
然后,我们来创建皮肤包
创建一个空的工程,直接命名为MyAppliaction2
同样res/values文件夹下创建colors.xml文件,把颜色改为绿色,name还是text_color不变
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="text_color">#0f0</color>
</resources>
将这个MyAppliaction2项目打包生成apk。这就是一个皮肤包了。然后不用安装,直接放到手机目录。为了省事,我直接放到外部存储的根目录。
如上图,app-debug.apk即为皮肤包。
注意,如果皮肤包放在外部存储,主应用(此文为MyAppliaction)一定要添加读写SD卡的权限,并赋予权限。
皮肤包做成了,如何使用这个皮肤包呢。
创建一个工具类SkinUtil.java
public class SkinUtil {
private static SkinUtil skinUtil;
private Resources mResources;
private String mPath;
private Context mContext;
private String skinPackageName;
private SkinUtil(Context context) {
this.mPath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/app-debug.apk";//皮肤包路径。如果放在外部存储,一定要添加外部存储的读写权限
this.mResources = getAsseResources(context, mPath);
skinPackageName = getApkInfo(context,mPath);
}
public static SkinUtil getInstance(Context context) {
if (skinUtil == null) {
skinUtil = new SkinUtil(context);
}
skinUtil.mContext = context;
return skinUtil;
}
//获取皮肤包的Resources
private Resources getAsseResources(Context context, String skinApkPath) {
try {
Method method = AssetManager.class.getDeclaredMethod("addAssetPath", String.class);
AssetManager assetManager = AssetManager.class.newInstance();
method.invoke(assetManager, skinApkPath);
return new Resources(assetManager,
context.getResources().getDisplayMetrics(),
context.getResources().getConfiguration()
);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public int getColor(int id) {
//通过本应用R文件的id获取name
String name = mContext.getResources().getResourceEntryName(id);
//通过name获取其在皮肤包的R文件中的id
int identifier = mResources.getIdentifier(name, "color", skinPackageName);
if (identifier == 0) {
//如果没有查到,返回当前的应用的颜色
return mContext.getResources().getColor(id);
}
//根据皮肤包的R文件中的id获取颜色
return mResources.getColor(identifier);
}
public Drawable getDrawable(int id) {
//通过本应用R文件的id获取name
String name = mContext.getResources().getResourceEntryName(id);
//通过name获取其在皮肤包的R文件中的id
int identifier = mResources.getIdentifier(name, "drawable", skinPackageName);
if (identifier == 0) {
//如果没有查到,返回当前的应用的颜色
// return mContext.getResources().getColor(id);
return null;
}
//根据皮肤包的R文件中的id获取颜色
// return mResources.getColor(identifier);
return mResources.getDrawable(identifier);
}
//通过文件路径解析包名
public String getApkInfo(Context context, String apkPath) {
try {
PackageManager pm = context.getPackageManager();
PackageInfo info = pm.getPackageArchiveInfo(apkPath, PackageManager.GET_ACTIVITIES);
if (info != null) {
ApplicationInfo appInfo = info.applicationInfo;
return appInfo.packageName; //得到安装包名称
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
getAsseResources
方法就是前文提到的获取皮肤包的资源文件,getColor
方法就是获取皮肤包的color,返回一个color的16进制值。然后通过setTextColor设置这个值就可以了。把前文的setTextColor
修改成使用SkinUtil的getcolor
方法。
textView.setTextColor(SkinUtil.getInstance(this).getColor(R.color.text_color));
这里介绍了getcolor的方法,如果是drawable、string原理都是的一样的,通先获取当前资源id对应的name,然后用这个name获取其在皮肤包中的R文件对应的id,然后再返回具体的颜色图片的。
这里有个缺陷,不能处理setContentView(R.layout.XXX)
的颜色,而且设置颜色需要把Context.getResources.get...
换成SkinUtil.getInstance(context).get..
,如果是开发初还可以依次改,如果是一个开发了很久的项目,改的地方太多了。为了解决这个问题,下一篇讲如何用hook修改全局属性。