您的位置:首页 > 产品设计 > UI/UE

Android UI绘制流程(一)

2017-05-03 18:21 387 查看
本文将介绍Android UI的绘制流程。简单来说就是Android的界面是经过怎样的步骤来显示出来的。

我们一般都是创建Activity。然后在Activity的onCreate()方法中通过setContentView()来设置自己的布局。那么:

设置的布局加载到哪里了呢?

Activity和布局之间有什么样的关系呢?

1 首先从Activity的setContentView()方法开始分析。

public void setContentView(View view) {
getWindow().setContentView(view);
initWindowDecorActionBar();
}


可以看出在Activity的setContent()方法中,调用了

getWindow().setContentView(view);


而getWindow()方法返回的是Window对象

public Window getWindow() {
return mWindow;
}


也就是说在Activity的setContentView()方法调用的是Window的setContentView()方法。

2 下面来看Window的setContentView()方法。

public abstract void setContentView(@LayoutRes int layoutResID);


可以看出Window的setContentView()方法是个抽象的方法。那么Window的实现类是谁呢?从Window类的注释中可以看出:

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


Window类的唯一的实现类是PhoneWindow,接下来打开PhoneWindow来看在PhoneWindow中的setContentView()方法。我们可以发现根本就没有Link,全局也搜索不出这个类,说明这个类时隐藏的。那只好打开 SDK中的source文件夹中去搜索这个类。打开后:

3 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)) {
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();
}
}


3.1可以看出当mContentParent = null时,会调用installDecor()方法。

首先看下mContentParen
f75f
t 是个什么鬼

// 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.
private ViewGroup mContentParent;


这个注释说的很清晰。用我自己的话来说就是这个mContentParent就是Window用来显示内容的view.这个view有可能是mDecor也可能是mDecor的子类。

擦,那mDecor又是个什么东东?

// This is the top-level view of the window, containing the window decor.
private DecorView mDecor;


从注释中可以看出,mDecor是Window中最高水平的View。(在后文中可以看出这个mDecor就是一个FrameLayout)

在setContentView(View view, ViewGroup.LayoutParams params) 方法中:

有这一句:

if (mContentParent == null) {
installDecor();
}


哦,也就是说当mContentParent 为null时就去创建mDecor了?

3.2下面看PhoneWnidow中的installDecor()方法。(what a fuck? why this method is so long?)

private void installDecor() {
if (mDecor == null) {
mDecor = generateDecor();
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
}


这段代码有点长 我只复制了一部分。首先看这一段

if (mDecor == null) {
//3.2.1
mDecor = generateDecor();
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
}


当mDecor==null时,会调用generateDecor()方法。

3.2.1接下来看generateDecor()方法

protected DecorView generateDecor() {
return new DecorView(getContext(), -1);
}


可以看出在generateDecor()方法中创建了DecorView对象。那么DecorView到底是个什么呢?

private final class DecorView extends FrameLayout


原来这个DecorView就是一个Fragment。是PhoneWindow中的一个内部类。

接着看3.2中的这一段代码

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


这段代码调用了generateLayout()方法。

3.2.2接下来看generateLayout()方法。

这个方法有点长我截取几部分

1 首先获取主题的样式

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);
}


那么都获取了哪些属性呢?比如上面的这段。就是获取了是否显示标题栏。也就是屏幕最顶端的标题栏。并且如果标题栏不显示,那么ActionBar也不显示。

获取了主题的样式和属性后,接着

2 获取布局Id。

int layoutResource;


else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
// If no other features and not embedded, only need a title.
// If the window is floating, we need a dialog layout
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
R.attr.dialogTitleDecorLayout, res, true);
layoutResource = res.resourceId;
} else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
layoutResource = a.getResourceId(
R.styleable.Window_windowActionBarFullscreenDecorLayout,
R.layout.screen_action_bar);
} else {
layoutResource = R.layout.screen_title;
}
// System.out.println("Title!");
} else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
layoutResource = R.layout.screen_simple_overlay_action_mode;
} else {
// Embedded, so no decoration is needed.
layoutResource = R.layout.screen_simple;
// System.out.println("Simple!");
}


上面这段代码就是根据之前的样式所设置的属性来给布局id layoutResource来赋值。

可以看到有两种layoutResource被赋值的情况,如:

layoutResource = R.layout.screen_title;


layoutResource = R.layout.screen_simple;


那么这两种布局对应的是什么样的呢?打开源码可以看到:

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>


R.layout.screen_title

<com.android.internal.widget.ActionBarOverlayLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/decor_content_parent"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:splitMotionEvents="false"
android:theme="?attr/actionBarTheme">
<FrameLayout android:id="@android:id/content"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<com.android.internal.widget.ActionBarContainer
android:id="@+id/action_bar_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
style="?attr/actionBarStyle"
android:transitionName="android:action_bar"
android:touchscreenBlocksFocus="true"
android:gravity="top">
<com.android.internal.widget.ActionBarView
android:id="@+id/action_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?attr/actionBarStyle" />
<com.android.internal.widget.ActionBarContextView
android:id="@+id/action_context_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
style="?attr/actionModeStyle" />
</com.android.internal.widget.ActionBarContainer>
<com.android.internal.widget.ActionBarContainer android:id="@+id/split_action_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?attr/actionBarSplitStyle"
android:visibility="gone"
android:touchscreenBlocksFocus="true"
android:gravity="center"/>
</com.android.internal.widget.ActionBarOverlayLayout>


也就是说会根据不同主题样式来加载不同的布局文件。而这两个布局文件都有一个id为content的FramLayout。

3 把layoutResource对应的布局添加到mDecor中

View in = mLayoutInflater.inflate(layoutResource, null);
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));


至此 mDecor做为DecorView的对象,已经初始化完成了。但是

mDecor和我们在Activity中设置的布局id是怎么联系到一起的呢?

在setContentView(int layoutResID)方法中会调用installDecor()方法,在installDecor()方法中,用mDecor来构造mContentParent。

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


在generateLayout(DecorView decor)方法中会有如下这一段:

 View in = mLayoutInflater.inflate(layoutResource, null);
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
mContentRoot = (ViewGroup) in;

ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);


这段代码中contentParent就是最后返回的ViewGroup.也就是说这段代码执行后,将会有:

mContentParent = conentParent。而contentParent是mDecor中的id为content的FrameLayout。

/**
* The ID that the main layout in the XML layout file should have.
*/
public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;


在setContentView(int layoutResID)方法中

mLayoutInflater.inflate(layoutResID, mContentParent);


至此,自己设置的资源文件id已经添加到id为content的FramLayout中了。

用图片来表示:



下面在看文章开头的两个问题:

设置的布局加载到哪里了呢?

从上图可以看出,设置的布局layoutResID会添加到DecorView中的FramLayout中。

Activity和布局之间有什么样的关系呢?

1 Activity通过Window的API来完成界面的绘制

2 PhoneWindow是Window的子类

3 PhoneWindow中包含一个内部类DecorView,它是一个FramLayout。

4 Activity对应的布局会添加到即为DecorView中所包含的一个FramLayout中。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: