摘要:本文将带你了解Android应用开发Android:换肤技术讲解,希望本文对大家学Android有所帮助。
本文将带你了解Android应用开发Android:换肤技术讲解,希望本文对大家学Android有所帮助。
主题,是许多APP必备的一个功能,用户可以根据自己的喜好,来切换具有个性的主题,同时能让我们的APP更具把玩性。这篇博文就来聊聊皮肤切换的原理,
这里为了便于理解,在换肤的时候,只是简单切换背景图片,文件颜色和组件背景色
这篇博文将用到一下知识点:
classLoader:实例化控件 PackageManager:拿到插件的包信息 反射:拿到插件的resource LayoutInflaterFactory:解析xml
一、思路
首先通过LayoutInflaterCompat的setFactory方法设置自定义的LayoutInflaterFactory,并实现onCreateView,我们可以在该方法中解析xml的每一个节点(即view ),先通过组件名创建对应的view ,再遍历每一个view的 attrs属性和值,并以map保存,以便后续调用(即皮肤资源的切换)。
我们知道,在Android中是通过resource来获取资源的,若能获取插件的resource对象,那么就可以获取其图片等资源,到达换肤的目的,说得简单点,换肤就是换resource和packName。
在拿到插件的resource之后,就可以通过resource Id 来给每一个view设置其属性(如background)
当然,道理想必大家都懂,show me your code ~
二、偷梁换柱,换掉应用的LayoutInflaterFactory
我们需要持切换皮肤的组件,因此创建SkinFactory类实现LayoutInflaterFactory接口,并实现该接口中的方法onCreateView
/**
* Hook you can supply that is called when inflating from a LayoutInflater.
* You can use this to customize the tag names available in your XML
* layout files.
*
* @param parent The parent that the created view will be placed
* in; note that this may be null.
* @param name Tag name to be inflated.
* @param context The context the view is being created in.
* @param attrs Inflation attributes as specified in XML file.
*
* @return View Newly created view. Return null for the default
* behavior.
*/
View onCreateView(View parent, String name, Context context, AttributeSet attrs);
显然这是一个hook, 执行LayoutInflater.inflate()的时候调用,如上所述,我们可以通过该方法获取每一个节点的属性和值(即资源id),资源类型(drawable 、color 等)。先简单介绍这四个参数:
parent:即当前节点的父类节点,可能为null name :节点名,列如 TextView context :该执行过程的上下文 attrs:该节点的属性集合,例如 background属性
那么,我们怎么通过节点来创建对应的组件对象呢?我们都知道在android.widget包下的Button在布局文件中的节点名只有Button,并不是完整的包路径,例如
以及android.view包下的SurfaceView等等。
想必读者明白列举以上的用意了,对,我们需要先对获取到的节点名字进行处理,判断获取到的节点名是系统组件,还是自定义组件,从而构建完整的class name 。如下代码
private static final String[] preFixList = {
"android.widget.",
"android.view.",
"android.webkit."
}; //这些都是系统组件
@Override
public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
View view = null;
if (name.indexOf(".") == -1) {
//系统控件
for (String prix : preFixList) {
view = createView(context, attrs, prix + name);
if (null != view) {
break;
}
}
} else {
//自定义控件
view = createView(context, attrs, name);
}
if (null != view) {
parseSkinView(view, context, attrs);
}
return view;
}
这里需要我们返回一个view,即该组件对应的view,既然能拿到组件对应的class name,那就好办,直接通过classloader去load一个class即可
//创建一个view
private View createView(Context context, AttributeSet attrs, String name) {
try {
//实例化一个控件
Class clarr = context.getClassLoader().loadClass(name);
Constructor constructor =
clarr.getConstructor(new Class[]{Context.class, AttributeSet.class});
constructor.setAccessible(true);
return constructor.newInstance(context, attrs);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return null;
}
在拿到该控件后,需要遍历其需要替换值的属性,例如background,存放在list集合中。
//找到需要换肤的控件
private void parseSkinView(View view, Context context, AttributeSet attrs) {
List
for (int i = 0; i < attrs.getAttributeCount(); i++) {
//拿到属性名
String attrName = attrs.getAttributeName(i);
String attrValue = attrs.getAttributeValue(i);
int id =-1;
String entryName ="";
String typeName ="";
SkinInterface skinInterface = null ;
switch (attrName) {
case "background"://需要进行换肤
id = Integer.parseInt(attrValue.substring(1));
entryName = context.getResources().getResourceEntryName(id);
typeName = context.getResources().getResourceTypeName(id);
skinInterface = new BackgroundSkin(attrName,id,entryName,typeName);
break;
case "textColor":
id = Integer.parseInt(attrValue.substring(1));
entryName = context.getResources().getResourceEntryName(id);
typeName = context.getResources().getResourceTypeName(id);
skinInterface = new TextSkin(attrName,id,entryName,typeName);
break;
default:
break;
}
if(null != skinInterface){
attrList.add(skinInterface);
}
}
SkinItem skinItem = new SkinItem(attrList,view);
map.put(view,skinItem);
//在这里进行应用,判断是皮肤资源还是本地资源
skinItem.apply();
}
为了方便属性的替换,这里用SkinItem对象来持有view和view对应的属性集合list。
class SkinItem {
public SkinItem(List
this.attrList = attrList;
this.view = view;
}
public List
public View view;
//更新组件资源,调用skinInterface 的实现类body{ }
本文由职坐标整理并发布,希望对同学们有所帮助。了解更多详情请关注职坐标移动开发之Android频道!
您输入的评论内容中包含违禁敏感词
我知道了
请输入正确的手机号码
请输入正确的验证码
您今天的短信下发次数太多了,明天再试试吧!
我们会在第一时间安排职业规划师联系您!
您也可以联系我们的职业规划师咨询:
版权所有 职坐标-一站式IT培训就业服务领导者 沪ICP备13042190号-4
上海海同信息科技有限公司 Copyright ©2015 www.zhizuobiao.com,All Rights Reserved.
沪公网安备 31011502005948号