`
hxdawxyhxdawxy
  • 浏览: 125902 次
  • 性别: 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切换皮肤,更换背景DEMO

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

    android切换主题一(apk方式)

    android切换主题。 方式:apk皮肤方式。 具体分析:http://blog.csdn.net/springsky_/article/details/24476137

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

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

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

    Android手指滑动切换页面 换皮肤.rar,太多无法一一验证是否可用,程序如果跑不起来需要自调,部分代码功能进行参考学习。

    android皮肤分离

    实现android主题的切换 将皮肤分离 方便下载安装皮肤

    Android夜间模式动态切换

    app不乏需要添加夜间模式和日间模式的皮肤,本demo采用Android官方在Support Library 23.2.0中已经加入了夜间主题

    Android 应用更换皮肤实现方法

    Android应用更换皮肤功能的实现思路  我们可以将皮肤包做成一个apk文件,在应用apk中读取皮肤包中的数据。  应用apk读取皮肤包apk的数据的条件  1. 有相同签名  2. AndroidManifest.xml中配置android:...

    android 插件式无刷新换肤和字体大小切换 兼容android 8.1

    android 插件式无刷新换肤和字体大小切换 兼容android 8.1; theme_dome 为dome 案例工程;black_theme 皮肤包工程。

    SkinSwitch-Android:Android 版流行的 Minecraft 皮肤切换器

    适用于 Android 的 SkinSwitch介绍SkinSwitch 是适用于 iOS 和 Android 的 Minecraft 皮肤切换器构建。 Android 版本已根据 GNU GPL v3 许可条款发布,因此请在这些条款下随意使用它!汇编通过使用 Gradle 将项目...

    Android 高仿点心桌面皮肤源码.zip

    这是一款基于android平台的点心桌面皮肤源码,可以进行皮肤切换,在这里和大家分享一下应用皮肤的实现方式。其实现原理是,主的应用是一个apk,皮肤是一个apk,在主的应用中读取已安装的apk。  

    Android相册程序,包含所有源代码

    Android加密相册程序。可以修改密码。 支持皮肤切换

    百度输入法皮肤,适合双拼输入,界面精致美观

    本皮肤特点:占用内存少(图片色度较低),故切换速度快,相比那些华丽的大文件皮肤更实用;去掉一排数字键,全键盘键位较大,使用熟练后,打字飞快; 九宫格字母排序与全键盘一致,适合熟练微软双拼的人士使用,...

    新版Android开发教程.rar

    ----------------------------------- Android 编程基础 1 封面----------------------------------- Android 编程基础 2 开放手机联盟 --Open --Open --Open --Open Handset Handset Handset Handset Alliance ...

    Android天气预报应用

    在Android平台上已经有很多天气预报类的应用,其中也不乏设计精良的作品。本文介绍了天气预报的一种比较简单的实现,并提供了完整的源代码供初学者进行参考。本应用主要功能有: (1)在屏幕上显示当前时刻的详细...

    Android版‘音乐一点通’音乐播放器源代码

    支持皮肤背景更换,随心换喜好的皮肤背景 支持正常模式/夜间模式切换,支持定时关闭 歌曲分类详细,按照歌手,专辑,文件夹,清晰明了 支持自定义扫描本地歌曲,默认扫描到默认列表中,目前只支持常用格式MP3,WMV,...

    Android代码-QSkinLoader

    基本原理是通过代理LayoutInflater的View创建过程解析皮肤相关属性(background/src/textColor等),将皮肤相关属性设置到View的Tag内,在切换皮肤时寻找对应的皮肤来完成实时刷新动作。此方案具有代码及XML侵入性小...

    Android Theme更换主题换肤应用实例

    Android Theme界面换肤应用实例,也就是更改系统主题风格,可在“应用透明背景的主题”、“应用布景主题1”、“应用布景主题2”三种皮肤风格之间切换,Android换肤对Android手机来说是再简单不过的功能了,个性化...

    Android代码-FloatingShadow

    android上的辅助操控工具,通过它你可以在任意界面快速的激活各种功能、在全屏与非全屏之间自如的切换、在深夜里随心的调节色温。 ★★可以做什么? 进入全屏沉浸模式,享受阅读的乐趣 模拟 返回键、主页键、菜单键等...

Global site tag (gtag.js) - Google Analytics