android theme 皮肤主题 的应用

如今的程序猿,当然先从网上搜索一番了。关于皮肤的制作 ,主要有三种,似乎所有 的文章都是从一处抄来的。

我也顺便抄了一段:其中以apk安装版本的为例子。
shareuserid这个东西,由实践得出结论,不是用在这里的,没有也没关系。

创建相关的Context:
Context context = createPackageContext("com.yuchen.customskin"
            Context.CONTEXT_IGNORE_SECURITY);
前面的包名,你自己想办法 弄到了。
有了这个东西,就可以处理相关的界面了。比如:Drawable drawable = context.getResources().getDrawable(R.drawable.bg);

这段就是网上常见的东西了。

这里说一些个人实践得出的一些结果。
有些程序是先载入布局,然后对里面的背景,图片等资源,重新加载一次,就是用上面的东西了。由createPackageContext创建一个对象,然后得到里面的资源。

这是一个办法,如果想直接得到布局呢:
Context ctxt=context.createPackageContext(packageName, Context.CONTEXT_IGNORE_SECURITY);
LayoutInflater inflater = LayoutInflater.from(ctxt);
然后用这个inflater来加载,最常用的就是Fragment了。
String themeName = prefsWrapper.getPreferenceStringValue(SipConfigManager.THEME);//从sp设置中得到主题的名字,可以以存储在其它地方,如数据库。。。
        Log.d(THIS_FILE, "prefsWrapper themeName:"+themeName);
        if (!TextUtils.isEmpty(themeName)) {
            Theme theme=new Theme(getActivity(), themeName, null);
            View view=theme.getLayoutResource(getActivity(), container, "com.packageName:layout/a_fragment");
            if(null!=view) {
            	return view;
            }
        }
        return inflater.inflate(resId, container, false);

这样就可以直接在onCreateView载入布局了,而不必在载入后再对其中的按钮,文本。。。进行处理了。

在csipsimple代码中有一段是关于主题的,但它是使用异步加载。
查询当前所有的相关主题 apk安装包:
public static HashMap<String, String> getAvailableThemes(Context ctxt){
		HashMap<String, String> result = new HashMap<String, String>();
		result.put(ctxt.getResources().getString(R.string.app_name), "");
		
		PackageManager packageManager = ctxt.getPackageManager();
		Intent it = new Intent(SipManager.ACTION_GET_DRAWABLES);
		
		List<ResolveInfo> availables = packageManager.queryBroadcastReceivers(it, 0);
		Log.d(THIS_FILE, "We found " + availables.size() + "themes");
		for(ResolveInfo resInfo : availables) {
			Log.d(THIS_FILE, "We have -- "+resInfo);
			ActivityInfo actInfos = resInfo.activityInfo;
			String packagedActivityName = actInfos.packageName + "/" + actInfos.name;
			Log.d(THIS_FILE, "packagedActivityName:"+packagedActivityName);
			result.put((String) resInfo.loadLabel(packageManager), packagedActivityName);
		}
		
		return result;
	}
在皮肤apk名是只有一个ThemeReceiver,这个接收的字符串固定是
	public static final String ACTION_GET_DRAWABLES = "com.csipsimple.themes.GET_DRAWABLES";
,
于是我下载了一个皮肤 :com.csipsimple.themes.froyo
可惜,不能用。
查询的结果,只是在选择主题时候用到。

应用 主题 :它是在onresume里面调用的。可想而知,如果是这样,每次恢复 都会调用一次,也挺耗资源的。
String theme = prefsWrapper.getPreferenceStringValue(SipConfigManager.THEME);
        Log.d(THIS_FILE, "prefsWrapper theme:"+theme);
        if (!TextUtils.isEmpty(theme)) {
new Theme(getActivity(), theme, new Theme.onLoadListener() {
                @Override
                public void onLoad(Theme t) {

                    dialPad.applyTheme(t);
                     t.applyBackgroundDrawable(getView().findViewById(R.id.vmButton),
                            "btn_dial_action_left_normal");
...... 这里是异步处理的,程序运行的结果是先看到原始界面,然后调用这个方法异步处理后,界面会发生变化。
                }
            });
}

public Theme(Context ctxt, String packageName, onLoadListener l) {
		listener = l;
		pm = ctxt.getPackageManager();
		
		String[] splitPackage = packageName.split("/");
		ComponentName cn = new ComponentName(splitPackage[0], splitPackage[1]);
		
		Intent it = new Intent(SipManager.ACTION_GET_DRAWABLES);
		it.setComponent(cn);
		
		ctxt.sendOrderedBroadcast(it, null, new BroadcastReceiver() {
			@Override
			public void onReceive(Context context, Intent intent) {
				resolvedInfos = getResultExtras(true);
				Log.d(THIS_FILE, "We have logs : " + resolvedInfos.size());
				Iterator<String> it=resolvedInfos.keySet().iterator();
				while (it.hasNext()) {
					String val=it.next();
					Log.d(THIS_FILE, "key:"+val);
				}
				if(listener != null) {
					listener.onLoad(Theme.this);
				}
			}
		}, null, Activity.RESULT_OK, null, null);

		Log.d(THIS_FILE, "After broadcast" );
			
	}

