美文网首页Android进化之设置模块
Android 谈谈Activity、ActionBar与Too

Android 谈谈Activity、ActionBar与Too

作者: 锄禾豆 | 来源:发表于2020-01-13 08:53 被阅读0次

    案例

    <Toolbar
                android:id="@+id/action_bar"
                style="?android:attr/toolbarStyle"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:navigationContentDescription="@*android:string/action_bar_up_description"
                android:background="@drawable/actionbar_background"/>
    

    分析

    注意:代码来源8.1系统

    1.通过Activity设置ActionBar

    Activity.java
    public void setActionBar(@Nullable Toolbar toolbar) {
            final ActionBar ab = getActionBar();
            if (ab instanceof WindowDecorActionBar) {
                throw new IllegalStateException("This Activity already has an action bar supplied " +
                        "by the window decor. Do not request Window.FEATURE_ACTION_BAR and set " +
                        "android:windowActionBar to false in your theme to use a Toolbar instead.");
            }
    
            // If we reach here then we're setting a new action bar
            // First clear out the MenuInflater to make sure that it is valid for the new Action Bar
            mMenuInflater = null;
    
            // If we have an action bar currently, destroy it
            if (ab != null) {
                ab.onDestroy();
            }
    
            if (toolbar != null) {
                final ToolbarActionBar tbab = new ToolbarActionBar(toolbar, getTitle(), this);
                mActionBar = tbab;
                mWindow.setCallback(tbab.getWrappedWindowCallback());
            } else {
                mActionBar = null;
                // Re-set the original window callback since we may have already set a Toolbar wrapper
                mWindow.setCallback(this);
            }
    
            invalidateOptionsMenu();
        }
    mActionBar有两种对象:一种是WindowDecorActionBar,一种是ToolbarActionBar,不管是何种,都是继承ActionBar
    可见,如果我们能够设置ActionBar成功,代表我们用的就是ToolbarActionBar
    

    2.通过Activity获取ActionBar

    Activity.java
    public ActionBar getActionBar() {
            initWindowDecorActionBar();
            return 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
    

    3.单独分析ToolbarActionBar:

    我们会常用方法actionBar.setDisplayHomeAsUpEnabled(true)来显示导航键
    ToolbarActionBar.java
    public void setDisplayHomeAsUpEnabled(boolean showHomeAsUp) {
            setDisplayOptions(showHomeAsUp ? DISPLAY_HOME_AS_UP : 0, DISPLAY_HOME_AS_UP);
    }
    
    public void setDisplayOptions(@DisplayOptions int options, @DisplayOptions int mask) {
            final int currentOptions = mDecorToolbar.getDisplayOptions();
            mDecorToolbar.setDisplayOptions(options & mask | currentOptions & ~mask);
        }
    mDecorToolbar为ToolbarWidgetWrapper对象
    
    ToolbarWidgetWrapper.java
    public void setDisplayOptions(int newOpts) {
            final int oldOpts = mDisplayOpts;
            final int changed = oldOpts ^ newOpts;
            mDisplayOpts = newOpts;
            if (changed != 0) {
                if ((changed & ActionBar.DISPLAY_HOME_AS_UP) != 0) {
                    if ((newOpts & ActionBar.DISPLAY_HOME_AS_UP) != 0) {
                        updateHomeAccessibility();
                    }
                    updateNavigationIcon();
                }
    
                if ((changed & AFFECTS_LOGO_MASK) != 0) {
                    updateToolbarLogo();
                }
    
                if ((changed & ActionBar.DISPLAY_SHOW_TITLE) != 0) {
                    if ((newOpts & ActionBar.DISPLAY_SHOW_TITLE) != 0) {
                        mToolbar.setTitle(mTitle);
                        mToolbar.setSubtitle(mSubtitle);
                    } else {
                        mToolbar.setTitle(null);
                        mToolbar.setSubtitle(null);
                    }
                }
    
                if ((changed & ActionBar.DISPLAY_SHOW_CUSTOM) != 0 && mCustomView != null) {
                    if ((newOpts & ActionBar.DISPLAY_SHOW_CUSTOM) != 0) {
                        mToolbar.addView(mCustomView);
                    } else {
                        mToolbar.removeView(mCustomView);
                    }
                }
            }
        }
    从这里可以看到了我们熟悉的Toolbar控件,涉及Title、navigationicon及自定义的mCustomView。
    也就是控制Toolbar的都是类ToolbarWidgetWrapper。
    
    难道ToolbarActionBar没有关联Toolbar嘛?
    public ToolbarActionBar(Toolbar toolbar, CharSequence title, Window.Callback windowCallback) {
            mDecorToolbar = new ToolbarWidgetWrapper(toolbar, false);
            mWindowCallback = new ToolbarCallbackWrapper(windowCallback);
            mDecorToolbar.setWindowCallback(mWindowCallback);
            toolbar.setOnMenuItemClickListener(mMenuClicker);
            mDecorToolbar.setWindowTitle(title);
    }
    在ToolbarActionBar构造方法中,可以看到,它直接把传进来的toolbar赋给了ToolbarWidgetWrapper。
    想一想为什么要这么做?
    前面也提到了mActionBar对象可能有两种,如果都在对象中实现,那是不是做了很多重复工作?
    

    4.以上是通过在代码中设置到达要求,是否可以通过设置主题便可以使用Toolbar?

    关键点

    PhoneWindow.java
    protected ViewGroup generateLayout(DecorView decor) {
        ······
               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);//1
                } else {
                    layoutResource = R.layout.screen_title;
                }
        ······
    }
    注解1:常规情况下,我们Activity的View默认是:screen_action_bar,但是如果你设置的主题对
    windowActionBarFullscreenDecorLayout重新赋了值,效果就不一样了。
    1)此时,我们搜索framework-res.apk中的资源文件,找到如下:
    frameworks/base/core/res/res/values/themes_material.xml (2 matches)
    173: <item name="windowActionBarFullscreenDecorLayout">@layout/screen_toolbar</item> 
    541: <item name="windowActionBarFullscreenDecorLayout">@layout/screen_toolbar</item> 
    说明有这样的主题,分别对应:<style name="Theme.Material">和<style name="Theme.Material.Light" parent="Theme.Light">
    
    2)我们看一下screen_toolbar.xml(frameworks/base/core/res/res/layout)的成分:
    <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:keyboardNavigationCluster="true"
            android:gravity="top">
            <Toolbar
                android:id="@+id/action_bar"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:navigationContentDescription="@string/action_bar_up_description"
                style="?attr/toolbarStyle" />
            <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.ActionBarOverlayLayout>
    
    3)显然,此时用的就是Toolbar,对应的风格为toolbarStyle。也就是说,如果我们需要改
    Activity中ActionBar的标题、导航栏等相关信息,就直接重新定义toolbarStyle即可。
    例如,导航键和title之间的间距、title的字体风格等等,就从这里出发分析。
    

    整体代码思路


    ActionBar代码框架.png

    通过ActionBar怎么更快捷的退出Activity?

    设置ActionBar.png

    设置中的Activity实现就是如此,也就是重新封装了onNavigateUp方法:

    对Activity继承了,重新封装如下方法:
    public boolean onNavigateUp() {
            finish();
            return true;
    }
    

    这里再衍生一下,如果Activity在AndroidManifest.xml中定义android:parentActivityName,会怎么样?
    以设置代码为例:

    <activity android:name=".Settings$NetworkDashboardActivity"
                android:taskAffinity="com.android.settings"
                android:label="@string/qk_radio_controls_title"
                android:icon="@drawable/ic_settings_more"
                android:parentActivityName="Settings">
    此时点击ActionBar中的navigationicon,则会自动跳转到android:parentActivityName所定义的界面。
    

    对此功能进行源码详细分析:
    逆向分析思维

    针对设置主题涉及到的ToolBar
    
    1)查看ToolBar.java中设置点击事件的方法
    public void setNavigationOnClickListener(OnClickListener listener) {
            ensureNavButtonView();
            mNavButtonView.setOnClickListener(listener);
    }
    
    2)查找谁调用此public接口:
    ToolbarWidgetWrapper.java的构造方法中有如下设置:
    mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
                final ActionMenuItem mNavItem = new ActionMenuItem(mToolbar.getContext(),
                        0, android.R.id.home, 0, 0, mTitle);
                @Override
                public void onClick(View v) {
                    if (mWindowCallback != null && mMenuPrepared) {
                        mWindowCallback.onMenuItemSelected(Window.FEATURE_OPTIONS_PANEL, mNavItem);
                    }
                }
            });
    
    3)查看回调方法mWindowCallback.onMenuItemSelected。mWindowCallback具体是怎么来的呢?
    ToolbarWidgetWrapper.java
    public void setWindowCallback(Window.Callback cb) {
            mWindowCallback = cb;
    }
    
    4)调用setWindowCallback的类为ActionBarOverlayLayout.java
    public void setWindowCallback(Window.Callback cb) {
            pullChildren();
            mDecorToolbar.setWindowCallback(cb);
    }
    
    5)初始化ActionBarOverlayLayout的类为PhoneWindow.java
    private void installDecor() {
        ······
        DecorContentParent decorContentParent = (DecorContentParent) mDecor.findViewById(
                        R.id.decor_content_parent);
        if (decorContentParent != null) {
                    mDecorContentParent = decorContentParent;
                    mDecorContentParent.setWindowCallback(getCallback());
                    ······
         }
         ······
    }
    
    6)初始化PhoneWindow为Activity.java
     final void attach(······){
         mWindow = new PhoneWindow(this, window, activityConfigCallback);
         ·····
         mWindow.setCallback(this);
         ······
     }
     
     也就是最后是调用到了Activity.java
     public boolean onMenuItemSelected(int featureId, MenuItem item) {
            CharSequence titleCondensed = item.getTitleCondensed();
    
            switch (featureId) {
                case Window.FEATURE_OPTIONS_PANEL:
                    // Put event logging here so it gets called even if subclass
                    // doesn't call through to superclass's implmeentation of each
                    // of these methods below
                    if(titleCondensed != null) {
                        EventLog.writeEvent(50000, 0, titleCondensed.toString());
                    }
                    if (onOptionsItemSelected(item)) {//1
                        return true;
                    }
                    if (mFragments.dispatchOptionsItemSelected(item)) {
                        return true;
                    }
                    if (item.getItemId() == android.R.id.home && mActionBar != null &&
                            (mActionBar.getDisplayOptions() & ActionBar.DISPLAY_HOME_AS_UP) != 0) {
                        if (mParent == null) {
                            return onNavigateUp();//2
                        } else {
                            return mParent.onNavigateUpFromChild(this);
                        }
                    }
                    return false;
    
                case Window.FEATURE_CONTEXT_MENU:
                    if(titleCondensed != null) {
                        EventLog.writeEvent(50000, 1, titleCondensed.toString());
                    }
                    if (onContextItemSelected(item)) {
                        return true;
                    }
                    return mFragments.dispatchContextItemSelected(item);
    
                default:
                    return false;
            }
    }
    注解2:这里就是,onNavigateUp之所以被回调的真正原因
    
    小结:
    不管通过是什么方式,最后都会回调到Activity.onMenuItemSelected
    

    总结

    1.Activity设置和获取ActionBar
    2.ActionBar的目的是用来控制Toolbar控件。ActionBar本身自带menu功能
    3.Activity可以利用ActionBar做一些便捷的操作,例如onNavigateUp,onCreateOptionsMenu。

    相关文章

      网友评论

        本文标题:Android 谈谈Activity、ActionBar与Too

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