1. 思路?
对于实现插件式换肤一般来说我们有2种比较常见的方法:
思路一:
android support library23.2 支持白天和黑夜切换的(最简单,直接切换主题即可),但是这个只有白天和黑夜2套皮肤的,并且这2套皮肤是在apk里边的;
思路二:
如果需要多套皮肤来回切换,可以把皮肤从服务器下载到本地,然后直接从本地获取皮肤,这里有个问题就是,如何获取另一个apk中的资源,这里指的就是apk中的ImageView图片;
2. 效果实现
2.1>:我们写一个SkinPaign的demo,然后就只在drawable下边放一张 image_src图片,然后运行,等运行成功后,会在这个目录下边生成一个 app-debug.apk;
2.2>:然后把这个apk复制到桌面,给它重命名为 red.skin,或者其他的名称都可以,只要是 以.skin结尾的都可以,然后把这个 red.skin文件复制到 手机目录存储中,就表示我们已经把皮肤从服务器中下载到本地,这里只是做演示,就把它复制到手机存储中;
2.3>:然后我们可以通过获取另一个apk中的资源,从而获取到该资源中的图片,然后我点击一下换肤的按钮,就要把我手机上边的图片换成从手机目录中获取到的那张图片,达到 换肤的目的;
3. 效果如下图所示
点击之前:
点击之后,就替换成从手机目录中 的 red.skin文件中获取的图片:
4. 思路如下
1>:写一个demo,里边就只在drawable中放一张 图片,不做任何操作,然后会在app - build - outputs - apk下边生成一个 app-debug.apk;
2>:把这个apk复制到桌面,然后重命名为 red.skin,或者其他的以 .skin结尾的都可以;
3>:这里为了测试效果,就直接把 red.skin 文件复制到手机存储目录,表示已经从服务器下载了 皮肤到本地;
4>:然后点击 换肤的 按钮,就需要从手机目录中读取 red.skin文件,然后从中获取到 它drawable下边的那一张图片;
5>:然后用获取到的图片 替换 换肤按钮 下边的 这张图片即可;
5. 注意
这里会涉及到 ImageView的src、TextView的textColor中 资源是如何加载的源码知识,如果不是特别了解的,可以先去看下我之前的文章
ImageView的src、TextView的textColor等资源加载源码
1>:不能直接 new AssetManager()来创建对象,源码如下:
/**
* Create a new AssetManager containing only the basic system assets.
* Applications will not generally use this method, instead retrieving the
* appropriate asset manager with {@link Resources#getAssets}. Not for
* use by applications.
* {@hide}
*/
public AssetManager() {
synchronized (this) {
if (DEBUG_REFS) {
mNumRefs = 0;
incRefsLocked(this.hashCode());
}
init(false);
if (localLOGV) Log.v(TAG, "New asset manager: " + this);
ensureSystemAssets();
}
}
发现 AssetManager上边是 hide,hide就表示只能够系统调用,我们如果想要调用只能用反射,来创建AssetManager对象;
2>:不能直接调用 asset.addAssetPath(String path),源码如下:
/**
* Add an additional set of assets to the asset manager. This can be
* either a directory or ZIP file. Not for use by applications. Returns
* the cookie of the added asset, or 0 on failure.
* {@hide}
*/
public final int addAssetPath(String path) {
synchronized (this) {
int res = addAssetPathNative(path, appAsLib);
makeStringBlocks(mStringBlocks);
return res;
}
}
发现这个方法也是 hide,hide就表示只能够系统调用,我们如果想要调用,就只能够通过反射,来调用 addAssetPath()方法
6. 代码如下
1>:activity_main布局文件如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="cn.novate.essayjoke_day01.MainActivity">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/test_btn"
android:text="换肤"
/>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/test_iv"
android:src="@drawable/image_src"
android:layout_marginTop="10dp"
/>
</LinearLayout>
2>:在MainActivity中,点击 换肤 按钮,然后利用反射从手机目录中获取 red.skin中的资源图片,然后替换本地图片即可,代码如下:
/**
* Email: 2185134304@qq.com
* Created by JackChen 2018/4/14 20:03
* Version 1.0
* Params:
* Description: 换肤测试 示例demo
*/
public class MainActivity extends BaseSkinActivity implements View.OnClickListener{
private Button test_btn;
private ImageView test_iv;
@Override
protected void initData() {
}
@Override
protected void initView() {
test_btn = (Button) findViewById(R.id.test_btn);
test_btn.setOnClickListener(this);
test_iv = (ImageView) findViewById(R.id.test_iv);
}
@Override
protected void initTitle() {
}
@Override
protected void setContentView() {
setContentView(R.layout.activity_main);
}
/**
* 1>:写一个demo,里边就只在drawable中放一张 图片,不做任何操作,然后会在app - build - outputs - apk下边生成一个 app-debug.apk;
* 2>:把这个apk复制到桌面,然后重命名为 red.skin,或者其他的以 .skin结尾的都可以;
* 3>:这里为了测试效果,就直接把 red.skin 文件复制到手机存储目录,表示已经从服务器下载了 皮肤到本地;
* 4>:然后点击 换肤的 按钮,就需要从手机目录中读取 red.skin文件,然后从中获取到 它drawable下边的那一张图片;
* 5>:然后用获取到的图片 替换 换肤按钮 下边的 这张图片;
*
* 以上,就达到换肤的目的;
*/
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.test_iv:
try {
// 读取本地的一个 .skin里面的资源
Resources superRes = getResources() ;
// 创建AssetsManager
// 不能直接 new AssetManager() ;
// 通过反射来创建 asset对象
AssetManager asset = AssetManager.class.newInstance() ;
// 添加本地下载好的 资源皮肤,就是复制到 手机目录中的 red.skin
// 不能直接调用 addAssetPath()方法,只能通过反射调用 该方法
// 参数1:表示方法名称 参数2:表示方法里边的参数类型 如果是String path -> String.class int path -> int.class 等等
Method method = AssetManager.class.getDeclaredMethod("addAssetPath" , String.class) ;
method.setAccessible(true); // 设置权限,防止addAssetPath()方法是私有private的
// 反射执行addAssetPath()方法 File.separator就和 "/" 是一样的
method.invoke(asset , Environment.getExternalStorageDirectory().getAbsolutePath() +
File.separator + "red.skin") ;
Resources resources = new Resources(asset , superRes.getDisplayMetrics() , superRes.getConfiguration()) ;
// 获取资源 id
// 参数1:表示图片的名称 参数2:表示类型 参数3:表示包名
int drawableId = resources.getIdentifier("image_src", "drawable", "cn.novate.skinpagin");
Drawable drawable = resources.getDrawable(drawableId) ;
test_iv.setImageDrawable(drawable);
} catch (Exception e) {
e.printStackTrace();
}
break;
}
}
}
代码已上传至github:
https://github.com/shuai999/EssayJoke_day1-Two-Qi-.git