为什么前面的皮肤froyo不能用呢?因为onReceive方法没有内容。所以resolvedInfos.size()=0.
文档中说sendOrderedBroadcast里面的BroadcastReceiver.getResultExtras方法接收上一次的结果。由于前面的onReceive没有返回东西,所以这里也接收不到。
于是下载源码修改了:
if(ACTION_GET_DRAWABLES.equalsIgnoreCase(intent.getAction())) {
			Bundle bundle=new Bundle();
			bundle.putString("dial_num_0", "com.csipsimple.themes.froyo:drawable/dial_num_0");
			bundle.putString("dial_num_1", "com.csipsimple.themes.froyo:drawable/dial_num_1");
			bundle.putString("dial_num_2", "com.csipsimple.themes.froyo:drawable/dial_num_2");
			bundle.putString("dial_num_3", "com.csipsimple.themes.froyo:drawable/dial_num_3");
			bundle.putString("dial_num_4", "com.csipsimple.themes.froyo:drawable/dial_num_4");
			bundle.putString("dial_num_5", "com.csipsimple.themes.froyo:drawable/dial_num_5");
			bundle.putString("dial_num_6", "com.csipsimple.themes.froyo:drawable/dial_num_6");
			bundle.putString("dial_num_7", "com.csipsimple.themes.froyo:drawable/dial_num_7");
			bundle.putString("dial_num_8", "com.csipsimple.themes.froyo:drawable/dial_num_8");
			bundle.putString("dial_num_9", "com.csipsimple.themes.froyo:drawable/dial_num_9");

			bundle.putString("btn_dial_press", "com.csipsimple.themes.froyo:drawable/btn_dial_press");
			bundle.putString("btn_dial_normal", "com.csipsimple.themes.froyo:drawable/btn_dial_normal");
			bundle.putString("btn_dial_action_left_normal", "com.csipsimple.themes.froyo:drawable/btn_dial_action_left_normal");
			bundle.putString("dial_num_star", "com.csipsimple.themes.froyo:drawable/dial_num_star");

			bundle.putString("call_log_fragment", "com.csipsimple.themes.froyo:layout/call_log_fragment");
			setResultExtras(bundle);
		}
先把一些东西放进去,没有写全部的。

然后就可以看到效果了。

从上面也可以看到一些缺点,就是ui的应用,在第一次启动程序时,会有两次变化,一次是原始的皮肤,一次是应用后的新皮肤,而且需要在onReceive里面写一堆相关的包名+资源名。(当然这一步可能可以优化下,比如可以在oncreate的setcontentview载入后只设置一次,这样修改皮肤 就需要重启这个activity了。)

个人还是比较倾向于载入布局,这样就可以连带其它资源的载入了。不用一个一个地设置图片。如果没有修改ActionBar就还好,如果有的话,就麻烦一点,需要单独地处理ActionBar的资源。

从csipsimple中的代码可以了解:
查询安装的皮肤是可以通过一个receiver来处理的,而网上流传的版本普遍是通过shareuserid。对于 onreceive里面是否有返回一些数据,可能只是csipsimple有必要。而且试过了:

public View getLayoutResource(Context context,ViewGroup container,String name) {
		//if(resolvedInfos != null) {
			String layoutName =name;// resolvedInfos.getString(name);
			Log.d(THIS_FILE, "getLayoutResource:"+layoutName+" name:"+name);
			if(layoutName != null) {
				Log.d(THIS_FILE, "Theme package we search for " + layoutName);
				
				String packageName = layoutName.split(":")[0];
				
				//Log.d(THIS_FILE, "Theme package we search for " + packageName);
				
				PackageInfo pInfos;
				try {
					pInfos = pm.getPackageInfo(packageName, 0);
					Log.d(THIS_FILE, "pInfos.packageName:"+pInfos.packageName);
					Resources remoteRes = pm.getResourcesForApplication(pInfos.applicationInfo);
					int id = remoteRes.getIdentifier(layoutName, null, null);
					Log.d(THIS_FILE, "id:"+id);
					if(id!=0) {
						Context ctxt=context.createPackageContext(packageName, Context.CONTEXT_IGNORE_SECURITY);
						LayoutInflater inflater = LayoutInflater.from(ctxt);
						return inflater.inflate(id, container, false);
					}
				} catch (NameNotFoundException e) {
					Log.e(THIS_FILE, "Unable to get resources for this theme package");
				}
			}else {
				Log.w(THIS_FILE, "Theme is not complete, not found : "+name);
			}
		/*}else {
			Log.d(THIS_FILE, "No results yet !! ");
		}*/
		return null;
	}
然后在Fragment的oncreateview中应用,是可以得到这个布局的。这也取消了前面的设置返回值,当然布局文件名在皮肤的apk中要一致,以免找不到资源。

在froyo中新建一个layout,用上面的代码 就可以载入了。

老外可能也不是很在意皮肤,不像国人,多数注重外表,所以关于皮肤的文章不多。froyo是旧系统的资源,而不是特定的某种皮肤 。


附件是apk可以先安装一个CSipSimpleTest.apk,看到第一个Fragment,拨号面板,与第二个Fragment的样子,然后再安装第二个apk,CSipSimpleThemeFroyo.apk再看看样子。是不同的。
虽然丑了点,此示例主要在于测试。

如果只是修改同一个apk中不同的主题 可以这样:
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {

        Context darkTheme = new ContextThemeWrapper(getActivity(), R.style.AppTheme_Dark_NoActionBar);

        inflater = LayoutInflater.from(darkTheme);

        return inflater.inflate(R.layout.awesome_fragment, container, false);

    },这段是创建了主题相关的Context,然后再应用。如果应用内的皮肤,也不必这样麻烦了,可以直接 设置Activity的theme的style了。

源码以后附上。

先记录到此吧。以后补充。







猜你喜欢

转载自phenom.iteye.com/blog/1724581
今日推荐