android app换肤详解

来源:互联网 发布:中行外汇分析软件 编辑:程序博客网 时间:2024/05/16 16:20

前言

现在很多APP中都会有换肤功能,看着很神奇,一键点击app大换样,那么这篇就来简单阐释一下换肤是如何实现的

效果图

这里写图片描述

图1

这里写图片描述

图2

换肤实现方式?

软件换肤从功能上可以划分三种:

1) 软件内置多个皮肤,不可由用户增加或修改;

最低的自由度,软件实现相对于后两种最容易。

2) 官方提供皮肤供下载,用户可以使用下载的皮肤;

用户可选择下载自己喜欢的皮肤,有些玩家会破解皮肤的定制方法,自己做皮肤使用,或者传到网上给大家用。

3) 官方提供皮肤制作工具或方法,用户可自制皮肤。

这种方式使用户有参与感,自由度较高。用户可根据自己的喜好定制软件的皮肤。有些软件官网提供皮肤定制的工具或者方法,我建议最好有可视化带向导的工具。用户只要自己找一些图片、修改文字的字体替换就可以了。用户可以上传自制的皮肤,提供其他用户下载,还可以赚得一些虚拟货币或者奖品什么的。这种一般都是打包为.zip格式的。扩展名可由各公司自定义,有制作工具的话直接导出来最方便。

首先我们要弄清楚换肤的定义,软件皮肤包括图标字体布局交互风格等,换肤就是换掉皮肤包括的部分或所有资源。

前面提到的三种皮肤,从软件实现上来看,它们的本质区别是皮肤是否内置到应用程序中。对于内置的实现比较简单,只要在开发应用的过程中设计几套皮肤供用户选择。这里用到的知识不超过Android基础,不详细讲解。

这里主要介绍外置方式

采用压缩包方式(zip/apk)

首先需要了解Context原理,android资源管理机制分析,这两篇blog

换肤分为几个步骤

1.触发换肤逻辑

2.提前下载/触发时下载 皮肤资源包(apk,zip)

3.使用AssetManager创建 皮肤资源包Resource 对象

4.通知宿主app进行换肤动作。(需提前收集需要换肤的界面)

根据上面的步骤,写的demo

package com.nuoyuan.utils.changetheme;import android.content.Context;import android.content.pm.PackageInfo;import android.content.pm.PackageManager;import android.content.res.AssetManager;import android.content.res.Resources;import android.os.AsyncTask;import com.nuoyuan.utils.base.SkinConfig;import java.lang.reflect.Method;/** * Author: weichyang * Date:   2017/10/12 * Description: 解析皮肤资源包 */public class SkinPackageManager   {    private static SkinPackageManager mInstance;    private Context mContext;  /**    * 当前资源包名    */    public String mPackageName;    /**    * 皮肤资源    */    public Resources mResources;  private SkinPackageManager(Context mContext)    {      this.mContext=mContext;    }    public static SkinPackageManager getInstance(Context mContext)    {      if(mInstance==null)      {        mInstance=new SkinPackageManager(mContext);      }      return mInstance;    }    /**    * 异步加载皮肤资源    * @param dexPath    *        需要加载的皮肤资源    * @param callback    *        回调接口    */    public void loadSkinAsync(String dexPath,final loadSkinCallBack callback)    {      new AsyncTask<String,Void,Resources>()    {        protected void onPreExecute()         {          if(callback!=null)          {            callback.startloadSkin();          }        };        @Override        protected Resources doInBackground(String... params)         {          try {            if(params.length==1)            {              String dexPath_tmp=params[0];              PackageManager mPm=mContext.getPackageManager();            PackageInfo mInfo=mPm.getPackageArchiveInfo(dexPath_tmp,PackageManager.GET_ACTIVITIES);            mPackageName=mInfo.packageName;            AssetManager assetManager = AssetManager.class.newInstance();            Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);            addAssetPath.invoke(assetManager, dexPath_tmp);              Resources superRes = mContext.getResources();              Resources skinResource=new Resources(assetManager, superRes.getDisplayMetrics(), superRes.getConfiguration());              SkinConfig.getInstance(mContext).setSkinResourcePath(dexPath_tmp);            return skinResource;            }            return null;          } catch (Exception e) {            return null;          }         };        protected void onPostExecute(Resources result)         {          mResources=result;          if(callback!=null)          {            if(mResources!=null)            {              callback.loadSkinSuccess();            }else            {              callback.loadSkinFail();            }          }        };      }.execute(dexPath);    }  /**   * Author: weichyang   * Date:   2017/1   * Description:加载资源的回调接口    */  public static interface loadSkinCallBack    {      public void startloadSkin();      public void loadSkinSuccess();      public void loadSkinFail();    }  }  

分析下核心代码

PackageManager mPm=mContext.getPackageManager();            PackageInfo mInfo=mPm.getPackageArchiveInfo(dexPath_tmp,PackageManager.GET_ACTIVITIES);            mPackageName=mInfo.packageName;            AssetManager assetManager = AssetManager.class.newInstance();            Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);            addAssetPath.invoke(assetManager, dexPath_tmp);              Resources superRes = mContext.getResources();              Resources skinResource=new Resources(assetManager, superRes.getDisplayMetrics(), superRes.getConfiguration());  

通过传递的资源apk 路径,用 PackageManager 获取到 资源apk 包名,因为 addAssetPath()为隐藏方法,采用反射进行调用,传递资源路径。然后拼装参数的构建皮肤资源apk的Resources 对象并设置为成员变量返回。

以上步骤就完成了 资源apk Resource的生成,有了Resources 也就有了调用资源文件的权限。

    //进行主题更新    @Override    public void updateTheme() {        try {          // 获取资源apk Resources对象            Resources mResource = SkinPackageManager.getInstance(this).mResources;           //调用colors 方式            int id1 = mResource.getIdentifier("colorPrimary", "color", "theme.nydialog.com.theme");            btn_main.setBackgroundColor(mResource.getColor(id1));            int id2 = mResource.getIdentifier("colorPrimary", "color", "theme.nydialog.com.theme");            main_view.setBackgroundColor(mResource.getColor(id2));//调用图片资源,注意这里采用Drawable方式            linearLayout.setBackground(mResource.getDrawable(mResource.getIdentifier("bg", "drawable","theme.nydialog.com.theme")));        } catch (Exception e) {            // TODO Auto-generated catch block            e.printStackTrace();        }    }

具体细节问题请查看

Nydialog https://github.com/yatou252303/Nydialog

引用
Android应用如何实现换肤功能
http://www.cnblogs.com/suiyc/archive/2011/05/27/2059778.html

原创粉丝点击