View绘制源码浅析(一)布局的加载
Posted zhuliyuan丶
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了View绘制源码浅析(一)布局的加载相关的知识,希望对你有一定的参考价值。
前言
说到View的绘制大部分人都能说上一两句,但细节实在太多如果没系统的去看很难吃透。近期我专门抽了一周多的时间读了绘制相关的源码,这里准备用三篇博客做一个系统的讲述,目录如下。
- View绘制源码浅析(一)布局的加载
- View绘制源码浅析(二)布局的测量、布局、绘制
- View绘制源码浅析(三)
requestLayout
、invalidate
与postInvalidate
三者的区别
本文的源码基于API27。
疑问
布局加载最重要的就是setContentView()
方法了,只需要我们传入一个布局id即可完成布局的加载,但实际上这里是有几个疑问的。
- 如何根据xml创建View的。
- 如何读取xml中View相关属性的。
- 创建的View添加到了哪。
接下来我们带着这些问题再去看源码,避免迷失方向。
setContentView()
我们先从setContentView()
这个布局加载的入口开始,看看究竟如何加载布局的。
//MainActivity.java
public class MainActivity extends AppCompatActivity //继承appCompatActivity
@Override
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);//布局加载的入口
//AppCompatActivity.java
@Override
public void setContentView(@LayoutRes int layoutResID)
getDelegate().setContentView(layoutResID);//拿到Activity的委托对象调用setContentView()
//AppCompatActivity.java
@NonNull
public AppCompatDelegate getDelegate() //获取Activity委托对象
if (mDelegate == null)
mDelegate = AppCompatDelegate.create(this, this);
return mDelegate;
//AppCompatDelegate.java
public static AppCompatDelegate create(Activity activity, AppCompatCallback callback) //创建Activity委托对象
return create(activity, activity.getWindow(), callback);//这里将activity.getWindow()传入。
//AppCompatDelegate.java
private static AppCompatDelegate create(Context context, Window window,
AppCompatCallback callback) //根据不同的版本创建不同的Activity委托对象
if (Build.VERSION.SDK_INT >= 24)
return new AppCompatDelegateImplN(context, window, callback);
else if (Build.VERSION.SDK_INT >= 23)
return new AppCompatDelegateImplV23(context, window, callback);
else
return new AppCompatDelegateImplV14(context, window, callback);
//AppCompatDelegateImplV9.java
//最终是调到v9的setContentView方法
@Override
public void setContentView(int resId)
ensureSubDecor();//确保SubDecor相关布局初始化完成
ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);//找到id为content的view
contentParent.removeAllViews();
LayoutInflater.from(mContext).inflate(resId, contentParent);//通过LayoutInflater直接把我们的布局添加到id为content的布局上
mOriginalWindowCallback.onContentChanged();
由于继承的是appCompatActivity
这个兼容的Activity
所以是根据不同的api版本创建不同的AppCompatDelegate
实现类以兼容老逻辑。setContentView()
最终是调到了AppCompatDelegateImplV9
的setContentView()
,接下来具体实现分为两步。
- 通过
ensureSubDecor()
方法确保SubDecor
相关布局初始化完成。 - 找到
SubDecor
中id为content的布局,将我们自己的布局inflater到content上。
这里说明下,SubDecor
不是DecorView
,只是一个变量名为subDecor
的ViewGroup
不过这里充当DecorView
的角色,不要混淆了。
这里先说第一步ensureSubDecor()
//AppCompatDelegateImplV9.java
private void ensureSubDecor() //确保SubDecor的创建
if (!mSubDecorInstalled) //如果没有创建SubDecor
mSubDecor = createSubDecor();//创建SubDecor
...
private ViewGroup createSubDecor()
TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);//拿到AppCompat相关的主题属性
//根据主题中的属性执行对应的Feature方法
if (a.getBoolean(R.styleable.AppCompatTheme_windowNoTitle, false))
requestWindowFeature(Window.FEATURE_NO_TITLE);//我们比较熟悉的FEATURE_NO_TITLE Feature
else if (a.getBoolean(R.styleable.AppCompatTheme_windowActionBar, false))
// Don't allow an action bar if there is no title.
requestWindowFeature(FEATURE_SUPPORT_ACTION_BAR);
if (a.getBoolean(R.styleable.AppCompatTheme_windowActionBarOverlay, false))
requestWindowFeature(FEATURE_SUPPORT_ACTION_BAR_OVERLAY);
if (a.getBoolean(R.styleable.AppCompatTheme_windowActionModeOverlay, false))
requestWindowFeature(FEATURE_ACTION_MODE_OVERLAY);
mIsFloating = a.getBoolean(R.styleable.AppCompatTheme_android_windowIsFloating, false);
a.recycle();
mWindow.getDecorView();//确保DecorView的创建
final LayoutInflater inflater = LayoutInflater.from(mContext);//用来填充SubDecor的inflater
ViewGroup subDecor = null;//subDecor布局
//接下来是根据主题属性初始化不同的subDecor布局
if (!mWindowNoTitle)
if (mIsFloating)
// If we're floating, inflate the dialog title decor
subDecor = (ViewGroup) inflater.inflate(
R.layout.abc_dialog_title_material, null);
// Floating windows can never have an action bar, reset the flags
mHasActionBar = mOverlayActionBar = false;
else if (mHasActionBar)
/**
* This needs some explanation. As we can not use the android:theme attribute
* pre-L, we emulate it by manually creating a LayoutInflater using a
* ContextThemeWrapper pointing to actionBarTheme.
*/
TypedValue outValue = new TypedValue();
mContext.getTheme().resolveAttribute(R.attr.actionBarTheme, outValue, true);
Context themedContext;
if (outValue.resourceId != 0)
themedContext = new ContextThemeWrapper(mContext, outValue.resourceId);
else
themedContext = mContext;
// Now inflate the view using the themed context and set it as the content view
subDecor = (ViewGroup) LayoutInflater.from(themedContext)
.inflate(R.layout.abc_screen_toolbar, null);
mDecorContentParent = (DecorContentParent) subDecor
.findViewById(R.id.decor_content_parent);
mDecorContentParent.setWindowCallback(getWindowCallback());
/**
* Propagate features to DecorContentParent
*/
if (mOverlayActionBar)
mDecorContentParent.initFeature(FEATURE_SUPPORT_ACTION_BAR_OVERLAY);
if (mFeatureProgress)
mDecorContentParent.initFeature(Window.FEATURE_PROGRESS);
if (mFeatureIndeterminateProgress)
mDecorContentParent.initFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
else
if (mOverlayActionMode)
subDecor = (ViewGroup) inflater.inflate(
R.layout.abc_screen_simple_overlay_action_mode, null);
else
subDecor = (ViewGroup) inflater.inflate(R.layout.abc_screen_simple, null);
if (Build.VERSION.SDK_INT >= 21)
// If we're running on L or above, we can rely on ViewCompat's
// setOnApplyWindowInsetsListener
ViewCompat.setOnApplyWindowInsetsListener(subDecor,
new OnApplyWindowInsetsListener()
@Override
public WindowInsetsCompat onApplyWindowInsets(View v,
WindowInsetsCompat insets)
final int top = insets.getSystemWindowInsetTop();
final int newTop = updateStatusGuard(top);
if (top != newTop)
insets = insets.replaceSystemWindowInsets(
insets.getSystemWindowInsetLeft(),
newTop,
insets.getSystemWindowInsetRight(),
insets.getSystemWindowInsetBottom());
// Now apply the insets on our view
return ViewCompat.onApplyWindowInsets(v, insets);
);
else
// Else, we need to use our own FitWindowsViewGroup handling
((FitWindowsViewGroup) subDecor).setOnFitSystemWindowsListener(
new FitWindowsViewGroup.OnFitSystemWindowsListener()
@Override
public void onFitSystemWindows(Rect insets)
insets.top = updateStatusGuard(insets.top);
);
if (subDecor == null) //检查SubDecor是否创建
throw new IllegalArgumentException(
"AppCompat does not support the current theme features: "
+ "windowActionBar: " + mHasActionBar
+ ", windowActionBarOverlay: "+ mOverlayActionBar
+ ", android:windowIsFloating: " + mIsFloating
+ ", windowActionModeOverlay: " + mOverlayActionMode
+ ", windowNoTitle: " + mWindowNoTitle
+ " ");
final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
R.id.action_bar_activity_content);//找到subDecor中id为action_bar_activity_content的布局
final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);//找到PhoneWindow中id为content的布局
if (windowContentView != null)
while (windowContentView.getChildCount() > 0) //将windowContentView的子布局全部添加到subDecor中id为action_bar_activity_content的布局上
final View child = windowContentView.getChildAt(0);
windowContentView.removeViewAt(0);
contentView.addView(child);
windowContentView.setId(View.NO_ID);//清除PhoneWindow中id为content的布局id
contentView.setId(android.R.id.content);//给subDecor中id为action_bar_activity_content的布局设置上新的id为content以假乱真
if (windowContentView instanceof FrameLayout)
((FrameLayout) windowContentView).setForeground(null);
// Now set the Window's content view with the decor
mWindow.setContentView(subDecor);//将subDecor设置到window上
return subDecor;
这里先拿到主题中的属性,然后根据主题中的属性设置对应的Feature,并根据条件创建对应的subDecor
。接下来拿到PhoneWindow
中id为content
的View,把它的子布局全部添加到subDecor中id为action_bar_activity_content
的布局上,然后将windowContentView
的id移除,给subDecor中id为action_bar_activity_content
的布局设置上新的id为content
以假乱真充当ContentView
的角色,最后将SubDecor
通过mWindow.setContentView(subDecor)
设置到window上。
那么经过ensureSubDecor()
方法后我们就完成了DecorView
和SubDecor
的初始化并通过mWindow.setContentView(subDecor)
将SubDecor
添加到了DecorView
上。完成了SubDecor
和DecorView
的关联。
在回到我们之前的setContentView()
//AppCompatDelegateImplV9.java
//最终是调到v9的setContentView方法
@Override
public void setContentView(int resId)
ensureSubDecor();//确保SubDecor相关布局初始化完成
ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);//找到id为content的view
contentParent.removeAllViews();
LayoutInflater.from(mContext).inflate(resId, contentParent);//通过LayoutInflater直接把我们的布局添加到id为content的布局上
mOriginalWindowCallback.onContentChanged();
完成SubDecor
初始化后,我们通过mSubDecor.findViewById(android.R.id.content)
找到contentParent
,然后直接LayoutInflater.from(mContext).inflate(resId, contentParent)
将布局添加到了contentParent
上完成了布局的添加。
那么对于第一和第二个问题则必须在LayoutInflater.inflate()
中寻找答案了,而第三个问题我们已经可以回答了
创建的View添加到了哪?
答:添加到了id为
android.R.id.content
的view上。
LayoutInflater.inflate()
接下来我们看下inflate()
是如何将我们的布局添加到id为content的view上的。
//LayoutInflater.java
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 XmlResourceParser parser = res.getLayout(resource);//根据资源id创建解析器
try
return inflate(parser, root, attachToRoot);
finally
parser.close();
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot)
synchronized (mConstructorArgs)
...
final Context inflaterContext = mContext;
final AttributeSet attrs = Xml.asAttributeSet(parser);
Context lastContext = (Context) mConstructorArgs[0];
mConstructorArgs[0] = inflaterContext;
View result = root;
try
// Look for the root node.
int type;
while ((type = parser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT) //拿到第一个节点
// Empty
if (type != XmlPullParser.START_TAG) //如果不是开始标签报错
throw new InflateException(parser.getPositionDescription()
+ ": No start tag found!");
final String name = parser.getName();//拿到节点名字
if (TAG_MERGE.equals(name)) //merge单独处理
if (root == null || !attachToRoot)
throw new InflateException("<merge /> can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
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);//创建xml中根布局
ViewGroup.LayoutParams params = null;
if (root != null)
if (DEBUG)
System.out.println("Creating params from root: " +
root);
// Create layout params that match root, if supplied
params = root.generateLayoutParams(attrs);//根据父布局创建xml中根布局的lp,因为自己的lp是跟父布局有关的。
if (!attachToRoot) //当满足root不为null并且attachToRoot为false则直接将lp设置到temp上
// Set the layout params for temp if we are not
// attaching. (If we are, we use addView, below)
temp.setLayoutParams(params);
if (DEBUG)
System.out.println("-----> start inflating children");
// Inflate all children under temp against its context.
rInflateChildren(parser, temp, attrs, true);//inflate temp的子布局,具体实现就是递归的把view添加到temp上
if (DEBUG)
System.out.println("-----> done inflating children");
// We are supposed to attach all the views we found (int temp)
// to root. Do that now.
if (root != null && attachToRoot) //如果root不为null 并且 attachToRoot为true则直接add到root上
root.addView(temp, params);
// Decide whether to return the root that was passed in or the
// top view found in xml.
if (root == null || !attachToRoot)
result = temp;
catch (XmlPullParserException e)
final InflateException ie = new InflateException(e.getMessage(), e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
catch (Exception e)
final InflateException ie = new InflateException(parser.getPositionDescription()
+ ": " + e.getMessage(), e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
finally
以上是关于View绘制源码浅析(一)布局的加载的主要内容,如果未能解决你的问题,请参考以下文章