轻松实现Android 更换皮肤(主题)

转自:http://blog.csdn.net/yuanzeyao/article/details/42390431

目前很多app都具有换肤功能,可以根据用户自己的喜好定制自己的界面,比如新浪微博,网易新闻等等。今天这里我就是要介绍一种机制实现app换肤。

我找了几款app换肤的应用,换肤基本都是更换了界面的Icon,背景图片,背景色等等,基本没有遇到更换布局的,其实布局也是可以更换的,但是觉得没有必要。所以这篇文章讲解的换肤也是指换icon,背景图片等资源。

通过网络搜索我发现网上上提供了大概这么集中换肤机制:

1、直接将皮肤包放入apk中,这种方案实现非常简单,但是不够灵活,而且还将apk搞大了。

2、将皮肤做成一个独立的apk文件,并和项目工程公用一个shareUsedId,并拥有相同的签名。这种方案较第一种方案就是灵活性比较大,缺点就是需要用户安装,新浪微博目前使用的就是这种方案。


我今天要介绍的这种方案和第二种比较类似,但是我的资源包是不要安装的,毕竟用户一般愿意装一些乱七八糟的应用。

在学习这篇文章之前最好学习我的前一篇文章《Android资源管理机制分析》,因为皮肤管理其实就是资源的管理。下面开始学习如何换肤吧


1、首先我们需要准备一个皮肤包,这个皮肤包里面不会包含任何Activity,里面只有资源文件,这里我为了简单,仅仅加入一个color.xml(其实就相当于Android系统中的framework_res.apk)

  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <resources>  
  3.     <color name="main_btn_color">#E61ABD</color>  
  4.     <color name="main_background">#38F709</color>  
  5.       
  6.     <color name="second_btn_color">#000000</color>  
  7.     <color name="second_background">#FFFFFF</color>  
  8.       
  9. </resources>  


2、将该资源打包成apk文件,放入sd卡中(实际项目你可以从我网络下载)

3、将需要换肤的Activity实现ISkinUpdate(这个可以自己随便定义名称)接口


  1. public class MainActivity extends Activity implements ISkinUpdate,OnClickListener  
  2. {  
  3.     private Button btn_main;  
  4.     private View main_view;  
  5.   
  6.     @Override  
  7.     protected void onCreate(Bundle savedInstanceState) {  
  8.         super.onCreate(savedInstanceState);  
  9.       this.setContentView(R.layout.activity_main);  
  10.           
  11.         SkinApplication.getInstance().mActivitys.add(this);  
  12.         btn_main=(Button)this.findViewById(R.id.btn_main);  
  13.     btn_main.setOnClickListener(this);  
  14.           
  15.         main_view=this.findViewById(R.id.main_view);  
  16.           
  17.           
  18.           
  19.           
  20.     }  
  21.           
  22.     @Override  
  23.     protected void onResume() {  
  24.       super.onResume();  
  25.       if(SkinPackageManager.getInstance(this).mResources!=null)  
  26.       {  
  27.         updateTheme();  
  28.         Log.d("yzy""onResume-->updateTheme");  
  29.       }  
  30.     }  
  31.   
  32.     @Override  
  33.     public boolean onCreateOptionsMenu(Menu menu) {  
  34.         // Inflate the menu; this adds items to the action bar if it is present.  
  35.         getMenuInflater().inflate(R.menu.main, menu);  
  36.         return true;  
  37.     }  
  38.   
  39.     @Override  
  40.     public boolean onOptionsItemSelected(MenuItem item) {  
  41.         int id = item.getItemId();  
  42.         if (id == R.id.action_settings) {  
  43.             //Toast.makeText(this, "change skin", 1000).show();  
  44.             File dir=new File(Environment.getExternalStorageDirectory(),"plugins");  
  45.               
  46.             File skin=new File(dir,"SkinPlugin.apk");  
  47.             if(skin.exists())  
  48.             {  
  49.                   SkinPackageManager.getInstance(MainActivity.this).loadSkinAsync(skin.getAbsolutePath(), new loadSkinCallBack() {  
  50.           @Override  
  51.           public void startloadSkin()   
  52.           {  
  53.             Log.d("yzy""startloadSkin");  
  54.           }  
  55.             
  56.           @Override  
  57.           public void loadSkinSuccess() {  
  58.             Log.d("yzy""loadSkinSuccess");  
  59.             MainActivity.this.sendBroadcast(new Intent(SkinBroadCastReceiver.SKIN_ACTION));  
  60.           }  
  61.             
  62.           @Override  
  63.           public void loadSkinFail() {  
  64.             Log.d("yzy""loadSkinFail");  
  65.           }  
  66.         });  
  67.             }  
  68.             return true;  
  69.         }  
  70.         return super.onOptionsItemSelected(item);  
  71.     }  
  72.   
  73.     @Override  
  74.     public void updateTheme()   
  75.     {  
  76.         // TODO Auto-generated method stub  
  77.         if(btn_main!=null)  
  78.         {  
  79.             try {  
  80.                 Resources mResource=SkinPackageManager.getInstance(this).mResources;  
  81.                 Log.d("yzy""start and mResource is null-->"+(mResource==null));  
  82.                 int id1=mResource.getIdentifier("main_btn_color""color""com.skin.plugin");  
  83.                 btn_main.setBackgroundColor(mResource.getColor(id1));  
  84.                 int id2=mResource.getIdentifier("main_background""color","com.skin.plugin");  
  85.                 main_view.setBackgroundColor(mResource.getColor(id2));  
  86.                 //img_skin.setImageDrawable(mResource.getDrawable(mResource.getIdentifier("skin", "drawable","com.skin.plugin")));  
  87.             } catch (Exception e) {  
  88.                 // TODO Auto-generated catch block  
  89.                 e.printStackTrace();  
  90.             }  
  91.         }  
  92.     }  
  93.       
  94.     @Override  
  95.     protected void onDestroy() {  
  96.         // TODO Auto-generated method stub  
  97.         SkinApplication.getInstance().mActivitys.remove(this);  
  98.         super.onDestroy();  
  99.     }  
  100.   
  101.     @Override  
  102.     public void onClick(View v) {  
  103.         // TODO Auto-generated method stub  
  104.         if(v.getId()==R.id.btn_main)  
  105.         {  
  106.             Intent intent=new Intent(this,SecondActivity.class);  
  107.             this.startActivity(intent);  
  108.         }  
  109.     }  
  110. }  

