設置Activity懸浮
通過在styles.xml中設置windowIsFloating屬性實現(xiàn)Activity懸浮
<item name="android:windowIsFloating">true</item>

設置Activity能滑動消失
有兩種方式:
- 在
styles.xml中設置windowSwipeToDismiss屬性
<item name="android:windowSwipeToDismiss">true</item>
- 在Activity中調用requestWindowFeature方法
requestWindowFeature(Window.FEATURE_SWIPE_TO_DISMISS);

000
設置是否顯示ActionBar
查看PhoneWindow的generateLayout方法
PhoneWindow#generateLayout
else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
// Don't allow an action bar if there is no title.
requestFeature(FEATURE_ACTION_BAR);
}
系統(tǒng)的注釋說明了要設置顯示ActionBar的前提條件是得設置窗口包含title,即設置Window的FEATURE_NO_TITLE屬性為false。
<item name="android:windowNoTitle">false</item>
在generateLayout方法中當FEATURE_NO_TITLE屬性為false的時候會進入下列條件判斷:
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!");
}
從上述代碼可以知道當設置了顯示ActionBar時,layoutResource會從windowActionBarFullscreenDecorLayout中取值,如果取不到值,默認為screen_action_bar布局。
了解了什么時候加載的ActionBar的布局,就有一個疑問,這個ActionBar的布局是在什么時候使用到的呢?
在Activity的setContentView方法中初始化了這個ActionBar
Activity#initWindowDecorActionBar
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());
}
從上述代碼中可以看到WindowDecorActionBar這個類的構造方法初始化了ActionBar
mDecorToolbar = getDecorToolbar(decor.findViewById(com.android.internal.R.id.action_bar));
而mDecorToolbar的類型則可能是DecorToolbar或者ToolbarWidgetWrapper
WindowDecorActionBar#getDecorToolbar
private DecorToolbar getDecorToolbar(View view) {
if (view instanceof DecorToolbar) {
return (DecorToolbar) view;
} else if (view instanceof Toolbar) {
return ((Toolbar) view).getWrapper();
} else {
throw new IllegalStateException("Can't make a decor toolbar out of " +
view.getClass().getSimpleName());
}
}
點擊com.android.internal.R.id.action_bar這個id查看布局中的ActionBar是什么類型,得知在布局screen_action_bar.xml中ActionBar是ActionBarView類型的,它是DecorToolbar這個接口類型的實現(xiàn)類;
在布局screen_toolbar.xml中ActionBar是Toolbar類型的。
顯示默認的ActionBar
有兩種方式:
- 在
styles.xml中設置windowActionBar屬性
<item name="android:windowNoTitle">false</item>
<item name="android:windowSwipeToDismiss">true</item>
- 在Activity中調用requestWindowFeature方法
<item name="android:windowNoTitle">false</item>
requestWindowFeature(Window.FEATURE_ACTION_BAR);

通過hierarchyviewer查看視圖樹我們可以看到id為action_bar的View在API25中是Toolbar類型的,從之前的分析得知actionbar的布局是由windowActionBarFullscreenDecorLayout屬性來決定的,但是我們定義主題的時候并未定義該屬性,那么這個屬性是在那里定義的呢?

打開此Activity的主題,這里我使用的是創(chuàng)建app時,系統(tǒng)默認使用的主題
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
點開AppTheme的繼承關系,一直找到Platform.AppCompat.Light時,這里針對不同版本的app使用了不同的values目錄:

由于我這里使用的是API25的模擬器,則系統(tǒng)則會選擇values-v21.xml文件中的Platform.AppCompat.Light,繼續(xù)看繼承關系,找到了Theme.Material.Light這個主題,該主題中定義了windowActionBarFullscreenDecorLayout屬性:
<item name="windowActionBarFullscreenDecorLayout">@layout/screen_toolbar</item>
這就解釋了為什么在API25中action_bar是Toolbar。
而我們平常經常為了兼容各版本,通常Activity都會繼承AppCompatActivity,那么AppCompatActivity是如何來做ActionBar的兼容的呢?
假如只是更改一下將繼承Activity變?yōu)槔^承AppCompatActivity,還是上面的例子,但是會出現(xiàn)兩個ActionBar:

