一、ActionBar
1. 使用
- themes.xml
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.HelloWorld" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_500</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/white</item>
<!-- Customize your theme here. -->
</style>
</resources>
- menu_for_action_bar.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/search"
android:icon="@drawable/ic_baseline_search_24"
android:title="@string/app_name"
app:actionViewClass="androidx.appcompat.widget.SearchView"
app:showAsAction="ifRoom|collapseActionView" />
<item
android:id="@+id/add"
android:icon="@drawable/ic_baseline_add_circle_24"
android:title="@string/app_name"
app:showAsAction="ifRoom" />
<item
android:id="@+id/add1"
android:icon="@drawable/ic_baseline_add_circle_24"
android:title="@string/app_name"
app:showAsAction="ifRoom" />
<item
android:id="@+id/add2"
android:icon="@drawable/ic_baseline_add_circle_24"
android:title="@string/app_name"
app:showAsAction="ifRoom" />
<item
android:id="@+id/add3"
android:icon="@drawable/ic_baseline_add_circle_24"
android:title="@string/app_name"
app:showAsAction="ifRoom" />
</menu>
- ActionBarActivity.kt
class ActionBarActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
initActionBar()
}
private fun initActionBar() {
val actionBar = supportActionBar!!
// 1. 显示返回按钮
actionBar.setDisplayHomeAsUpEnabled(true)
// 2. 显示 logo
actionBar.setDisplayShowHomeEnabled(true)
actionBar.setDisplayUseLogoEnabled(true)
// both setLogo and setIcon are OK
// actionBar.setLogo(R.mipmap.ic_launcher)
actionBar.setIcon(R.mipmap.ic_launcher)
// 3. 显示主标题
actionBar.title = "Hello"
// 4. 显示副标题
actionBar.subtitle = "Hello World."
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
// 点击三个点后展开的 item 左侧默认不显示 icon,添加如下调用可以显示 icon,但是会提示 api 调用受限
// (menu as MenuBuilder).setOptionalIconsVisible(true)
menuInflater.inflate(R.menu.menu_for_action_bar, menu)
return super.onCreateOptionsMenu(menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
android.R.id.home -> finish()
R.id.add -> ToastUtils.showShort("add clicked.")
}
return super.onOptionsItemSelected(item)
}
}
-
效果
效果图.png
2. 层级
层级.png可以看到 ActionBar 是和 R.id.content 平级的,都隶属于 decor view。
Activity#getWindow()
Window#getDecorView()
从 ToolBar.java 的注释也可以看出,ActionBar 是受 Framework 控制的,并不隶属于应用 APP 的 UI,应用 APP 的 UI 都装在 R.id.content 里。
ToolBar.png
3. 区别 support 库
Activity#getActionBar()
AppCompatActivity#getSupportActionBar()
4. 实现
ActionBar 的实现类有两个,一个是 ToolbarActionBar,另一个是 WindowDecorActionBar。
ActionBar 实现类.png
其中,ToolbarActionBar 是为了桥接 ToolBar 和 ActionBar 的。从上面 ToolBar 的注释可以看出,可以指派一个 ToolBar 来作为 action bar,通过调用 setActionBar(Toolbar) 或者 setSupportActionBar(Toolbar) 方法即可,注意都要传入一个 Toolbar 作为参数。
另外, WindowDecorActionBar 则为默认情况下真正的 ActionBar 的具体实现,也就是说在 Activity 里调用 getActionBar() 或者 getSupportActionBar() 返回的是 WindowDecorActionBar 的实例。而 WindowDecorActionBar 的实例化在 AppCompatDelegateImpl 中。
是 WindowDecorActionBar 的实例.pngAppCompatDelegateImpl#initWindowDecorActionBar()
private void initWindowDecorActionBar() {
ensureSubDecor();
if (!mHasActionBar || mActionBar != null) {
return;
}
if (mHost instanceof Activity) {
mActionBar = new WindowDecorActionBar((Activity) mHost, mOverlayActionBar);
} else if (mHost instanceof Dialog) {
mActionBar = new WindowDecorActionBar((Dialog) mHost);
}
if (mActionBar != null) {
mActionBar.setDefaultDisplayHomeAsUpEnabled(mEnableDefaultActionBarUp);
}
}
而 WindowDecorActionBar 又间接持有了 ToolBar,看 init() 方法,调用了 getDecorToolBar() 方法。通过调试,发现传进 getDecorToolBar() 的 view 确实是 Toolbar 的实例,也就是说 R.id.action_bar 这个 id 代表的控件为一个 Toolbar 控件。
WindowDecorActionBar#init(View)
private void init(View decor) {
mOverlayLayout = (ActionBarOverlayLayout) decor.findViewById(R.id.decor_content_parent);
if (mOverlayLayout != null) {
mOverlayLayout.setActionBarVisibilityCallback(this);
}
mDecorToolbar = getDecorToolbar(decor.findViewById(R.id.action_bar));
mContextView = (ActionBarContextView) decor.findViewById(
R.id.action_context_bar);
mContainerView = (ActionBarContainer) decor.findViewById(
R.id.action_bar_container);
if (mDecorToolbar == null || mContextView == null || mContainerView == null) {
throw new IllegalStateException(getClass().getSimpleName() + " can only be used " +
"with a compatible window decor layout");
}
...
}
private DecorToolbar getDecorToolbar(View view) {
if (view instanceof DecorToolbar) {
return (DecorToolbar) view;
} else if (view instanceof Toolbar) {
// 通过调试,发现传进来的 view 确实是 Toolbar 的实例
return ((Toolbar) view).getWrapper();
} else {
throw new IllegalStateException("Can't make a decor toolbar out of " +
(view != null ? view.getClass().getSimpleName() : "null"));
}
}
再看 ToolBar 的 getWrapper() 方法
ToolBar#getWrapper()
public DecorToolbar getWrapper() {
if (mWrapper == null) {
mWrapper = new ToolbarWidgetWrapper(this, true);
}
return mWrapper;
}
可以看到,返回的是 ToolbarWidgetWrapper 的实例,也就是说 WindowDecorActionBar 持有 ToolbarWidgetWrapper,而 ToolbarWidgetWrapper 持有真正的 ToolBar。
后续的一系列调用,都是先到 WindowDecorActionBar,再到 ToolbarWidgetWrapper,然后到 ToolBar,比如 setTitle() 的调用。
private fun initActionBar() {
val actionBar = supportActionBar!!
// 1. 显示返回按钮
actionBar.setDisplayHomeAsUpEnabled(true)
// 2. 显示 logo
actionBar.setDisplayShowHomeEnabled(true)
actionBar.setDisplayUseLogoEnabled(true)
// both setLogo and setIcon are OK
// actionBar.setLogo(R.mipmap.ic_launcher)
actionBar.setIcon(R.mipmap.ic_launcher)
// 3. 显示主标题
actionBar.title = "Hello"
// 4. 显示副标题
actionBar.subtitle = "Hello World."
}
5. 结论
ActionBar 可以看成抽象出了一堆接口,具体的实现还是交由 ToolBar 去实现的
二、ToolBar
1. 使用
- ToolBarActivity.kt 直接按 ToolBar 来使用
class ToolBarActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
useToolBarDirectly()
}
private fun useToolBarDirectly() {
setContentView(R.layout.activity_tool_bar)
val toolBar: Toolbar = findViewById(R.id.tool_bar)
toolBar.setNavigationIcon(R.drawable.ic_baseline_arrow_back_24)
toolBar.title = "Hello"
toolBar.subtitle = "Use ToolBar Directly."
toolBar.setNavigationOnClickListener {
finish()
}
}
}
-
效果
直接按 ToolBar 来使用.png -
ToolBarActivity.kt 当成 ActionBar 来使用
class ToolBarActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
useToolBarAsActionBar()
}
private fun useToolBarAsActionBar() {
setContentView(R.layout.activity_tool_bar)
setSupportActionBar(findViewById(R.id.tool_bar))
val actionBar: ActionBar = supportActionBar!!
actionBar.setHomeAsUpIndicator(R.drawable.ic_baseline_arrow_back_24)
actionBar.setDisplayHomeAsUpEnabled(true)
actionBar.title = "Hello"
actionBar.subtitle = "Use ToolBar As ActionBar."
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
android.R.id.home -> finish()
}
return super.onOptionsItemSelected(item)
}
}
-
效果
当成 ActionBar 来使用.png
- AndroidManifest.xml
直接使用 **NoActionBar 主题
主题,如 Theme.MaterialComponents.DayNight.NoActionBar
<activity
android:name=".ui.toolbar.ToolBarActivity"
android:theme="@style/Theme.MaterialComponents.DayNight.NoActionBar" />
还是使用 **ActionBar 主题,如 Theme.MaterialComponents.DayNight.DarkActionBar,但是需要设置 windowNoTitle 和 windowActionBar
<activity
android:name=".ui.toolbar.ToolBarActivity"
android:theme="@style/MyTheme.ToolBarActivity" />
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.HelloWorld" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_500</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/white</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_700</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style>
<style name="MyTheme.ToolBarActivity" parent="Theme.HelloWorld">
<item name="windowNoTitle">true</item>
<item name="windowActionBar">false</item>
</style>
</resources>
- activity_tool_bar.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/my_root"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.toolbar.ToolBarActivity">
<androidx.appcompat.widget.Toolbar
android:id="@+id/tool_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/purple_500"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:subtitleTextColor="#d0b3fa"
app:titleTextColor="#ffffff" />
</androidx.constraintlayout.widget.ConstraintLayout>
使用和 ActionBar 是很类似的,但是更灵活,也更加方便。
2. 层级
其实从 activity 的布局文件也可以看出,ToolBar 不再直接隶属于 window decor,因为这里的 ToolBar 四需要我们手动放到 activity 的 xml 布局文件里,然后调用 setContentView() 生效的。
层级.png
结论
ToolBar 由自己放置在布局 xml 文件中,可以放置在任何地方(不限于顶部),可以直接在 xml 中设置相关属性,同时还可以在 xml 布局文件里添加子 View,使用上稍微灵活点。不过,对于简单需求,ActionBar 足以应付。
另外,ToolBar 既可以直接单独使用,当成一个独立的控件来用。如果用惯了 ActionBar 的 API,也可以把 ToolBar 按 ActionBar 来使用(需要调用 setSupportActionBar() 方法),其内部实现就要用到上面提到的 ToolbarActionBar 了,看 AppCompatDelegateImpl.java 源码:
@Override
public void setSupportActionBar(Toolbar toolbar) {
if (!(mHost instanceof Activity)) {
// Only Activities support custom Action Bars
return;
}
final ActionBar ab = getSupportActionBar();
if (ab instanceof WindowDecorActionBar) {
throw new IllegalStateException("This Activity already has an action bar supplied " +
"by the window decor. Do not request Window.FEATURE_SUPPORT_ACTION_BAR and set " +
"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(),
mAppCompatWindowCallback);
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(mAppCompatWindowCallback);
}
invalidateOptionsMenu();
}
从代码可以看出 WindowDecorActionBar 和 ToolbarActionBar 是不能共存的,这也就是为什么使用 ToolBar 时需要设置主题为 **NoActionBar
的原因了。
其它
windowNoTitle 和 android:windowNoTitle 的区别
- windowNoTitle 用于 AppCompatActivity
- android:windowNoTitle 用于 Activity
- windowNoTitle = false 并且 android:windowNoTitle = false 时,会出现两个标题(继承 AppCompatActivity 的情况下)
windowNoTitle 和 windowActionBar 的区别
- windowNoTitle 用于控制是否显示标题栏,windowNoTitle = true 时,windowActionBar 的值不重要
- windowNoTitle = false 时,若 windowActionBar = false,会报错: AppCompat does not support the current theme features
最后
鼓掌.png
网友评论