美文网首页Android知识
动态换肤三(加载皮肤包中的资源)

动态换肤三(加载皮肤包中的资源)

作者: radish520like | 来源:发表于2018-05-15 22:26 被阅读0次

前言

  上一篇文章,不但获取到了所有的 View,还将需要换肤的 View 进行了筛选并且保存在了 List 中,那么接下来,就需要获取皮肤包中的资源,然后让这些 View 加载这些资源就 OK 了。

上一篇文章地址:https://www.jianshu.com/p/1e180d8ed33b

生成皮肤包

  加载皮肤包的资源,首先要生成皮肤包,当然,按理来说这个应该是从网上下载下来的,但是我们现在自己造一个皮肤包,保存到 sdcard 中。
  继续看我们 MainActivity 的布局文件,现在只有一个 LinearLayout 和第二个 TextView 可以换肤(该 TextView 可以换背景和字体颜色),其中,background 属性对应的值是 ?attr/colorAccent 和 @color/black ,textColor 属性对应的值是 @color/black。
  接下来我们新建一个项目,用这个新项目生成皮肤包,该项目不需要写什么代码,就将上述这些对应的资源进行设置即可,假如说我们将背景设置成绿色,将字体设置成红色,然后生成 apk,导入到 sdcard 中。
cd sdcard/
app-skin-debug.apk

获取皮肤包中的资源

  导入成功后,我们就先要加载这个 apk,然后获取其资源,具体怎么获取呢?首先我们要获取路径,然后将路径设置到 AssetManager 中,然后根据这个 AssetManager 获取皮肤包中的 Resources,获取皮肤包的包名。接下来我们需要将主包中的资源和皮肤包中的资源进行转换, 怎么转换?假如属性的值是 R.drawable.ic_launcher,我们先获取他的名字 ic_launcher,再获取他的类型 drawable,然后通过 Resources.getIdentifier(name,type,packageName);就可以获得类型为 drawable 的 ic_launcher 在 packageName 中的资源 id 的值。

import android.app.Application;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.text.TextUtils;
import android.widget.Toast;

import com.radish.android.skin_core.util.SkinPreference;
import com.radish.android.skin_core.util.SkinResources;

import java.io.File;
import java.lang.reflect.Method;
import java.util.Observable;

/**
 * 皮肤管理器
 */

public class SkinManager extends Observable {

    private static SkinManager instance = null;

    public static void init(Application application){
        if(instance == null){
            synchronized (SkinManager.class){
                if(instance == null){
                    instance = new SkinManager(application);
                }
            }
        }
    }

    public static SkinManager getInstance(){
        return instance;
    }

    private Application application;

    private SkinManager(Application application){
        this.application = application;
        application.registerActivityLifecycleCallbacks(new SkinActivityLifecycle());
        SkinResources.init(application);
        SkinPreference.init(application);

    }

    /**
     * 加载皮肤包
     * @param path  皮肤包的路径
     */
    public void loadSkin(String path) throws Exception {
        if(TextUtils.isEmpty(path)){
            //如果是空,就加载默认资源,清空皮肤包的配置
            SkinPreference.getInstance().setSkin("");
            SkinResources.getInstance().reset();
        }else{
            File file = new File(path);
            if(!file.exists()){
                throw new Exception("您加载的资源包不存在");
            }

        /*
            这里,由于 AssetMaanger 的限制比较大,我们通过反射来获取 AssetManager 实例对象,
            然后给他设置路径的方法是被隐藏了,我们继续使用反射的方式来获取。
         */
            AssetManager assetManager = AssetManager.class.newInstance();
            Method method = assetManager.getClass().getMethod("addAssetPath", String.class);
            method.setAccessible(true);
            method.invoke(assetManager,path);

            //这个是我们应用的 Resources
            Resources resources = application.getResources();
            //这个是皮肤包对应的 Resources
            Resources skinResources = new Resources(assetManager,resources.getDisplayMetrics(),resources.getConfiguration());

            //获取包名
            PackageManager packageManager = application.getPackageManager();
            PackageInfo info = packageManager.getPackageArchiveInfo(path, PackageManager.GET_ACTIVITIES);
            if(info != null){
                String packageName = info.packageName;
                //设置后可以获取对应皮肤包中的资源了
                SkinResources.getInstance().applySkin(skinResources,packageName);
                //设置完成后,记得保存一下皮肤包的路径
                SkinPreference.getInstance().setSkin(path);
            }else{
                Toast.makeText(application, "加载皮肤资源包失败", Toast.LENGTH_SHORT).show();
            }
        }
        //通知观察者进行更新
        setChanged();
        notifyObservers();
    }
}
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;

