换肤分为动态换肤和静态换肤
静态换肤
这种换肤的方式,也就是我们所说的内置换肤,就是在APP内部放置多套相同的资源。进行资源的切换。
这种换肤的方式有很多缺点,比如, 灵活性差,只能更换内置的资源、apk体积太大,在我们的应用Apk中等一般图片文件能占到apk大小的一半左右。
当然了,这种方式也并不是一无是处, 比如我们的应用内,只是普通的 日夜间模式 的切换,并不需要图片等的更换,只是更换颜色,那这样的方式就很实用。
动态换肤
适用于大量皮肤,用户选择下载,像QQ、网易云音乐这种。它是将皮肤包下载到本地,皮肤包其实是个APK。
换肤包括替换图片资源、布局颜色、字体、文字颜色、状态栏和导航栏颜色。
动态换肤步骤包括:
- 采集需要换肤的控件
- 加载皮肤包
- 替换资源
实现原理
首先Activity的onCreate()方法里面我们都要去调用setContentView(int id) 来指定当前Activity的布局文件:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
再往里看:
@Override
public void setContentView(int resId) {
ensureSubDecor();
ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
contentParent.removeAllViews();
LayoutInflater.from(mContext).inflate(resId, contentParent);//这里实现view布局的加载
mOriginalWindowCallback.onContentChanged();
}
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null);
}
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
if (DEBUG) {
Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
+ Integer.toHexString(resource) + ")");
}
final XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
...
final String name = parser.getName();
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
...
return temp;
}
可以看到inflate会返回具体的View对象出去,那么我们的关注焦点就放在createViewFromTag中了
/**
* Creates a view from a tag name using the supplied attribute set.
* <p>
* <strong>Note:</strong> Default visibility so the BridgeInflater can
* override it.
*
* @param parent the parent view, used to inflate layout params
* @param name the name of the XML tag used to define the view
* @param context the inflation context for the view, typically the
* {@code parent} or base layout inflater context
* @param attrs the attribute set for the XML tag used to define the view
* @param ignoreThemeAttr {@code true} to ignore the {@code android:theme}
* attribute (if set) for the view being inflated,
* {@code false} otherwise
*/
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr) {
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;
}
return view;