requestFeature的實際運用

設置Activity懸浮

通過在styles.xml中設置windowIsFloating屬性實現Activity懸浮

<item name="android:windowIsFloating">true</item>
activity_floating.png

設置Activity能滑動消失

有兩種方式:

  1. styles.xml中設置windowSwipeToDismiss屬性
<item name="android:windowSwipeToDismiss">true</item>
  1. 在Activity中調用requestWindowFeature方法
requestWindowFeature(Window.FEATURE_SWIPE_TO_DISMISS);
activity_swipe_dismiss.gif

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

系統的注釋說明了要設置顯示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這個接口類型的實現類;
在布局screen_toolbar.xml中ActionBar是Toolbar類型的。

顯示默認的ActionBar

有兩種方式:

  1. styles.xml中設置windowActionBar屬性
<item name="android:windowNoTitle">false</item>
<item name="android:windowSwipeToDismiss">true</item>
  1. 在Activity中調用requestWindowFeature方法
<item name="android:windowNoTitle">false</item>
requestWindowFeature(Window.FEATURE_ACTION_BAR);
Activity_ActionBar.png

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

hierarchy_actionbar.png

打開此Activity的主題,這里我使用的是創建app時,系統默認使用的主題

<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">

點開AppTheme的繼承關系,一直找到Platform.AppCompat.Light時,這里針對不同版本的app使用了不同的values目錄:

values_tree.png

由于我這里使用的是API25的模擬器,則系統則會選擇values-v21.xml文件中的Platform.AppCompat.Light,繼續看繼承關系,找到了Theme.Material.Light這個主題,該主題中定義了windowActionBarFullscreenDecorLayout屬性:

<item name="windowActionBarFullscreenDecorLayout">@layout/screen_toolbar</item>

這就解釋了為什么在API25中action_bar是Toolbar。

而我們平常經常為了兼容各版本,通常Activity都會繼承AppCompatActivity,那么AppCompatActivity是如何來做ActionBar的兼容的呢?

假如只是更改一下將繼承Activity變為繼承AppCompatActivity,還是上面的例子,但是會出現兩個ActionBar:

AppCompatActivity_ActionBar.png

查看視圖樹:

hierarchy-actionbar-appcompat.png

為什么會顯示兩個標題呢?

查看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的流程,我們知道它會根據style中定義的AppCompatThemeWindow的屬性分別創建subDecormDecor。而根據主題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:windowActionBarwindowActionBar都為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方法是抽象類,找到該方法的具體實現:

AppCompatDelegateImplV9#initWindowDecorActionBar

  1. 確保創建了subDecor,這里的subDecor其實就是Activity的mDecor
ensureSubDecor();
  1. 創建WindowDecorActionBar
if (mOriginalWindowCallback instanceof Activity) {
    mActionBar = new WindowDecorActionBar((Activity) mOriginalWindowCallback,
            mOverlayActionBar);
} else if (mOriginalWindowCallback instanceof Dialog) {
    mActionBar = new WindowDecorActionBar((Dialog) mOriginalWindowCallback);
}
  1. 如果ActionBar不為空,設置顯示Home元素
if (mActionBar != null) {
    mActionBar.setDefaultDisplayHomeAsUpEnabled(mEnableDefaultActionBarUp);
}

比對一下AppCompatActivity和Activity中對于ActionBar的創建大同小異,都是使用initWindowDecorActionBar創建了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的屬性

根據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>

由于優先級高的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>
custom_actionbar.png
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,825評論 6 546
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,814評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,980評論 0 384
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 64,064評論 1 319
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,779評論 6 414
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,109評論 1 330
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,099評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,287評論 0 291
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,799評論 1 338
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,515評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,750評論 1 375
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,221評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,933評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,327評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,667評論 1 296
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,492評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,703評論 2 380

推薦閱讀更多精彩內容