Android进化之设置模块

Android 谈谈Activity、ActionBar与Too

2020-01-13  本文已影响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。

上一篇 下一篇

猜你喜欢

热点阅读