案例
<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。
网友评论