这段代码里面主要看onOptionsItemSelected,这个方法里面,通过资源apk路径,拿到该资源apk对应Resources对象。我们直接看看SkinPacakgeManager里面做了什么吧
  1. /** 
  2.  * 解析皮肤资源包 
  3.  * com.skin.demo.SkinPackageManager 
  4.  * @author yuanzeyao <br/> 
  5.  * create at 2015年1月3日 下午3:24:16 
  6.  */  
  7. public class SkinPackageManager   
  8. {  
  9.   private static SkinPackageManager mInstance;  
  10.   private Context mContext;  
  11.   /** 
  12.    * 当前资源包名 
  13.    */  
  14.   public String mPackageName;  
  15.     
  16.   /** 
  17.    * 皮肤资源 
  18.    */  
  19.   public Resources mResources;  
  20.     
  21.   private SkinPackageManager(Context mContext)  
  22.   {  
  23.     this.mContext=mContext;  
  24.   }  
  25.     
  26.   public static SkinPackageManager getInstance(Context mContext)  
  27.   {  
  28.     if(mInstance==null)  
  29.     {  
  30.       mInstance=new SkinPackageManager(mContext);  
  31.     }  
  32.       
  33.     return mInstance;  
  34.   }  
  35.     
  36.     
  37.   /** 
  38.    * 异步加载皮肤资源 
  39.    * @param dexPath 
  40.    *        需要加载的皮肤资源 
  41.    * @param callback 
  42.    *        回调接口 
  43.    */  
  44.   public void loadSkinAsync(String dexPath,final loadSkinCallBack callback)  
  45.   {  
  46.     new AsyncTask<String,Void,Resources>()  
  47.     {  
  48.   
  49.       protected void onPreExecute()   
  50.       {  
  51.         if(callback!=null)  
  52.         {  
  53.           callback.startloadSkin();  
  54.         }  
  55.       };  
  56.      
  57.       @Override  
  58.       protected Resources doInBackground(String... params)   
  59.       {  
  60.         try {  
  61.           if(params.length==1)  
  62.           {  
  63.             String dexPath_tmp=params[0];  
  64.             PackageManager mPm=mContext.getPackageManager();  
  65.             PackageInfo mInfo=mPm.getPackageArchiveInfo(dexPath_tmp,PackageManager.GET_ACTIVITIES);  
  66.             mPackageName=mInfo.packageName;  
  67.               
  68.               
  69.             AssetManager assetManager = AssetManager.class.newInstance();  
  70.             Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);  
  71.             addAssetPath.invoke(assetManager, dexPath_tmp);  
  72.               
  73.             Resources superRes = mContext.getResources();  
  74.             Resources skinResource=new Resources(assetManager, superRes.getDisplayMetrics(), superRes.getConfiguration());  
  75.             SkinConfig.getInstance(mContext).setSkinResourcePath(dexPath_tmp);  
  76.             return skinResource;  
  77.           }  
  78.           return null;  
  79.         } catch (Exception e) {  
  80.           return null;  
  81.         }   
  82.           
  83.       };  
  84.         
  85.       protected void onPostExecute(Resources result)   
  86.       {  
  87.         mResources=result;  
  88.          
  89.         if(callback!=null)  
  90.         {  
  91.           if(mResources!=null)  
  92.           {  
  93.             callback.loadSkinSuccess();  
  94.           }else  
  95.           {  
  96.             callback.loadSkinFail();  
  97.           }  
  98.         }  
  99.       };  
  100.         
  101.     }.execute(dexPath);  
  102.   }  
  103.     
  104.   /** 
  105.    * 加载资源的回调接口 
  106.    * com.skin.demo.loadSkinCallBack 
  107.    * @author yuanzeyao <br/> 
  108.    * create at 2015年1月4日 下午1:45:48 
  109.    */  
  110.   public static interface loadSkinCallBack  
  111.   {  
  112.     public void startloadSkin();  
  113.       
  114.     public void loadSkinSuccess();  
  115.       
  116.     public void loadSkinFail();  
  117.   }  
  118.     
  119.     
  120.    
  121. }  
