Android中窗口添加ActionBar

有没有想过,在eclipse或studio中创建一个android项目,自动生成app工程后直接运行就能看到ActionBar的存在,而且看工程代码里并没有添加ActionBar相关的代码,这是为什么呢?很显然,在默认的情况下,系统(或兼容库)提供的窗口已集成了ActionBar,应用开发只需要使用它即可。

在应用setContentView的时候,系统会为这个窗口添加一个基础的布局来作为应用内容的容器。在Activity中setContentView最终会调用到PhoneWindow中的setContentView方法(关于PhoneWindow的构造过程可以参考其它资料),PhoneWindow是管理窗口基础内容的类。看看它的setContentView方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Override
public void setContentView(int layoutResID) {
if (mContentParent == null) {
installDecor();
} else {
mContentParent.removeAllViews();
}
mLayoutInflater.inflate(layoutResID, mContentParent);
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}

可以看到应用的content是添加到一个用变量mContentParent指向的布局类中,而installDecor方法就是初始化这个mContentParent的地方

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
private void installDecor() {
if (mDecor == null) {
mDecor = generateDecor();
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
....
}
}
protected ViewGroup generateLayout(DecorView decor) {
// Apply data from current theme.
TypedArray a = getWindowStyle();
....
// 从主题中读取窗口属性,是否无标题和是否需要ActionBar,这两个属性会影响ActionBar的有无
if (a.getBoolean(com.android.internal.R.styleable.Window_windowNoTitle, false)) {
requestFeature(FEATURE_NO_TITLE);
} else if (a.getBoolean(com.android.internal.R.styleable.Window_windowActionBar, false)) {
// Don't allow an action bar if there is no title.
requestFeature(FEATURE_ACTION_BAR);
}
....
// Inflate the window decor.
int layoutResource;
int features = getLocalFeatures();
// System.out.println("Features: 0x" + Integer.toHexString(features));
if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
...
} else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
&& (features & (1 << FEATURE_ACTION_BAR)) == 0) {
...
} else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
...
} else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
// 如果主题中没有请求上面奇怪的属性,就会进入到这里。一般应用使用的设备主题(DeviceDefault)、Material主题和Holo主题,都会进入到这里
// 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(
com.android.internal.R.attr.dialogTitleDecorLayout, res, true);
layoutResource = res.resourceId;
} else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {// 如果不是浮动窗口并且有ActionBar特效(上面requestFeature加入的)
if ((features & (1 << FEATURE_ACTION_BAR_OVERLAY)) != 0) {
layoutResource = com.android.internal.R.layout.screen_action_bar_overlay;
} else {
// 一般也不会请求overlay模式的ActionBar
layoutResource = com.android.internal.R.layout.screen_action_bar;
}
} else {
layoutResource = com.android.internal.R.layout.screen_title;
}
// System.out.println("Title!");
} else {
...
}
mDecor.startChanging();
// 使用带有ActionBar的布局,把它加入整个窗口的root view——decorView中
View in = mLayoutInflater.inflate(layoutResource, null);
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
// 从当前view tree中找到id为content的view group作为应用content的容器
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
...
mDecor.finishChanging();
return contentParent;
}

上面看到在installDecor过程中,使用一个布局文件inflate进整个窗口的view tree中。这个布局文件就是前面说的窗口的基础布局。我们看看带有ActionBar的布局是怎样的,找到screen_action_bar.xml这个文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<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>

看到这个布局中的view你应该明白点什么了吧,那么多带“ActionBar”字样的view,明显就是一套为ActionBar服务的布局。于是也应该纠正认为ActionBar就是一个view的想法,有许多view为ActionBar的功能服务呢。

回到初始化基础布局的流程中,读取两个属性windowNoTitlewindowActionBar来决定是否取带有上述一套ActionBar的view的布局。所以使用Theme.Material.Light.NoActionBar主题来实现没有标题栏的原理就是那么简单:

1
2
3
4
5
<!-- Variant of the material (light) theme with no action bar. -->
<style name="Theme.Material.Light.NoActionBar">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
</style>

就是这样,PhoneWindow会根据主题的设置来决定是否要集成一套为ActionBar服务的view。但是现在只是加入了view而已,刚刚说了ActionBar不是一个view,而是一套方法,通过这些方法去控制这些view的行为来实现它的功能。那么ActionBar是怎样和这些view关联在一起的呢?我们看回Activity的getActionBar()方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
* Retrieve a reference to this activity's ActionBar.
*
* @return The Activity's ActionBar, or null if it does not have one.
*/
@Nullable
public ActionBar getActionBar() {
initWindowDecorActionBar();
return mActionBar;
}
/**
* Creates a new ActionBar, locates the inflated ActionBarView,
* initializes the ActionBar with the view, and sets mActionBar.
*/
private void initWindowDecorActionBar() {
Window window = getWindow();
// Initializing the window decor can change window feature flags.
// Make sure that we have the correct set before performing the test below.
window.getDecorView();
if (isChild() || !window.hasFeature(Window.FEATURE_ACTION_BAR) || mActionBar != null) {
return;
}
mActionBar = new WindowDecorActionBar(this);
mActionBar.setDefaultDisplayHomeAsUpEnabled(mEnableDefaultActionBarUp);
mWindow.setDefaultIcon(mActivityInfo.getIconResource());
mWindow.setDefaultLogo(mActivityInfo.getLogoResource());
}

返回ActionBar的引用前进行一次初始化,然后看到真正返回的是WindowDecorActionBar的实例,这个类继承了ActionBar并实现了它的所有方法。在new这个WindowDecorActionBar的时候传人了当前Activity的引用。再看看WindowDecorActionBar的构造方法里做了什么操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
public WindowDecorActionBar(Activity activity) {
mActivity = activity;
Window window = activity.getWindow();
View decor = window.getDecorView();
boolean overlayMode = mActivity.getWindow().hasFeature(Window.FEATURE_ACTION_BAR_OVERLAY);
init(decor);
if (!overlayMode) {
mContentView = decor.findViewById(android.R.id.content);
}
}
private void init(View decor) {
mOverlayLayout = (ActionBarOverlayLayout) decor.findViewById(
com.android.internal.R.id.decor_content_parent);
if (mOverlayLayout != null) {
mOverlayLayout.setActionBarVisibilityCallback(this);
}
mDecorToolbar = getDecorToolbar(decor.findViewById(com.android.internal.R.id.action_bar));
mContextView = (ActionBarContextView) decor.findViewById(
com.android.internal.R.id.action_context_bar);
mContainerView = (ActionBarContainer) decor.findViewById(
com.android.internal.R.id.action_bar_container);
mSplitView = (ActionBarContainer) decor.findViewById(
com.android.internal.R.id.split_action_bar);
if (mDecorToolbar == null || mContextView == null || mContainerView == null) {
throw new IllegalStateException(getClass().getSimpleName() + " can only be used " +
"with a compatible window decor layout");
}
mContext = mDecorToolbar.getContext();
mContextDisplayMode = mDecorToolbar.isSplit() ?
CONTEXT_DISPLAY_SPLIT : CONTEXT_DISPLAY_NORMAL;
...
}

在构造方法里也是做一些初始化的工作,主要是从docor view(window的root view)中find到和ActionBar有关的view并保存到成员变量,那后续就可以控制这些view了。

至此,窗口就为应用集成了ActionBar

Comments