apk换肤在有些app上实现的还不错,比如QQ,网易云等;那怎么去实现一个无闪烁,无启动的换肤呢?
接下来从代码角度一步一步去实现app的换肤功能
要想实现真正换肤的话,首先要面临两个问题;首先是怎么知道哪些控件是需要换肤的?然后换肤的资源包是怎么从外部加载进来的?
拦截系统创建收集需要换肤的View
如上图所示,首先我们要做的是拦截系统实例化View的过程,自己创建View,收集需要换肤的View;那怎么去拦截呢?我们知道android 的页面大部分是通过布局文件xml来编写的,那系统是怎么解析这个xml的呢?
我们从AppCompatActivity->setContentView方法看起:
@Override
public void setContentView(@LayoutRes int layoutResID) {
getDelegate().setContentView(layoutResID);
}
可以看到它调用到了AppCompatDelegateImpl的setContentView方法:
@Override
public void setContentView(int resId) {
ensureSubDecor();
ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
contentParent.removeAllViews();
LayoutInflater.from(mContext).inflate(resId, contentParent);
mAppCompatWindowCallback.getWrapped().onContentChanged();
}
我们看到它是通过LayoutInflater加载的,最终它会走到LayoutInflater的inflate方法:
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");
final Context inflaterContext = mContext;
final AttributeSet attrs = Xml.asAttributeSet(parser);
//代码省略///
try {
if (TAG_MERGE.equals(name)) {
//关键解析代码
rInflate(parser, root, inflaterContext, attrs, false);
} else {
// Temp is the root view that was found in the xml
//关键代码/
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
if (root != null) {
if (DEBUG) {
System.out.println("Creating params from root: " +
root);
}
// Inflate all children under temp against its context.
//关键代码//
rInflateChildren