Android App换肤实现 - 准备工作
2020-06-19 本文已影响0人
小名坎坎
从网易云音乐换肤开始
Android 模拟器或者Android手机(需要root权限)
-
安装网易云音乐并选择皮肤进行下载换肤
-
命令窗口中进入data/data/com.netease.cloudmusic/files/theme文件夹
皮肤包
-
adb拉取文件到电脑端,改为zip文件解压
皮肤包
从上述步骤我们可以看出,网易云音乐的皮肤包就是一个Apk资源包。我们的皮肤包也按照这种方式。
寻找切入点 - 从Avtivity的setContentView()开始
- Avtivity - setContentView()
- Window - setContentView(int layoutResID)
public void setContentView(@LayoutRes int layoutResID) {
//调用了window的->所以来到PhoneWindow的查找
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
- PhoneWindow - setContentView(int layoutResID)
public void setContentView(int layoutResID) {
/*----------------------------省略------------------------------------*/
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
// 调用了inflate方法加载资源
mLayoutInflater.inflate(layoutResID, mContentParent);
}
/*----------------------------省略------------------------------------*/
}
- LayoutInflater - inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot)
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
//如果存在预编译,则可以通过tryInflatePrecompiled获取到View
View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
if (view != null) {
return view;
}
// 获取xml解析器
XmlResourceParser parser = res.getLayout(resource);
try {
// 调用这个方法来加载view
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
- LayoutInflater - inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot)
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
/*----------------------------省略------------------------------------*/
// 遍历获得根节点
advanceToRootNode(parser);
// 获得根节点名称
final String name = parser.getName();
// == merge
if (TAG_MERGE.equals(name)) {
if (root == null || !attachToRoot) {
rInflate(parser, root, inflaterContext, attrs, false);
} else {
// 通过createViewFromTag创建View
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
}
}
/*----------------------------省略------------------------------------*/
}
- LayoutInflater - createViewFromTag(View parent, String name, Context context, AttributeSet attrs, boolean ignoreThemeAttr)
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,boolean ignoreThemeAttr) {
/*----------------------------省略------------------------------------*/
// 通过这个方法来创建view
View view = tryCreateView(parent, name, context, attrs);
// 如果view为null的话,那么才会调用LayoutInflater的onCreateView方法或着createView方法
if (view == null) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
// 自定义控件 / 系统控件
if (-1 == name.indexOf('.')) {
view = onCreateView(context, parent, name, attrs);
} else {
view = createView(context, name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
/*----------------------------省略------------------------------------*/
}
- LayoutInflater - tryCreateView(@Nullable View parent, @NonNull String name,@NonNull Context context,@NonNull AttributeSet attrs)
public final View tryCreateView(@Nullable View parent, @NonNull String name,@NonNull Context context,@NonNull AttributeSet attrs) {
// 调用Factory的onCreateView来实现创建
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);
}
return view;
}
看到这里,我们考虑能不能我们实现一个Factory然后赋值进去,然后我们自己就能控制View的创建。
准备工作完成,下一篇实现颜色、图片等替换