/**
 * 资源转换工具类
 */

public class SkinResources {

    private static SkinResources instance;
    private String packageName;

    private SkinResources(Context context){
        this.mAppResources = context.getResources();
    }

    public static void init(Context context){
        if(instance == null){
            synchronized (SkinResources.class){
                if(instance == null){
                    instance = new SkinResources(context);
                }
            }
        }
    }

    private Resources mSkinResources;
    private Resources mAppResources;
    /**
     * 用于表示是否加载默认皮肤
     */
    private boolean isDefaultSkin = true;

    public static SkinResources getInstance(){
        return instance;
    }

    public void applySkin(Resources resources,String packageName){
        this.mSkinResources = resources;
        this.packageName = packageName;
        isDefaultSkin = TextUtils.isEmpty(packageName) || (resources == null);
    }

    public int getIdentifier(int resId){
        String resourceEntryName = mAppResources.getResourceEntryName(resId);
        String resourceTypeName = mAppResources.getResourceTypeName(resId);
        return mSkinResources.getIdentifier(resourceEntryName, resourceTypeName, packageName);
    }

    public int getColor(int resId){
        if(isDefaultSkin){
            return resId;
        }
        int skinId = getIdentifier(resId);
        if(skinId == 0){
            return mAppResources.getColor(resId);
        }
        return mSkinResources.getColor(skinId);
    }

    public ColorStateList getColorStateList(int resId){
        if(isDefaultSkin){
            return mAppResources.getColorStateList(resId);
        }
        int skinId = getIdentifier(resId);
        if(skinId== 0){
            return mAppResources.getColorStateList(resId);
        }
        return mSkinResources.getColorStateList(skinId);
    }

    public Drawable getDrawable(int resId){
        if(isDefaultSkin){
            return mAppResources.getDrawable(resId);
        }
        int skinId = getIdentifier(resId);
        if(skinId == 0){
            return mAppResources.getDrawable(resId);
        }
        return mSkinResources.getDrawable(skinId);
    }

    /**
     * 这里返回值写成 Object 是因为 background 可能是
     * color 也可能是 drawable
     */
    public Object getBackground(int resId){
        String resourceTypeName = mAppResources.getResourceTypeName(resId);
        if("color".equals(resourceTypeName)){
            return getColor(resId);
        }else{
            return getDrawable(resId);
        }
    }

    public String getString(int resId){
        if(isDefaultSkin){
            return mAppResources.getString(resId);
        }
        int skinId = getIdentifier(resId);
        if(skinId == 0){
            return mAppResources.getString(resId);
        }
        return mSkinResources.getString(skinId);
    }

    public void reset() {
        mSkinResources = null;
        packageName = "";
        isDefaultSkin = true;
    }
}
/**
 * 保存 Util
 */

public class SkinPreference {

    private static final String SKIN_SHARED = "skins";

    private static final String KEY_SKIN_PATH = "skin_path";

    private static SkinPreference instance;

    private SharedPreferences mPref;

    private SkinPreference(Context context){
        mPref = context.getSharedPreferences(SKIN_SHARED,Context.MODE_PRIVATE);
    }

    public static void init(Context context){
        if(instance == null){
            synchronized (SkinPreference.class){
                if(instance == null){
                    instance = new SkinPreference(context);
                }
            }
        }
    }

    public static SkinPreference getInstance(){
        return instance;
    }

    public void setSkin(String path){
        mPref.edit().putString(KEY_SKIN_PATH,path).apply();
    }

    public String getSkin(){
        return mPref.getString(KEY_SKIN_PATH,null);
    }
}
import android.app.Activity;
import android.app.Application;
import android.os.Bundle;
import android.view.LayoutInflater;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

/**
 * Actiity 声明周期回调监听
 */

public class SkinActivityLifecycle implements Application.ActivityLifecycleCallbacks {

    private Map<Activity,SkinLayoutFactory> mLayoutFactoryMap = new HashMap<>();