调用loadSkinAsync后,如果成功,就会发送一个换肤广播,并将当前皮肤apk的路径保存到sp中,便于下次启动app是直接加载该皮肤资源。


接受换肤广播是在SkinApplication中注册的,当接收到此广播后,随即调用所有已经启动,并且需要换肤的Activity的updateTheme方法,从而实现换肤。

  1. public class SkinApplication extends Application   
  2. {  
  3.     private static SkinApplication mInstance=null;  
  4.       
  5.     public ArrayList<ISkinUpdate> mActivitys=new ArrayList<ISkinUpdate>();  
  6.       
  7.     @Override  
  8.     public void onCreate() {  
  9.         // TODO Auto-generated method stub  
  10.         super.onCreate();  
  11.         mInstance=this;  
  12.         String skinPath=SkinConfig.getInstance(this).getSkinResourcePath();  
  13.         if(!TextUtils.isEmpty(skinPath))  
  14.         {  
  15.           //如果已经换皮肤,那么第二次进来时,需要加载该皮肤  
  16.           SkinPackageManager.getInstance(this).loadSkinAsync(skinPath, null);  
  17.         }  
  18.           
  19.         SkinBroadCastReceiver.registerBroadCastReceiver(this);  
  20.     }  
  21.       
  22.     public static SkinApplication getInstance()  
  23.     {  
  24.         return mInstance;  
  25.     }  
  26.       
  27.     @Override  
  28.     public void onTerminate() {  
  29.         // TODO Auto-generated method stub  
  30.         SkinBroadCastReceiver.unregisterBroadCastReceiver(this);  
  31.         super.onTerminate();  
  32.     }  
  33.       
  34.     public void changeSkin()  
  35.     {  
  36.         for(ISkinUpdate skin:mActivitys)  
  37.         {  
  38.             skin.updateTheme();  
  39.         }  
  40.     }  
  41. }  

由于这里换肤仅仅是更换icon,背景色之类的,所以比较简单,如果要更换布局文件,那就稍微要复杂一些,这里就不再介绍了,有兴趣的可以自己去研究..
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值