|
[md]
感谢[徐爱卿.《Debug 7.1.1源码一步步写Android换肤框架》](http://www.jianshu.com/p/c8748fbf6f60)
[项目地址](https://github.com/xujianhui404/SKinAppDemo)
# 0x00 其他实现方案
[Android换肤技术总结](http://blog.zhaiyifan.cn/2015/09 ... %E6%80%BB%E7%BB%93/)
>## 内部资源加载方案
>## 动态资源加载方案
>### resource替换
>### 自定义LayoutInflator.Factory
>### Hack Resources internally
张鸿洋的两篇博客,在加载view时,对每个可变换的view进行保存,又对每个view的可变换的属性进行保存,然后换肤时调用 SkinAttrType 的apply 方法。
[ChangeSkin](https://github.com/hongyangAndroid/ChangeSkin)
```
private String appendSuffix(String name)
{
if (!TextUtils.isEmpty(mSuffix))
return name += "_" + mSuffix;
return name ;
}
```
[AndroidChangeSkin](https://github.com/hongyangAndroid/AndroidChangeSkin)
```
//com.zhy.changeskin.attr.SkinAttrSupport#getSkinView
public static SkinView getSkinView(View view)
{
Object tag = view.getTag(R.id.skin_tag_id);
if (tag == null)
{
tag = view.getTag();
}
if (tag == null) return null;
if (!(tag instanceof String)) return null;
String tagStr = (String) tag;
List<SkinAttr> skinAttrs = parseTag(tagStr);
if (!skinAttrs.isEmpty())
{
changeViewTag(view);
return new SkinView(view, skinAttrs);
}
return null;
}
//com.zhy.changeskin.attr.SkinAttrType
BACKGROUND("background")
{
@Override
public void apply(View view, String resName)
{
Drawable drawable = getResourceManager().getDrawableByName(resName);
if (drawable != null)
{
view.setBackgroundDrawable(drawable);
} else
{
try{
int color = getResourceManager().getColor(resName);
view.setBackgroundColor(color);
} catch (Resources.NotFoundException ex) {
ex.printStackTrace();
}
}
}
```
# 0x01 源码追踪
之前只读了 infalte 的方法,没有注意 factory
```
android.app.Activity#startActivity(android.content.Intent)
android.app.Activity#startActivity(android.content.Intent, android.os.Bundle)
android.support.v4.app.FragmentActivity#startActivityForResult
android.app.Activity#startActivityForResult(android.content.Intent, int)
android.support.v4.app.BaseFragmentActivityJB#startActivityForResult
android.app.Activity#startActivityForResult(android.content.Intent, int, android.os.Bundle)
android.app.Instrumentation#execStartActivity(android.content.Context, android.os.IBinder, android.os.IBinder, android.app.Activity, android.content.Intent, int, android.os.Bundle)
android.app.ActivityManagerProxy#startActivity
...
android.app.ActivityThread.H#handleMessage
android.app.ActivityThread#handleLaunchActivity
android.app.ActivityThread#performLaunchActivity
android.app.Instrumentation#callActivityOnCreate(android.app.Activity, android.os.Bundle)
android.app.Activity#performCreate(android.os.Bundle)
...
android.support.v7.app.AppCompatActivity#onCreate
android.support.v7.app.AppCompatDelegateImplV9#installViewFactory
android.support.v4.view.LayoutInflaterCompat#setFactory
android.support.v4.view.LayoutInflaterCompat.LayoutInflaterCompatImplV21#setFactory
android.support.v4.view.LayoutInflaterCompatLollipop#setFactory
android.view.LayoutInflater#setFactory2
```
到这里就设置了mFactory2 ,后面的 infalte 流程以前已经分析过了。
```
android.support.v7.app.AppCompatActivity#setContentView(int)
android.support.v7.app.AppCompatDelegateImplV9#setContentView(int)
android.view.LayoutInflater#inflate(int, android.view.ViewGroup)
android.view.LayoutInflater#inflate(org.xmlpull.v1.XmlPullParser, android.view.ViewGroup, boolean)
android.view.LayoutInflater#createViewFromTag(android.view.View, java.lang.String, android.content.Context, android.util.AttributeSet)
android.view.LayoutInflater#createViewFromTag(android.view.View, java.lang.String, android.content.Context, android.util.AttributeSet, boolean)
这里有好几个可加载的。
...
try {
View view;
if (mFactory2 != null) {
view = mFactory2.onCreateView(parent, name, context, attrs);
} else if (mFactory != null) {
view = mFactory.onCreateView(name, context, attrs);
} else {
view = null;
}
if (view == null && mPrivateFactory != null) {
view = mPrivateFactory.onCreateView(parent, name, context, attrs);
}
if (view == null) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
if (-1 == name.indexOf('.')) {
view = onCreateView(parent, name, attrs);
} else {
view = createView(name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
return view;
}
...
可以看到依次用 mFactory2、mFactory、mPrivateFactory及自己加载。
mFactory2 前面分析过了。
如果继承的android.support.v7.app.AppCompatActivity会到
android.support.v7.app.AppCompatDelegateImplV9#onCreateView
android.support.v7.app.AppCompatDelegateImplV9#callActivityOnCreateView
android.support.v7.app.AppCompatDelegateImplV9#createView
android.support.v7.app.AppCompatViewInflater#createView(android.view.View, java.lang.String, android.content.Context, android.util.AttributeSet, boolean, boolean, boolean, boolean)
mFactory 在调试时没有调用,setFactory2 的时候会一起设置。
mPrivateFactory 如果继承的是Activity才会调用到,它的设置在
android.app.Activity#attach
mWindow.getLayoutInflater().setPrivateFactory(this);
参数为Factory2,要实现的方法为
android.view.LayoutInflater.Factory2#onCreateView
android.app.Activity#onCreateView(android.view.View, java.lang.String, android.content.Context, android.util.AttributeSet)
android.app.Activity#onCreateView(java.lang.String, android.content.Context, android.util.AttributeSet)
了就是说可以重写此方法即可修改创建的view
...
```
# 0x02 实现思路
继承 LayoutInflaterFactory 并实现 onCreateView,
创建 view 后在 saveViewAttrs 中判断如果有支持换肤的属性则将 view 添加到列表中,要换肤时遍历列表执行变换。
变换时,调用 SkinManager 的方法,其可以以相同的 resId 针对不同的皮肤加载出不同的资源(换肤时 SkinManager 重新加载资源)。
[/md]
|
|