作者:彭老师
日期:2019-07-17
类型:Android
说明:本文源于 彭老师 手写摘要,如需转载请带上链接或注明出处!
前言 动态换肤 技术在app应用中,越来越普及大众化了。那么具体的实现是怎么样呢?
今天我们就来大致分析下大众版换肤技术(思路分析)
核心6点:
1、 registerActivityLifecycleCallbacks注册观察者
2 、Activity接收用户操作:点击换肤按钮
3 、调用被观察者Observable方法:setChanged(); notifyObservers();
4 、实现Fatory2接口(收集/采集所有控件),并实现观察者Observer执行update()方法
5 、遍历2层(所有View控件,每个View控件所有属性),匹配换肤属性
6 、有皮肤包加载皮肤包资源,找不到则加载内置资源,更改控件属性值
1、registerActivityLifecycleCallbacks注册观察者 这是什么方法?有什么作用?
监听当前应用所有Activity生命周期方法的执行,也是谷歌AOP思想
该方法的意义就是:执行在setContentView()
方法之前
public class SkinActivityLifecycleCallbacks implements Application .ActivityLifecycleCallbacks { private SkinFactory skinFactory; @Override public void onActivityCreated (Activity activity, Bundle savedInstanceState) { LayoutInflater layoutInflater = LayoutInflater.from(activity); try { Field mFactorySet = LayoutInflater.class.getDeclaredField("mFactorySet" ); mFactorySet.setAccessible(true ); mFactorySet.set(layoutInflater, false ); } catch (Exception e) { e.printStackTrace(); } skinFactory = new SkinFactory(activity); LayoutInflaterCompat.setFactory2(layoutInflater, skinFactory); SkinEngine.getInstance().addObserver(skinFactory); } @Override public void onActivityDestroyed (Activity activity) { SkinEngine.getInstance().deleteObserver(skinFactory); } }
2 、Activity接收用户操作:点击换肤按钮 布局activity_netease.xml接收到用户的换肤操作,即点击按钮事件
3 、调用被观察者Observable方法:setChanged()
; notifyObservers()
public class SkinEngine extends Observable { public void updateSkin () { setChanged(); notifyObservers(); } }
4 、实现Fatory2接口(收集/采集所有控件),并实现观察者Observer执行update()
方法 public class SkinFactory implements LayoutInflater .Factory2 , Observer { @Override public View onCreateView (View parent, String name, Context context, AttributeSet attrs) { View resultView = createViewFromTag(parent, name, context, attrs); switch (name) { case "ImageView" : resultView = new ImageView(context, attrs); break ; case "xxxxx" : break ; } if (null == resultView) { resultView = createView(name, "" , context, attrs); } widgetViewList.saveWidgetView(attrs, resultView); return resultView; } private View createViewFromTag (View parent, String name, Context context, AttributeSet attrs) { View view = null ; for (String s : sClassPrefixList) { view = createView(name, s, context, attrs); if (view != null ) { break ; } } return view; } private View createView (String name, String prefix, Context context, AttributeSet attrs) { Constructor<? extends View> constructors = sConstructorMap.get(name); if (null == constructors) { Log.d(TAG, "需要反射找的>>>>>>>>>> prefix + name:" + prefix + name); try { Class<? extends View> classz = context.getClassLoader().loadClass(prefix + name).asSubclass(View.class); Constructor<? extends View> constructor = classz.getConstructor(mConstructorSignature); constructor.setAccessible(true ); sConstructorMap.put(name, constructor); return constructor.newInstance(context, attrs); } catch (Exception e) { } } else { try { constructors.setAccessible(true ); return constructors.newInstance(context, attrs); } catch (Exception e) { e.printStackTrace(); } } return null ; } @Override public View onCreateView (String name, Context context, AttributeSet attrs) { return null ; } @Override public void update (Observable o, Object arg) { widgetViewList.skinChange(); } }
5 、遍历2层(所有View控件,每个View控件所有属性),匹配换肤属性 public class WidgetViewList { public void skinChange () { for (WidgetView widgetView : WIDGET_VIEWS) { widgetView.skinChange(); } } class AttributeNameAndValue { String attrName; int attrValueInt; public AttributeNameAndValue (String attrName, int attrValueInt) { this .attrName = attrName; this .attrValueInt = attrValueInt; } } class WidgetView { View mView; List<AttributeNameAndValue> attributeNameAndValues; public WidgetView (View mView, List<AttributeNameAndValue> attributeNameAndValues) { this .mView = mView; this .attributeNameAndValues = attributeNameAndValues; } public void skinChange () { for (AttributeNameAndValue attributeNameAndValue : attributeNameAndValues) { switch (attributeNameAndValue.attrName) { case "background" : if (background instanceof Integer) { mView.setBackgroundColor((Integer) background); } else { mView.setBackground((Drawable) background); } break ; case "textColor" : TextView textView = (TextView) mView; textView.setTextColor(xxx); break ; } } } } }
6 、有皮肤包加载皮肤包资源,找不到则加载内置资源,更改控件属性值(AssetsManager) 根据app内置资源的 resourceID
获取资源 name
与 type
,需要指定包名(皮肤包)资源,如果skinResourceID
等于0则说明皮肤包资源加载失败
private int getSkinResourceIds (int resourceId) { String resourceName = appResources.getResourceEntryName(resourceId); String resourceType = appResources.getResourceTypeName(resourceId); int skinResourceId = skinResources.getIdentifier(resourceName, resourceType, skinPackageName); return skinResourceId == 0 ? resourceId : skinResourceId; }
这个架构的缺陷和性能:
1、置换setContentView()方法,容易不兼容,且自定义控件麻烦
2、控件信息收集:临时集合 + 所有控件
3、换肤时,双层遍历 + 改变控件
截图如下:
网易换肤技术,性能至少是上面 7 倍。随着布局中的控件递增,差距是几何倍!(支持自定义控件、字体,兼容5.0 - 9.0) 截图如下:
效果图如下: