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阅读 214,904评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,581评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,527评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,463评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,546评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,572评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,582评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,330评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,776评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,087评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,257评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,923评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,571评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,192评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,436评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,145评论 2 366
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,127评论 2 352

推荐阅读更多精彩内容