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

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

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

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

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

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

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

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

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

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="main_btn_color">#E61ABD</color>
    <color name="main_background">#38F709</color>

    <color name="second_btn_color">#000000</color>
    <color name="second_background">#FFFFFF</color>

</resources>

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

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

public class MainActivity extends Activity implements ISkinUpdate,OnClickListener
{
	private Button btn_main;
	private View main_view;

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

		SkinApplication.getInstance().mActivitys.add(this);
		btn_main=(Button)this.findViewById(R.id.btn_main);
    btn_main.setOnClickListener(this);

		main_view=this.findViewById(R.id.main_view);

	}

    @Override
    protected void onResume() {
      super.onResume();
      if(SkinPackageManager.getInstance(this).mResources!=null)
      {
        updateTheme();
        Log.d("yzy", "onResume-->updateTheme");
      }
    }

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		// Inflate the menu; this adds items to the action bar if it is present.
		getMenuInflater().inflate(R.menu.main, menu);
		return true;
	}

	@Override
	public boolean onOptionsItemSelected(MenuItem item) {
		int id = item.getItemId();
		if (id == R.id.action_settings) {
			//Toast.makeText(this, "change skin", 1000).show();
			File dir=new File(Environment.getExternalStorageDirectory(),"plugins");

			File skin=new File(dir,"SkinPlugin.apk");
			if(skin.exists())
			{
				  SkinPackageManager.getInstance(MainActivity.this).loadSkinAsync(skin.getAbsolutePath(), new loadSkinCallBack() {
          @Override
          public void startloadSkin()
          {
            Log.d("yzy", "startloadSkin");
          }

          @Override
          public void loadSkinSuccess() {
            Log.d("yzy", "loadSkinSuccess");
            MainActivity.this.sendBroadcast(new Intent(SkinBroadCastReceiver.SKIN_ACTION));
          }

          @Override
          public void loadSkinFail() {
            Log.d("yzy", "loadSkinFail");
          }
        });
			}
			return true;
		}
		return super.onOptionsItemSelected(item);
	}

	@Override
	public void updateTheme()
	{
		// TODO Auto-generated method stub
		if(btn_main!=null)
		{
			try {
				Resources mResource=SkinPackageManager.getInstance(this).mResources;
				Log.d("yzy", "start and mResource is null-->"+(mResource==null));
				int id1=mResource.getIdentifier("main_btn_color", "color", "com.skin.plugin");
				btn_main.setBackgroundColor(mResource.getColor(id1));
				int id2=mResource.getIdentifier("main_background", "color","com.skin.plugin");
				main_view.setBackgroundColor(mResource.getColor(id2));
				//img_skin.setImageDrawable(mResource.getDrawable(mResource.getIdentifier("skin", "drawable","com.skin.plugin")));
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}

	@Override
	protected void onDestroy() {
		// TODO Auto-generated method stub
		SkinApplication.getInstance().mActivitys.remove(this);
		super.onDestroy();
	}

	@Override
	public void onClick(View v) {
		// TODO Auto-generated method stub
		if(v.getId()==R.id.btn_main)
		{
			Intent intent=new Intent(this,SecondActivity.class);
			this.startActivity(intent);
		}
	}
}

这段代码里面主要看onOptionsItemSelected,这个方法里面,通过资源apk路径,拿到该资源apk对应Resources对象。我们直接看看SkinPacakgeManager里面做了什么吧

/**
 * 解析皮肤资源包
 * com.skin.demo.SkinPackageManager
 * @author yuanzeyao <br/>
 * create at 2015年1月3日 下午3:24:16
 */
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);
  }

  /**
   * 加载资源的回调接口
   * com.skin.demo.loadSkinCallBack
   * @author yuanzeyao <br/>
   * create at 2015年1月4日 下午1:45:48
   */
  public static interface loadSkinCallBack
  {
    public void startloadSkin();

    public void loadSkinSuccess();

    public void loadSkinFail();
  }

}

调用loadSkinAsync后,如果成功,就会发送一个换肤广播,并将当前皮肤apk的路径保存到sp中,便于下次启动app是直接加载该皮肤资源。

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

public class SkinApplication extends Application
{
	private static SkinApplication mInstance=null;

	public ArrayList<ISkinUpdate> mActivitys=new ArrayList<ISkinUpdate>();

	@Override
	public void onCreate() {
		// TODO Auto-generated method stub
		super.onCreate();
		mInstance=this;
		String skinPath=SkinConfig.getInstance(this).getSkinResourcePath();
		if(!TextUtils.isEmpty(skinPath))
		{
		  //如果已经换皮肤,那么第二次进来时,需要加载该皮肤
		  SkinPackageManager.getInstance(this).loadSkinAsync(skinPath, null);
		}

		SkinBroadCastReceiver.registerBroadCastReceiver(this);
	}

