Android——UI(一):UI绘制流程

xiaoxiao2021-02-28  91

UI绘制流程

1、为什么调用setContentView之后就可以显示我们想要的布局?

进入setContentView方法可以看到

public void setContentView(@LayoutRes int layoutResID) { getWindow().setContentView(layoutResID); initWindowDecorActionBar(); }

其中getWindow()返回了一个Window对象,Window是一个抽象类,Window中有很多抽象方法,最常见的就是我们经常写的findViewById。 通过注释

/** * Abstract base class for a top-level window look and behavior policy. An * instance of this class should be used as the top-level view added to the * window manager. It provides standard UI policies such as a background, title * area, default key processing, etc. * *

The only existing implementation of this abstract class is * android.view.PhoneWindow, which you should instantiate when needing a * Window. */

可以看到Window处于最顶层,并且在Android中只有PhoneWindow一个子类。

进入PhoneWindow的setContentView可以看到

@Override public void setContentView(int layoutResID) { // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window // decor, when theme attributes and the like are crystalized. Do not check the feature // before this happens. if (mContentParent == null) { installDecor(); } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) { mContentParent.removeAllViews(); } if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) { //Activity的转场动画 final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID, getContext()); transitionTo(newScene); } else { mLayoutInflater.inflate(layoutResID, mContentParent); } mContentParent.requestApplyInsets(); final Callback cb = getCallback(); if (cb != null && !isDestroyed()) { cb.onContentChanged(); } mContentParentExplicitlySet = true; }

首先是对mContentParent 进行判空,这个mContentParent 是一个ViewGroup,通过注释

// This is the view in which the window contents are placed. It is either // mDecor itself, or a child of mDecor where the contents go.

可以看到mContentParent 就是Window的内容,注释中的mDecor是DecorView,Android中最顶层的View。

往下看会看到mLayoutInflater.inflate(layoutResID, mContentParent); 将id设置到ViewGroup中,这里暂不做深入。

当mContentParent为空时会执行installDecor(); 进去之后,找到

if (mContentParent == null) { mContentParent = generateLayout(mDecor);

继续查看generateLayout(mDecor)

TypedArray a = getWindowStyle(); if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) { requestFeature(FEATURE_NO_TITLE); } else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) { // Don't allow an action bar if there is no title. requestFeature(FEATURE_ACTION_BAR); }

这里代码是不是很熟悉,在这里就会获取到Window的style。 继续往下看

int layoutResource; int features = getLocalFeatures(); // System.out.println("Features: 0x" + Integer.toHexString(features)); if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) { layoutResource = R.layout.screen_swipe_dismiss; } else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) { if (mIsFloating) { TypedValue res = new TypedValue(); getContext().getTheme().resolveAttribute( R.attr.dialogTitleIconsDecorLayout, res, true); layoutResource = res.resourceId; } else { layoutResource = R.layout.screen_title_icons; }

在这里通过不同的features去加载不同的layout,同时也说明了,requestFeature必须在setContentView之前进行调用。

再来看layoutResource即将加载的布局是什么,在这里以R.layout.screen_simple为例,进去之后可以看到

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:fitsSystemWindows="true" android:orientation="vertical"> <ViewStub android:id="@+id/action_mode_bar_stub" android:inflatedId="@+id/action_mode_bar" android:layout="@layout/action_mode_bar" android:layout_width="match_parent" android:layout_height="wrap_content" android:theme="?attr/actionBarTheme" /> //显示我们布局的地方 <FrameLayout android:id="@android:id/content" android:layout_width="match_parent" android:layout_height="match_parent" android:foregroundInsidePadding="false" android:foregroundGravity="fill_horizontal|top" android:foreground="?android:attr/windowContentOverlay" /> </LinearLayout>

小结:

每个Activity都有一个关联的Window抽象类,用来描述应用程序窗口。在Android中,Window有且仅有一个实现类PhoneWindowPhoneWindow中包含了一个DecorView我们自己设置的布局在DecorView下的FrameLayout中最后通过mLayoutInflater.inflate将id与mContentParent进行关联

2、LayoutInflater如何把xml转成View?

为什么include不能作为xml资源文件布局根节点? 为什么merge只能作为xml资源文件布局根节点?

带着疑问继续看源码。

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) + ")"); } //获取xml解析器 final XmlResourceParser parser = res.getLayout(resource); try { //使用解析器获取View return inflate(parser, root, attachToRoot); } finally { parser.close(); } }

进入inflate(parser, root, attachToRoot)继续看,

//获取xml中的属性集 final AttributeSet attrs = Xml.asAttributeSet(parser); int type; //获取根节点 while ((type = parser.next()) != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT) { // Empty } final String name = parser.getName(); if (TAG_MERGE.equals(name)) { 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 { 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); } // 获取xml上的属性,在自定义属性时经常用到 params = root.generateLayoutParams(attrs); if (!attachToRoot) { // 设置属性 temp.setLayoutParams(params); } } // 解析完父容器之后继续解析子控件 rInflateChildren(parser, temp, attrs, true); final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException { rInflate(parser, parent, parent.getContext(), attrs, finishInflate); } void rInflate(XmlPullParser parser, View parent, Context context, AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException { final int depth = parser.getDepth(); int type; //遍历节点 while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) { if (type != XmlPullParser.START_TAG) { continue; } final String name = parser.getName(); if (TAG_REQUEST_FOCUS.equals(name)) { parseRequestFocus(parser, parent); } else if (TAG_TAG.equals(name)) { parseViewTag(parser, parent, attrs); } else if (TAG_INCLUDE.equals(name)) { //判断是否是跟标签 if (parser.getDepth() == 0) { //include不能作为跟标签 throw new InflateException("<include /> cannot be the root element"); } parseInclude(parser, context, parent, attrs); } else if (TAG_MERGE.equals(name)) { //merge必须作为跟标签 throw new InflateException("<merge /> must be the root element"); } else { //最终将子布局添加到父布局中 final View view = createViewFromTag(parent, name, context, attrs); final ViewGroup viewGroup = (ViewGroup) parent; final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs); rInflateChildren(parser, view, attrs, true); viewGroup.addView(view, params); } } if (finishInflate) { parent.onFinishInflate(); } }

转载请注明原文地址: https://www.6miu.com/read-59069.html

最新回复(0)