Android 经常使用换肤方式以及原理分析

经常使用方法

1.经过Theme切换主题

经过在setContentView以前设置Theme实现主题切换。 在styles.xml定义一个夜间主题和白天主题:android

<style name="LightTheme" parent="Theme.AppCompat.Light.DarkActionBar">
    <item name="colorPrimary">@color/colorPrimary</item>
    <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
    <item name="colorAccent">@color/colorAccent</item>
    <!--主题背景-->
    <item name="backgroundTheme">@color/white</item>
</style>

<style name="BlackTheme" parent="Theme.AppCompat.Light.DarkActionBar">
    <item name="colorPrimary">@color/colorPrimary</item>
    <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
    <item name="colorAccent">@color/colorAccent</item>
    <!--主题背景-->
    <item name="backgroundTheme">@color/dark</item>
</style>
复制代码

设置主要切换主题View的背景:git

<android.support.constraint.ConstraintLayout 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:background="?attr/backgroundTheme"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="切换主题"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</android.support.constraint.ConstraintLayout>
复制代码

切换主题:github

经过调用setTheme()app

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setTheme(R.style.BlackTheme);
    setContentView(R.layout.activity_main);
}


finish();
Intent intent = getIntent();
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(intent);
overridePendingTransition(0, 0);
复制代码

效果以下: 框架

2.经过AssetManager切换主题

下载皮肤包,经过AssetManager加载皮肤包里面的资源文件,实现资源替换。ide

ClassLoader

Android能够经过classloader获取已安装apk或者未安装apk、dex、jar的context对象,从而经过反射去获取Class、资源文件等。测试

加载已安装应用的资源

//获取已安装app的context对象
Context context = ctx.getApplicationContext().createPackageContext("com.noob.resourcesapp", 		Context.CONTEXT_INCLUDE_CODE | Context.CONTEXT_IGNORE_SECURITY);
//获取已安装app的resources对象
Resources resources = context.getResources();
//经过resources获取classloader,反射获取R.class
Class aClass = context.getClassLoader().loadClass("com.noob.resourcesapp.R$drawable");
int resId = (int) aClass.getField("icon_collect").get(null);
imageView.setImageDrawable(resources.getDrawable(id));
复制代码

加载未安装应用的资源

String apkPath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/test.apk";
//经过反射获取未安装apk的AssetManager
AssetManager assetManager = AssetManager.class.newInstance();
//经过反射增长资源路径
Method method = assetManager.getClass().getMethod("addAssetPath", String.class);
method.invoke(assetManager, apkPath);
File dexDir = ctx.getDir("dex", Context.MODE_PRIVATE);
if (!dexDir.exists()) {
    dexDir.mkdir();
}
//获取未安装apk的Resources
Resources resources = new Resources(assetManager, ctx.getResources().getDisplayMetrics(),
        ctx.getResources().getConfiguration());
//获取未安装apk的ClassLoader
ClassLoader classLoader = new DexClassLoader(apkPath, dexDir.getAbsolutePath(), null, ctx.getClassLoader());
//反射获取class
Class aClass = classLoader.loadClass("com.noob.resourcesapp.R$drawable");
int id = (int) aClass.getField("icon_collect").get(null);
imageView.setImageDrawable(resources.getDrawable(id));
复制代码

LayoutInflater.Factory

分析setContentView源码

LayoutInflater.Factory是如何被调用的 this

setContentView最终调用了inflate方法,咱们来看一下inflate方法的源码
inflate最终调用了createViewFromTag方法来建立View,在这之中用到了factory, 若是factory存在就用factory建立对象,若是不存在就由系统本身去建立

咱们在setContentView以前调用测试代码 测试代码:spa

LayoutInflater.from(this).setFactory(new LayoutInflater.Factory() {
        @Override
        public View onCreateView(String name, Context context, AttributeSet attrs) {
            Log.e("MainActivity", "name :" + name);
            int count = attrs.getAttributeCount();
            for (int i = 0; i < count; i++) {
                Log.e("MainActivity", "AttributeName :" + attrs.getAttributeName(i) + "AttributeValue :"+ attrs.getAttributeValue(i));
            }
            return null;
        }
});
复制代码

log日志: 3d

结果发现咱们能够获取一个layout的全部View,此时咱们就能够对View进行皮肤切换效果。

经过AssetManager切换主题总结

经过AssetManager和LayoutInflater.Factory配合就能够达到调用外部资源获取皮肤的方法。若是想要动态更新,只须要把须要动态更新的View存起来,去遍历设置皮肤,或者用eventBus去通知也能够。

对比

上述两种方法是市面上大多数换肤框架的实现原理。
经过Theme切换主题:
优势:实现简单,配置简单
缺点:须要重启应用;是固定皮肤,不能动态切换
经过AssetManager切换主题:
优势:不须要重启应用;能够动态加载主题,用于盈利 缺点:实现较为复杂;皮肤包比较占资源

项目地址:github.com/JavaNoober/…

相关文章
相关标签/搜索