	public static SkinApplication getInstance()
	{
		return mInstance;
	}

	@Override
	public void onTerminate() {
		// TODO Auto-generated method stub
		SkinBroadCastReceiver.unregisterBroadCastReceiver(this);
		super.onTerminate();
	}

	public void changeSkin()
	{
		for(ISkinUpdate skin:mActivitys)
		{
			skin.updateTheme();
		}
	}
}

由于这里换肤仅仅是更换icon,背景色之类的,所以比较简单,如果要更换布局文件,那就稍微要复杂一些,这里就不再介绍了,有兴趣的可以自己去研究..

时间: 2024-08-19 11:06:42

轻松实现Android 更换皮肤(主题)的相关文章

Android更换皮肤解决方案

Android更换皮肤解决方案 转载请注明出处:IT_xiao小巫 本篇博客要给大家分享的一个关于Android应用换肤的Demo,大家可以到我的github去下载demo,以后博文涉及到的代码均会上传到github中统一管理. github地址:https://github.com/devilWwj/Android-skin-update 思路 换肤功能一般有什么? 元素一般有背景颜色.字体颜色.图片.布局等等 我们知道Android中有主题Theme还有style,theme是针对整个act

[ExtJS5学习笔记]第二十九节 sencha ext js 5.1.0中动态更换皮肤主题

本文地址:http://blog.csdn.net/sushengmiyan/article/details/42016107 本文作者:sushengmiyan ------------------------------------------------------------------------------------------------------------------------------------ 为方便起见,使用sencha cmd创建一个工程,使用app buil

[ExtJS5学习笔记]sencha ext js 5.1.0中动态更换皮肤主题

本文地址:http://blog.csdn.net/sushengmiyan/article/details/42016107 本文作者:sushengmiyan ------------------------------------------------------------------------------------------------------------------------------------ 为方便起见,使用sencha cmd创建一个工程,使用app buil

android 简单的更换皮肤

更换皮肤 1.更换皮肤其实就是更换Activity的背景图片 直接上代码: Activity: 1 public class MainActivity extends Activity implements OnClickListener { 2 3 private SkinManager skinManager; 4 private int downNums; 5 private Button btn; 6 7 @Override 8 protected void onCreate(Bundl

apk分享: Android应用更换皮肤功能的实现思路教程。

Android 的发展确实太快了,每年的都有很多新东西出现,想要覆盖所有新东西感觉也不太可能,我这里主要说一下主要的 Android 的主要新技术发展,其实了解 Android 的发展趋势,可能对开发者更有帮助. 开发工具 Android Studio: Google 官方放弃 Eclipse 和 Android Studio 普及.AS 虽然不算新,但是对 Android Studio 这个软件的更新速度快的惊人,有大量的新功能发布.例如支持很多注解代码提示注解.Live code templ

visual studio 2012更换皮肤、功能添加

首先在vs2012的菜单:工具->扩展和更新,打开扩展和更新窗口,点击左侧“联机”,搜索栏里面输入Theme Editor.然后点击按钮,安装之后,在工具->选项->环境常规 面板上面颜色主题下拉框,就可以选择换肤了.附图 visual studio 2012更换皮肤.功能添加

怎样给 VS 更换皮肤

微软的 Visual Studio 是目前最为流行的编程工具.在新版的 Visual Studio 中,提供了三种皮肤可供大家选择.那么,到底怎样给 Visual Studio 更换皮肤呢? 工具/原料 Visaul Studio 方法: 启动 Visual Studio.   点击菜单“工具”-“选项”.   在打开的选项窗口中,在左侧选择“环境”-“常规”.   此时,选择右侧“视觉体验”一项下不同的颜色主题,即可调整 Visual Studio 的皮肤外观.   下面是选择浅色皮肤后的界面

Android Studio显示主题/样式设置

估计很多刚开始用Android Studio的DEV,都有经常看到网上关于Android Studio的贴图是灰色样式的,但是为啥自己刚安装的就是白色样式的呢. 这个其实只要改下显示主题就可以了. 如下图,选择Darcula就可以了,IntelliJ是默认风格,Windows这个风格其实颜色和IntelliJ是差不多的: Android Studio显示主题/样式设置,布布扣,bubuko.com

pycharm 皮肤主题及个性化设置

1.设置IDE皮肤主题 File -> Settings -> IDE Settings -> Appearance -> Theme -> 选择“Alloy.IDEA Theme” 2.设置编辑器“颜色与字体”主题 File -> Settings -> IDE Settings -> Editor -> Colors & Fonts -> Scheme name -> 选择“Default” 说明:先选择“Default”,再“