`
hxdawxyhxdawxy
  • 浏览: 127300 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

android 皮肤切换

 
阅读更多
转载于 http://blog.csdn.net/suiyc/article/details/6329212


国内有很多的软件都支持皮肤定制,这也是与国外软件重大不同之一,国外用户注重社交、邮件等功能,国内用户则重视音乐、小说、皮肤等功能,本节课程就来讲解Android应用程序如何实现换肤功能。

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

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

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

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

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

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

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

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

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

本节课程重点讲解如何实现皮肤与应用程序分离。

皮肤一般含有多个文件,例如图片、配置等文件,分散的文件不利于传输和使用,最好打包。打包的格式一般选择zip格式。这里分两种情况,一种是apk,例如AdwLauncher,它的桌面皮肤格式是一个apk;另一种是自定义扩展名,例如墨迹天气皮肤扩展名是mja,搜狗输入法的皮肤扩展名是sga,它们的文件格式实际上都是zip。

下面我们分别讲解。

一.apk格式

现在的问题变成了一个应用如何读取另一个apk中的资源。

在android系统中,apk之间可以相互读取数据的条件是:有同样的签名,并且AndroidManifest.xml文件中配置的android:sharedUserId属性值相同,那么两个apk运行在同一个进程中,可以互相访问任意数据。

方法如下:

1) 应用程序和皮肤程序的AndroidManifest.xml中配置

例如: android:sharedUserId="org.yuchen"

2) 文件与应用apk中对同一功能的皮肤文件名要一致

例如:应用程序的背景图片路径:/SkinDemo/res/drawable-hdpi/bg.png

那么皮肤apk中的背景图片文件路径也应该是:

CustomSkin/res/drawable-hdpi/bg.png

3)访问资源的方法

Context context = createPackageContext("com.yuchen.customskin", Context.CONTEXT_IGNORE_SECURITY);


获取到org.yuchen.customskin对应的Context,通过返回的context对象就可以访问到org.yuchen.customskin中的任何资源。

例如:应用apk要获得皮肤apk中的bg.png,
Drawable drawable = context.getResources().getDrawable(R.drawable.bg); 


这样就得到了图片的引用,其他xml资源文件的获取方式也是类似的。

二.自定义扩展名的zip格式的皮肤

技术点在于如何去读取zip文件中的资源以及皮肤文件存放策略。

方案:如果软件每次启动都去读取SD卡上的皮肤文件,速度会比较慢。较好的做法是提供一个皮肤设置的界面,用户选择了哪一个皮肤,就把那个皮肤文件解压缩到”/data/data/[package name]/skin”路径下,这样不需要跨存储器读取,速度较快,而且不需要每次都去zip压缩包中读取,不依赖SD卡中的文件,即使皮肤压缩包文件被删除了也没有关系。

实现方法:

1. 在软件的帮助或者官网的帮助中提示用户将皮肤文件拷贝到SD卡指定路径下。

2. 在软件中提供皮肤设置界面。可以在菜单或者在设置中。可参考墨迹、搜狗输入法、QQ等支持换肤的软件。

3. 加载指定路径下的皮肤文件,读取其中的缩略图,在皮肤设置界面中显示,将用户选中的皮肤文件解压缩到”/data/data/[package name]/skin”路径下。

4. 软件中优先读取”/data/data/[package name]/skin/”路径下的资源。如果没有则使用apk中的资源。

---------------------------------------------------------------------------------
http://gundumw100.iteye.com/blog/1052260


该切换主题的demo里面一共实现了两个功能,其一,搜索已经安装的皮肤,其二,应用安装的皮肤。

主项目包名为org.leepood.skindemo,主题项目的包名为org.leepood.skin.blue,org.leepood.skin.red,等等,只要前缀是org.leepood.skin.就行。

首先是查找已安装主题的代码:
package org.leepood.skindemo;

import java.util.ArrayList;
import java.util.List;

import android.app.Activity;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.ContextMenu;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.View.OnCreateContextMenuListener;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.AdapterView.AdapterContextMenuInfo;

public class Main extends Activity implements SharedPreferences.OnSharedPreferenceChangeListener{

	private ListView listview;
	private Context c;
	private Handler mHandler;
	private ProgressDialog pDialog;
	private SkinAdapter adapter;
	private SharedPreferences sp;
	static final int MESSAGE_SEARCHED_SKIN=0;
	static final int MESSAGE_SEARCHING_SKIN=MESSAGE_SEARCHED_SKIN+1;
	static final int MESSAGE_SEARCHED_SKIN_FOR_NONTHING=MESSAGE_SEARCHING_SKIN+1;

	@Override
	protected void onCreate(Bundle savedInstanceState) {

		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);
		init();
		pDialog.show();
		new Thread(serachSkin).start();

	}

	private void init()
	{

		c=this;
		mHandler=new Handler(){

			@Override
			public void handleMessage(Message msg) {
				switch(msg.what)
				{
				case MESSAGE_SEARCHED_SKIN:
					ArrayList
 skins=(ArrayList
) msg.obj;//获取skins
					adapter=new SkinAdapter(c, skins);
					listview.setAdapter(adapter);
					Toast.makeText(c, "查找到已经安装的皮肤", 1).show();
					pDialog.dismiss();
					break;
				case MESSAGE_SEARCHED_SKIN_FOR_NONTHING:
					Toast.makeText(c, "未查找到任何皮肤", 1).show();
					pDialog.dismiss();
				}
			}

		};
		sp=this.getSharedPreferences("config",Context.MODE_WORLD_WRITEABLE);
		sp.registerOnSharedPreferenceChangeListener(this);

		listview=(ListView) findViewById(R.id.list);
		listview.setItemsCanFocus(false);
		listview.setChoiceMode(ListView.CHOICE_MODE_SINGLE);

		pDialog=new ProgressDialog(this);
		pDialog.setMessage("正在查找已经安装的皮肤");

		listview.setOnCreateContextMenuListener(new OnCreateContextMenuListener() {

			public void onCreateContextMenu(ContextMenu menu, View v,
					ContextMenuInfo menuInfo) {
				menu.add("使用该主题");

			}
		});

	}

	private Runnable serachSkin =new Runnable(){

		public void run() {
			PackageManager manager=c.getPackageManager();
			List
 packages=manager.getInstalledPackages(PackageManager.PERMISSION_GRANTED);

			ArrayList
 skins=new ArrayList
();
			for(PackageInfo info:packages)
			{
				//System.out.println(info.packageName);
				if(info.packageName.startsWith("org.leepood.skin."))
				{
					skins.add(info);
				}
			}
			if(skins.size()>0)
			{
				Message msg=mHandler.obtainMessage();
				msg.obj=skins;
				msg.what=MESSAGE_SEARCHED_SKIN;
				mHandler.sendMessage(msg);
			}
			else
			{
				mHandler.sendEmptyMessage(MESSAGE_SEARCHED_SKIN_FOR_NONTHING);
			}

		}

	};
	private class SkinAdapter extends BaseAdapter
	{

		LayoutInflater mInflater;
		ArrayList
 datas;
		PackageManager manager;
		public SkinAdapter(Context c,ArrayList
 datas)
		{

			this.datas=datas;
			 mInflater=LayoutInflater.from(c);
			 manager=c.getPackageManager();
		}

		public int getCount() {

			return datas.size();
		}

		public Object getItem(int position) {

			return datas.get(position);
		}

		public long getItemId(int position) {

			return 0;
		}

		public View getView(int position, View convertView, ViewGroup parent) {

			if(convertView==null)
			{
				convertView=mInflater.inflate(R.layout.skin_item, null);
			}
			ImageView icon=(ImageView) convertView.findViewById(R.id.skin_icon);
			TextView  skin_name=(TextView) convertView.findViewById(R.id.skin_name);
			PackageInfo info=datas.get(position);
			icon.setImageDrawable(info.applicationInfo.loadIcon(manager));
			skin_name.setText(info.applicationInfo.loadLabel(manager));
			return convertView;
		}

	}

	public void onThemeChanged(String newThemePackageName) {
		try {

			Context themeContext=this.createPackageContext(newThemePackageName, CONTEXT_IGNORE_SECURITY);
			Resources res=themeContext.getResources();
			setControlsStyle(res);

		} catch (NameNotFoundException e) {

			e.printStackTrace();
		}

	}

	private void setControlsStyle(Resources res)
	{
		listview.setBackgroundColor(res.getColor(R.color.ListView_bg));

	}

	@Override
	public boolean onContextItemSelected(MenuItem item) {
		AdapterContextMenuInfo menuInfo=(AdapterContextMenuInfo)item.getMenuInfo();
		PackageInfo info=(PackageInfo) adapter.getItem(menuInfo.position);

		sp.edit().putString("themePackage", info.packageName).commit();
		return super.onContextItemSelected(item);
	}

	public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
			String key) {
		System.out.println("themeChange");
		onThemeChanged(sharedPreferences.getString(key, ""));

	}

}


这段代码的含义就是去查找系统中安装的包名,若以org.leepood.skin.开头则说明该包为主题包,将其加入listview中显示出来。代码中使用了多线程避免时间过长堵塞UI。程序将当前主题配置保存在SharedPreference中,为SharedPreference注册了一个监听函数,当其值发生改变时自动调用新的样式。当然,这只是个demo而已,一开始加载Activity没有去读取主题,这个可以由大家自己去实现。

Android实现主题切换机制2
昨天花了点时间实现了主题的切换,但是里面还是不够灵活,回去想了想可以用继承和回调函数来进一步灵活更改主题,现在记录下我的实现办法
首先一个自定义类ThemeActivity继承自Activity,这个类是以后所有Activity的父类,在这个类里面定义了一个接口

public interface OnThemeChangedListener 
{ 
public void onChanged(String newThemePackageName); 

} 

接下来,首先是要给ThemeActivity注册一个主题切换的listener,代码如下:

public void setOnThemeChangedListener(OnThemeChangedListener listener) 
{ 
this.listener=listener; 
} 

然后就是注册一个SharedPreference来监听xml的变化,当发生改变的时候自动去调用listener.onChanged方法,将新的主题包名传递过去,代码如下:

public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, 
String key) { 
if(key.equals("themePackage")) 
{ 
listener.onChanged(sp.getString("themePackage", "")); 
} 

} 

接着在继承于ThemeActivity的子类里面首先是setOnThemeChangedListener.接着用一个匿名内部类搞定。
分享到:
评论
2 楼 hxdawxyhxdawxy 2012-02-23  
csh_tianya 写道
感谢大虾  小弟初学android   学习了

客气,一起进步
1 楼 csh_tianya 2012-01-15  
感谢大虾  小弟初学android   学习了

相关推荐

    Android应用皮肤切换Demo

    在Android开发中,皮肤切换是一种常见的用户界面定制技术,它允许用户根据个人喜好更改应用程序的外观。本Demo主要展示了如何在Android应用中实现皮肤切换功能,以提供更丰富的用户体验。以下将详细介绍Android应用...

    Android切换界面皮肤代码

    在Android开发中,实现界面皮肤切换的功能可以提升用户体验,让用户根据个人喜好自定义应用的视觉样式。本教程将深入探讨如何在Android应用中实现实时切换界面皮肤的代码技术。 首先,我们需要理解皮肤的基本概念。...

    android切换主题一(apk方式)

    在实现皮肤切换时,需要注意不同Android版本间的兼容性问题。同时,频繁的资源加载和替换可能会影响应用的性能,因此需要优化加载过程,例如,使用异步加载或者缓存机制。 7. **安全与稳定性** 考虑到用户可能会...

    Android-skin-sprite另一种不重启Activity切换Android夜间模式的库

    3. 注册可换肤的组件:对于需要动态更换皮肤的View或自定义View,需要实现`Skinable`接口,并覆写`applySkin()`方法,以在皮肤切换时更新UI。 4. 切换皮肤:提供了API如`SkinSprite.applySkin("skin_night")`来切换...

    Android 实现皮肤打包成apk的demo.zip

    总的来说,这个项目提供了一个完整的Android皮肤打包和应用流程,对于开发者来说,这是一个很好的学习和实践平台,可以帮助他们掌握如何在Android应用中实现皮肤系统。通过深入研究源码,开发者可以理解如何动态加载...

    android开发皮肤图片

    5. **资源切换**:为了实现皮肤切换功能,可以在应用中设置一个设置页面,让用户选择不同的皮肤。这通常涉及到动态更改主题或替换资源文件。 6. **优化性能**:考虑到性能,避免在运行时频繁地改变大量图片资源。...

    Android 应用 更换皮肤(zip实现)

    通过合理利用`ZipFile`类,我们可以实现应用皮肤的动态切换,为用户提供个性化的体验。这个过程需要对Android的资源系统有深入的理解,并能熟练使用Java的文件操作API。同时,注意资源加载的性能优化和资源释放,以...

    安卓换肤主题更换夜间模式相关-Android手指滑动切换页面换皮肤.rar

    "Android手指滑动切换页面+换皮肤"可能是包含具体实现的Java代码或XML布局文件,供开发者参考学习。 在实践中,为了确保代码的可维护性和扩展性,应该遵循良好的编程规范,将触摸事件处理和主题切换逻辑封装成单独...

    Android之主题皮肤实现

    本文将详细介绍三种在Android中实现主题皮肤切换的方法,帮助开发者更好地理解这一技术。 首先,我们来了解一下基本概念。主题(Theme)是Android系统提供的一种全局样式设定,它可以控制应用程序的整体外观,包括...

    Android代码-实现皮肤打包成apk的demo.zip

    - 为了提供良好的用户体验,皮肤切换应该尽可能平滑,避免应用重启或者界面闪烁。 - 可以使用动画过渡效果,使皮肤切换更自然。 8. **代码设计**: - 设计一个SkinManager类来负责皮肤的加载、切换和卸载,保持...

    android皮肤分离

    三、实现皮肤切换 1. 创建皮肤资源:首先,你需要为你的应用创建多个皮肤资源版本,每个版本代表一种主题。这些资源应按照Android的资源目录结构进行组织,如res/drawable、res/layout、res/values等。 2. 设计主题...

    android 皮肤demo

    此外,对于更复杂的皮肤切换场景,开发者可能会使用到第三方库,如“android-skin-loader”。这个库提供了一种方便的方式来管理皮肤加载和切换,减少了手动处理资源的繁琐工作。其主要特点包括: 1. 支持动态换肤:...

    Android 摇一摇换皮肤

    皮肤的更换可能涉及图片资源的替换,或者整个布局文件的切换,需要利用`LayoutInflater`加载新的布局,并使用`ViewGroup.removeAllViews()`移除旧视图,再用`ViewGroup.addView()`添加新视图。 4. **资源管理**: ...

    Android-Android主题换肤插件化换肤无缝切换

    "Android-Android主题换肤插件化换肤无缝切换"是一个专为Android应用程序设计的技术主题,它旨在实现动态的、插件化的皮肤更换,使得用户在不重启应用的情况下能够实现皮肤的平滑切换。以下是对这个主题的详细解释和...

    android 5.0 切换主题,夜间模式

    在Android 5.0系统中,为应用添加切换主题和夜间模式的功能,是提升用户体验的重要方式之一。Material Design是Android 5.0引入的设计语言,其中的Toolbar是新推出的导航组件,它允许开发者轻松实现界面风格的切换。...

    Android切换皮肤,更换背景DEMO

    Android换肤,更换背景,白天模式夜间模式demo 实现方法:通过自定义控件,以及自定义相关样式 利用偏好设置进行保存用户的设置 实现不用重新启动activity进行换肤功能,更换背景,相关图片,以及文字的颜色设置 ...

    安卓开发更改皮肤功能,全局皮肤

    7. **测试与优化**:开发完成后,进行详尽的测试是非常必要的,包括各种屏幕尺寸、分辨率、Android版本的适配测试,以及性能测试,以确保皮肤切换的流畅性和稳定性。 8. **用户反馈**:收集用户反馈,根据用户的...

    Android应用源码安卓软件实现动态皮肤更换.zip

    在Android中,我们可以通过改变这些资源来实现皮肤的切换。主要有以下几种方法: 1. **资源替换**:在不同的皮肤目录(如res/drawable-xxhdpi-skin1, res/drawable-xxhdpi-skin2)下存放不同皮肤的图片资源,通过...

    android theme 皮肤主题 的应用

    通过阅读`CSipSimple`的源码,可以理解如何实现自定义主题、皮肤切换,以及如何处理不同Android版本间的兼容性问题。 总结,Android主题和皮肤主题的应用涉及资源文件的管理、XML样式定义、代码中主题的设置和切换...

Global site tag (gtag.js) - Google Analytics