查看視圖樹:

為什么會顯示兩個標題呢?
查看styles.xml中定義的主題:
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<item name="android:windowActionBar">true</item>
<item name="android:windowNoTitle">false</item>
</style>
看過之前分析AppCompatActivity的setContentView的流程,我們知道它會根據(jù)style中定義的AppCompatTheme和Window的屬性分別創(chuàng)建subDecor和mDecor。而根據(jù)主題Theme.AppCompat.Light.DarkActionBar搜索繼承關系找到Base.V7.Theme.AppCompat.Light主題,其中定義了有關ActionBar的屬性:
<style name="Base.V7.Theme.AppCompat.Light" parent="Platform.AppCompat.Light">
<item name="windowNoTitle">false</item>
<item name="windowActionBar">true</item>
<item name="windowActionBarOverlay">false</item>
<item name="windowActionModeOverlay">false</item>
</style>
由于android:windowActionBar和windowActionBar都為true,所以會用abc_screen_toolbar作為subDecor的布局,screen_toolbar作為mDecor的布局,這兩個布局中都含有Toolbar,所以界面會顯示兩個標題。
標題的顯示已經很明白了,那么如何使用這個它呢?我們在AppCompatActivity的子類中獲取ActionBar是通過getSupportActionBar方法來取代getActionBar的:
AppCompatActivity#getSupportActionBar
getDelegate().getSupportActionBar()
以我使用的API25的模擬器為例,最終在AppCompatDelegateImplN的父類AppCompatDelegateImplBase類中找到了getSupportActionBar方法:
AppCompatDelegateImplBase#getSupportActionBar
// The Action Bar should be lazily created as hasActionBar
// could change after onCreate
initWindowDecorActionBar();
return mActionBar;
initWindowDecorActionBar方法是抽象類,找到該方法的具體實現(xiàn):
AppCompatDelegateImplV9#initWindowDecorActionBar
- 確保創(chuàng)建了subDecor,這里的subDecor其實就是Activity的mDecor
ensureSubDecor();
- 創(chuàng)建WindowDecorActionBar
if (mOriginalWindowCallback instanceof Activity) {
mActionBar = new WindowDecorActionBar((Activity) mOriginalWindowCallback,
mOverlayActionBar);
} else if (mOriginalWindowCallback instanceof Dialog) {
mActionBar = new WindowDecorActionBar((Dialog) mOriginalWindowCallback);
}
- 如果ActionBar不為空,設置顯示Home元素
if (mActionBar != null) {
mActionBar.setDefaultDisplayHomeAsUpEnabled(mEnableDefaultActionBarUp);
}
比對一下AppCompatActivity和Activity中對于ActionBar的創(chuàng)建大同小異,都是使用initWindowDecorActionBar創(chuàng)建了WindowDecorActionBar類。
修改默認的ActionBar樣式
以AppCompatV7的ActionBar為例,已知當屬性windowActionBar為true時,會用abc_screen_toolbar作為subDecor的布局,且WindowDecorActionBar的getDecorToolbar方法會返回ToolbarWidgetWrapper類:
abc_screen_toolbar.xml
<android.support.v7.widget.Toolbar
android:id="@+id/action_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:navigationContentDescription="@string/abc_action_bar_up_description"
style="?attr/toolbarStyle"/>
ToolbarWidgetWrapper構造方法
final TintTypedArray a = TintTypedArray.obtainStyledAttributes(toolbar.getContext(),
null, R.styleable.ActionBar, R.attr.actionBarStyle, 0);
查看res\values\values.xml中的ActionBar這個自定義屬性集合:
<attr name="navigationMode">
<enum name="normal" value="0"/>
<enum name="listMode" value="1"/>
<enum name="tabMode" value="2"/>
</attr>
<attr name="displayOptions">
<flag name="none" value="0"/>
<flag name="useLogo" value="0x1"/>
<flag name="showHome" value="0x2"/>
<flag name="homeAsUp" value="0x4"/>
<flag name="showTitle" value="0x8"/>
<flag name="showCustom" value="0x10"/>
<flag name="disableHome" value="0x20"/>
</attr>
<attr name="title"/>
<attr format="string" name="subtitle"/>
<attr format="reference" name="titleTextStyle"/>
<attr format="reference" name="subtitleTextStyle"/>
<attr format="reference" name="icon"/>
<attr format="reference" name="logo"/>
<attr format="reference" name="divider"/>
<attr format="reference" name="background"/>
<attr format="reference|color" name="backgroundStacked"/>
<attr format="reference|color" name="backgroundSplit"/>
<attr format="reference" name="customNavigationLayout"/>
<attr name="height"/>
<attr format="reference" name="homeLayout"/>
<attr format="reference" name="progressBarStyle"/>
<attr format="reference" name="indeterminateProgressStyle"/>
<attr format="dimension" name="progressBarPadding"/>
<attr name="homeAsUpIndicator"/>
<attr format="dimension" name="itemPadding"/>
<attr format="boolean" name="hideOnContentScroll"/>
<attr format="dimension" name="contentInsetStart"/>
<attr format="dimension" name="contentInsetEnd"/>
<attr format="dimension" name="contentInsetLeft"/>
<attr format="dimension" name="contentInsetRight"/>
<attr format="dimension" name="contentInsetStartWithNavigation"/>
<attr format="dimension" name="contentInsetEndWithActions"/>
<attr format="dimension" name="elevation"/>
<attr format="reference" name="popupTheme"/>
上面這些是我們ActionBar的屬性
根據(jù)TypedArray流程分析,xml style的樣式是toolbarStyle,在我們的例子中,這個樣式在主題Base.V7.Theme.AppCompat.Light中定義:
<!-- Toolbar styles -->
<item name="toolbarStyle">@style/Widget.AppCompat.Toolbar</item>
而theme style defStyleAttr的樣式是actionBarStyle,也在主題Base.V7.Theme.AppCompat.Light中定義:
<item name="actionBarStyle">@style/Widget.AppCompat.Light.ActionBar.Solid</item>
Widget.AppCompat.Light.ActionBar.Solid
<style name="Base.Widget.AppCompat.ActionBar" parent="">
<item name="displayOptions">showTitle</item>
<item name="divider">?attr/dividerVertical</item>
<item name="height">?attr/actionBarSize</item>
<item name="titleTextStyle">@style/TextAppearance.AppCompat.Widget.ActionBar.Title</item>
<item name="subtitleTextStyle">@style/TextAppearance.AppCompat.Widget.ActionBar.Subtitle</it
...
<item name="android:gravity">center_vertical</item>
...
<item name="popupTheme">?attr/actionBarPopupTheme</item>
</style>
由于優(yōu)先級高的toolbarStyle并沒有什么關于ActionBar的屬性定義,所以ActionBar的屬性大部分定義在actionBarStyle中,所以我們可以這樣自定義ActionBar:
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<item name="actionBarStyle">@style/MyActionBarStyle</item>
</style>
<style name="MyActionBarStyle" parent="Widget.AppCompat.Light.ActionBar.Solid">
<item name="displayOptions">showHome|useLogo|showTitle|homeAsUp</item>
<item name="title">"自定義標題"</item>
<item name="subtitle">"自定義子標題"</item>
<item name="icon">@mipmap/ic_launcher</item>
<item name="logo">@android:drawable/btn_star</item>
</style>
