美文网首页
requestFeature的实际运用

requestFeature的实际运用

作者: CP9 | 来源:发表于2017-07-18 17:14 被阅读404次

    设置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

    相关文章

      网友评论

          本文标题:requestFeature的实际运用

          本文链接:https://www.haomeiwen.com/subject/rnacfxtx.html