    @Override
    public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
        LayoutInflater layoutInflater = LayoutInflater.from(activity);
        try {
            /*
                这里需要注意一点,在调用 setFactory2的时候,源码有一个判断,
                if (mFactorySet) {
                    throw new IllegalStateException("A factory has already been set on this LayoutInflater");
                }
                这个意思就是 Android 布局加载器通过 mFactorySet 标记是否设置过 Factory,如果设置过就会抛出异常
                那么我们就强制这个 mFactorySet 是 false

             */
            Field field = LayoutInflater.class.getDeclaredField("mFactorySet");
            field.setAccessible(true);
            field.setBoolean(layoutInflater,false);
        } catch (Exception e) {
            e.printStackTrace();
        }
        SkinLayoutFactory skinLayoutFactory = new SkinLayoutFactory();
        layoutInflater.setFactory2(skinLayoutFactory);
        //注册观察者
        SkinManager.getInstance().addObserver(skinLayoutFactory);
        mLayoutFactoryMap.put(activity,skinLayoutFactory);
    }

    @Override
    public void onActivityStarted(Activity activity) {

    }

    @Override
    public void onActivityResumed(Activity activity) {

    }

    @Override
    public void onActivityPaused(Activity activity) {

    }

    @Override
    public void onActivityStopped(Activity activity) {

    }

    @Override
    public void onActivitySaveInstanceState(Activity activity, Bundle outState) {

    }

    @Override
    public void onActivityDestroyed(Activity activity) {
        //删除观察者
        SkinLayoutFactory skinLayoutFactory = mLayoutFactoryMap.get(activity);
        SkinManager.getInstance().deleteObserver(skinLayoutFactory);
    }
}

应用皮肤包中的资源

  现在皮肤包中的资源已经能获取到了,需要换肤的 View 也有了,我们只需要将需要换肤的 View 中某个属性的值换成皮肤包中的资源,就行了。
  首先要初始化我们的框架,SkinManager.init(application);这个写到Application 的 onCreate()方法里面即可。

public class MainActivity extends AppCompatActivity {

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

        findViewById(R.id.tv_click).setOnClickListener(v -> {
            //开始换肤
            try {
                SkinManager.getInstance().loadSkin("/sdcard/app-skin-debug.apk");
            } catch (Exception e) {
                e.printStackTrace();
            }
        });

        findViewById(R.id.tv_other).setOnClickListener(v -> {
            //还原
            try {
                SkinManager.getInstance().loadSkin("");
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
    }
}

  刚打开 app 我们的界面是这样的


换肤前

  点击换肤按钮


换肤后
  如果我们这时候关闭应用,重新打开,界面还是这样的,因为我们保存了。
  还原按钮点击后
还原

那么简单的初始化换肤就完成了。

下一篇文章地址:https://www.jianshu.com/p/30815e811c7c

相关文章

  • 动态换肤三(加载皮肤包中的资源)

    前言   上一篇文章,不但获取到了所有的 View,还将需要换肤的 View 进行了筛选并且保存在了 List 中...

  • Android 换肤

    1). 换肤思路 在源应用APP中,下载皮肤包,使得对应的文件资源得以应用。使用DexClassLoader加载资...

  • 动态换肤一(前期预备知识)

      动态换肤框架是仿照网易云音乐来换肤的,换肤的方式就是通过解压 apk 文件从中获取到皮肤包的资源,然后替换我们...

  • Android主题换肤框架 无缝切换

    Android-Skin-Loader 一个通过动态加载本地皮肤包进行换肤的皮肤框架 工程目录介绍 用法 1. 在...

  • Android-动态加载插件资源,皮肤包的实现原理

    原创-转载请注明出处 Android动态加载插件资源 最近在看app的换肤功能。简单的来说就是动态读取插件apk中...

  • 2.3 Android 换肤原理

    Android 换肤原理 制作皮肤包,皮肤包相当于一个apk,不过只包含了资源文件 获取到皮肤包的Resource...

  • 动态换肤

    写一个只包含皮肤资源信息的apk包,资源名称和换肤应用名称保持一致。 换肤应用在setContentView方法内...

  • Android App实现动态换肤

    准备阶段 皮肤包是什么样的文件? 动态换肤的思想是什么? 皮肤包是什么样的文件? 我们通过解析网易云音乐的皮肤包来...

  • Android 动态换肤原理与实现

    概述 本文主要分享类似于酷狗音乐动态换肤效果的实现。 动态换肤的思路: 收集换肤控件以及对应的换肤属性 加载插件皮...

  • iOS:JS对象转换为Dictionary/NSDictiona

    前言 项目中目前实现了通过动态下载资源包来替换widget皮肤的功能,资源包中包括js代码文件和资源图片. js文...

网友评论

    本文标题:动态换肤三(加载皮肤包中的资源)

    本文链接:https://www.haomeiwen.com/subject/jiyjdftx.html