源码:setContentView(),Activity与App
Activity.setContentView() 界面是怎么实例化的。
目前能做到,获取另外一个没安装的apk的资源文件(网上下载的)
实现换肤功能:
- 每一次打开的都是新的皮肤;
- 换肤之后,所有的Activity里面的View都要换肤;
- 每次进入app也需要换肤。
解决方案:
- 每次进入activity,都要把需要换肤的View找出来,然后调用代码去换肤。比较死板的一种方法。
- 拿到根布局RootView,然后遍历里面的子View,通过tag
android:tag="skin:background:blue_color@@textColor:black_color"
解析tag,然后获取内容。设置背景和文字颜色。 - 拦截View的创建,目前是比较好的方案。创建的时候,所有的View 添加到集合中。
点击换肤,遍历集合中所有的View, 去改变背景,color等。
系统是如何加载界面的?
Activity.setContentView()
Activity.setContentView()-- getWindow().setContentView(layoutResID)
PhoneWindow.setContentView(layoutResID)-- installDecor()
-- mDecor = generateDecor()
-- mContentParent = generateLayout(mDecor) // 找到叫android.R.id.content 的FrameLayout。
-- mLayoutInflater.inflate(layoutResID, mContentParent)
DecorView 继承自FrameLayout。
系统布局里面有个FrameLayout,它的id 是 com.android.internal.R.id.content
public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
mContentParent 就是内容的布局。
// PhoneWindow.java
protected ViewGroup generateLayout(DecorView decor) {
// Inflate the window decor。加载源码里面系统的布局文件。
// 找到合适的系统布局资源(eg:screen_simple,screen_simple_overlay_action_mode )
int layoutResource;
View in = mLayoutInflater.inflate(layoutResource, null);
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); // 加载系统的布局到decor中。
mContentRoot = (ViewGroup)in;
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
return contentParent;
}
// layoutResID 就是 activity_main.xml, 也就是将自己的layout 加载到mContentParent中。
mLayoutInflater.inflate(layoutResID, mContentParent);
AppCompatActivity.setContentView() 区别?
主要是为了兼容低版本。
setContentView(layoutResID)
-- getDelegate().setContentView(layoutResID)
---- mDelegate = AppCompatDelegate.create(this, this);
------ create(activity, activity.getWindow(), callback) // 里面针对23,14,11 做不同的兼容。
------ return AppCompatDelegateImplV7(context, window, callback)
-- AppCompatDelegateImplV7.setContentView()
// AppCompatDelegateImplV7
@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();
}
// AppCompatDelegateImplV7
AppCompatDelegateImplV7 implements LayoutInflaterFactory (里面只有onCreateView(parent,name,context,attrs)接口)
// AppCompatDelegateImpl
@Override
public void installViewFactory() {
LayoutInflater layoutInflater = LayoutInflater.from(mContext);
if (layoutInflater.getFactory() == null) {
LayoutInflaterCompat.setFactory2(layoutInflater, this);
} else {
if (!(layoutInflater.getFactory2() instanceof AppCompatDelegateImpl)) {
Log.i(TAG, "The Activity's LayoutInflater already has a Factory installed"
+ " so we can not install AppCompat's");
}
}
}
// LayoutInflaterCompat
public static void setFactory2(
@NonNull LayoutInflater inflater, @NonNull LayoutInflater.Factory2 factory) {
inflater.setFactory2(factory);
if (Build.VERSION.SDK_INT < 21) {
final LayoutInflater.Factory f = inflater.getFactory();
if (f instanceof LayoutInflater.Factory2) {
// The merged factory is now set to getFactory(), but not getFactory2() (pre-v21).
// We will now try and force set the merged factory to mFactory2
forceSetFactory2(inflater, (LayoutInflater.Factory2) f);
} else {
// Else, we will force set the original wrapped Factory2
forceSetFactory2(inflater, factory);
}
}
}
// AppCompatDelegateImpl.createView
// --mAppCompatViewInflater.createView()
// AppCompatViewInflater.createView() switch-case,完成View的替换
// 只要继承自AppCompatActivity,创建的View都会走入AppCompatViewInflater.createView()
// 这个方法. 在这个方法中完成View的替换。
总结:区别:
ImageView mImageView;
继承自 Activity: 图片对象是 android.widget.ImageView 对象。
继承自 AppCompatActivity: 图片对象是 android.support.v7.widget.AppCompatImageView 对象。
这样做到低版本的兼容。比如tint属性可在低版本中使用。
原因:创建View的时候,AppCompatActivity 会拦截,不会走系统的LayoutInflater的创建。
从而View就会被替换掉。从而达到兼容低版本,让低版本也能使用Material Design等特性。