Android 换肤技术点

1. 切换自定义Theme

优点: 实现简单 ,无侵入,易于理解 ,兼容性能好

缺点: 不能实时生效,静态更新,要在setContentView之前切换theme ,

自定义属性 :

1
2
3
4
5
<resources>
<attr name="textColor" format="reference|color"/>
<attr name="background" format="reference|color"/>
</resources>

自定义样式:

1
2
3
4
5
6
7
8
9
10

<style name="AppTheme_Light" >
<item name="textColor">@android:color/black</ item>
<item name="android:background">@android:color/white</ item>
</style>

<style name="AppTheme_Night">
<item name="textColor">@android:color/white</ item>
<item name="android:background">@android:color/black</ item>
</style>

应用:

1
2
3
4
5
6
7
<TextView
android :layout_width="wrap_content"
android :layout_height="wrap_content"
android :text="Hello World!"
android :background="?attr/background"
android :textColor="?attr/textColor"/>

1
2
3
4
5
6
7
8
9
protected void skinChange() {
TypedValue background = new TypedValue();
TypedValue textColor = new TypedValue();
Resources.Theme theme = getTheme();
theme.resolveAttribute(R.attr.background, background, true);
theme.resolveAttribute(R.attr.textColor, textColor, true);
mTextView .setTextColor(textColor.data);
mTextView.setBackgroundResource(background.resourceId);
}

2. 侵入式换肤框架

主要介绍一种侵入式方案的实现

优点: 使用简单,可以动态替换网络上下载的皮肤包

缺点: 侵入式 可能存在兼容性问题

2.1 LayoutInflater加载

首先是view的加载流程

  1. activity.java
    1
    2
    3
    public void setContentView(int layoutResID) {
    getWindow().setContentView(layoutResID);
    }
  2. PhoneWindow.java
1
2
3
4
5
6
7
8

@Override
public void setContentView(int layoutResID) {
...
mLayoutInflater.inflate(layoutResID, mContentParent);
...
}

  1. LayoutInflater.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public View inflate(int resource, ViewGroup root, boolean attachToRoot) {
...
XmlResourceParser parser = getContext().getResources().getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}

public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
...
View temp = createViewFromTag(name, attrs);
...

}


View createViewFromTag(String name, AttributeSet attrs) {
...
View view = (mFactory == null) ? null : mFactory.onCreateView(name,
mContext, attrs);

if (view == null) {
if (-1 == name.indexOf('.')) {
view = onCreateView(name, attrs);
} else {
view = createView(name, null, attrs);
}
}

if (DEBUG) System.out.println("Created view is: " + view);
return view;
...

}

我们重点看 createViewFromTag , 经过上面一系列步骤,我们看到了view的创建,首先会通过 mFactory ,

1
public void setFactory(Factory factory) 

通过 设置 factory,我们可以接管view的创建流程

2.2 加载外部资源皮肤包

皮肤包就是一个空的apk文件,只有 drawable,color 等

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

Resources skinResource = null;
try {
File file = new File(skinPath);
if(file == null || !file.exists()){
return null;
}

PackageManager mPm = mContext.getPackageManager();
PackageInfo mInfo =mPm.getPackageArchiveInfo(skinPath, PackageManager.GET_ACTIVITIES);
mSkinPackageName = mInfo.packageName;

AssetManager assetManager = AssetManager.class.newInstance();
Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
addAssetPath.invoke(assetManager, skinPath);

Resources superRes = mContext.getResources();
skinResource = new Resources(assetManager,superRes.getDisplayMetrics(),superRes.getConfiguration());
} catch (Exception e) {
e.printStackTrace();
}

代码托管: https://github.com/kongxs/PluginProject