Loading... ## 前言 ### 为什么使用Toolbar - MD设计,优化整体视觉体验. ### 使用注意事项 - V7包的兼容,theme的设置. - 版本API的兼容适配. ### 源码查看方式: 1. 在线: <span class="external-link"><a href="https://android.googlesource.com/platform/frameworks/support/+/android-8.1.0_r60/v7/appcompat/src/android/support/v7/widget/Toolbar.java" target="_blank">Toolbar.java地址: (版本Android 8.1)<i data-feather='external-link'></i></a></span> 2. 使用Git克隆到本地: > git clone https://android.googlesource.com/platform/frameworks/support > ## 官网API说明 **说明:** Toolbar在实际导入的时候会发现是有两个包的,一般使用v7包,因为要兼容低版本的缘故 <span class="external-link"><a href="https://developer.android.com/reference/android/support/v7/widget/Toolbar" target="_blank">官网的v7包下 Toolbar API说明地址<i data-feather='external-link'></i></a></span> (PS: 国内需要科学上网) **Toolbar包结构:** public class Toolbar extends ViewGroup > java.lang.Object > > ↳ android.view.View > > ↳ android.view.ViewGroup > > ↳ android.support.v7.widget.Toolbar **注意:** 1. 保留部分专有名词原英文名称,便于提炼. 2. 翻译内容仅供参考,不对的地方欢迎指正. **简要翻译:** Toolbar是应用内部使用的一个通用导航栏,一个Activity需要选择使用Toolbar需要使用 setSupportActionBar()方法 Toolbar 比ActionBar支持了更多的新功能,一个Toolbar可能包含以下可选的元素的组合. - *A navigation button.*,这个可能是一个Up arrow, navigation menu toggle, close, collapse, done 或者 another glyph 或者其他app的选择, 这个按钮应该总是用于访问Toolbar内的其他导航目标和指定的内容,如果设置了,则导航按钮在Toolbar的最小高度内垂直对齐 - *A branded logo image.* 这个可能自适应bar的高度和任意宽度 - *A title and subtitle.* 一级标题应该是当前Toolbar的层次位置以及其中包含的内容的路标,二级标题,如果当前应该指向任何关于当前内容的扩展. 如果一个app使用了logo图片,则它应该强烈考虑忽略一级和二级标题 - *One or more custom views.* 这个应用如果要添加任意的子view到Toolbar,则他们将显示在布局中的这个位置, 如果一个子view 的Toolbar.LayoutParams 设置了Gravity的值是**CENTER_HORIZONTAL**,则这个view将会显示在Toolbar其他元素被测量后的剩余可用位置的中间. - *An action menu.* 这个action menu将固定到Toolbar的最后位置,用来提供一些**频繁,重要或者典型**的操作以及可选的overflow menu以执行其他操作. 如果设置了,则action menu在Toolbar的最小高度内垂直对齐 在Android UI上, 开发者应该更多地依赖于Toolbar的视觉上不同的颜色方案而不是应用程序图标, API 21及其以上不鼓励使用应用的 icon plus title作为标准布局。 ## 源码解析 看源码的顺序(针对View/ViewGroup子类): 1. 构造 -> onMeasure -> onLayout -> draw(针对view)/onDraw(针对ViewGroup) 2. 实现关系与内部类 **取其中的Title源码流程来进行说明,其他设置子view的流程类似,menu会稍微复杂一些,可自行阅读源码.** **setTitle的流程大致如下:** <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAABS2lUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4KPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxMzggNzkuMTU5ODI0LCAyMDE2LzA5LzE0LTAxOjA5OjAxICAgICAgICAiPgogPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIi8+CiA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgo8P3hwYWNrZXQgZW5kPSJyIj8+IEmuOgAAAA1JREFUCJljOHz4cAMAB2ACypfyMOEAAAAASUVORK5CYII=" data-original="https://s2.ax1x.com/2019/01/17/kpuk0U.png" alt="Toolbar" /> ## 效果图实现&&分析 **项目环境:** Android Studio 3.0.1 + JDK 1.8 **测试手机:** ALLVIEW X5_Soul_Pro Android 8.1.0,API 27 **语言:** Kotlin **文件配置:** build.gradle 文件 ```gradle dependencies { classpath 'com.android.tools.build:gradle:3.0.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } ``` app下的build.gradle 文件 ```gradle compileSdkVersion 28 defaultConfig { applicationId "com.litchiny.testtoolbar" minSdkVersion 18 targetSdkVersion 28 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } ``` gradle-wrapper.properties 文件 ``` distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip ``` **效果图** <img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAABS2lUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4KPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxMzggNzkuMTU5ODI0LCAyMDE2LzA5LzE0LTAxOjA5OjAxICAgICAgICAiPgogPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIi8+CiA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgo8P3hwYWNrZXQgZW5kPSJyIj8+IEmuOgAAAA1JREFUCJljOHz4cAMAB2ACypfyMOEAAAAASUVORK5CYII=" data-original="https://s2.ax1x.com/2019/01/17/kpBOx0.png" alt="Show" /> **正确使用Toolbar的三步骤**: 1. 去除原ActionBar的显示. 2. Xml布局里设置Toolbar的相关设置. 3. 代码设置需要使用的Toolbar的属性和点击监听(PS: 一些设置生效是在版本21及其以上生效的). #### 以下是代码部分: **MainActivity.kt** ```kotlin class ToolbarActivity : AppCompatActivity() { private lateinit var toolbar: Toolbar private lateinit var context: Context override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_toolbar) this.context = this initView() initListener() } private fun initView() { toolbar = findViewById<Toolbar>(R.id.tb_show) //toolbar.title 设置说明:等同于2行设置,如果位置换到了3行的后面,则1行的设置失效,原因:参考问题Q4 toolbar.title = "一级标题" //1行 setSupportActionBar(toolbar) //3行 设置允许使用Toolbar // supportActionBar?.setDisplayHomeAsUpEnabled(true) //设置返回箭头,生效的前提: 不设置自定义的navigationIcon toolbar.navigationIcon = ContextCompat.getDrawable(context, R.mipmap.left) //设置自定义的 navigationIcon toolbar.logo = ContextCompat.getDrawable(context, R.mipmap.home) //设置logo,位置在title/subtitle的左侧 // supportActionBar?.title = "这是一级标题" //2行 直接使用toolbar.title在设置3行后的设置是不生效的 toolbar.subtitle = "这是二级标题" //设置subtitle的内容 toolbar.setTitleTextAppearance(context, R.style.ToolbarTitleText) //设置title的字体大小,颜色 16sp 黄色 toolbar.setSubtitleTextAppearance(context, R.style.ToolbarSubtitleText) //设置subtitle的字体大小,颜色 20sp 白色 if (Build.VERSION.SDK_INT >= 21) toolbar.setElevation(2f) } private fun initListener() { toolbar.setNavigationOnClickListener { showToast("Navigation 被点击了") } toolbar.setOnClickListener { showToast("id: ${it.id}") } setOnMenuItemClick() } //与onOptionsItemSelected 等同 private fun setOnMenuItemClick() { toolbar.setOnMenuItemClickListener { when (it.itemId) { R.id.action_edit, R.id.action_share, R.id.action_about -> { showToast("${it.title} 被点击了") true } else -> { false } } } } //与setOnMenuItemClick等同 override fun onOptionsItemSelected(item: MenuItem?): Boolean { when (item?.itemId) { R.id.action_edit, R.id.action_share, R.id.action_about -> { showToast("${item.title} 被点击了") } } return super.onOptionsItemSelected(item) } //需要是menu的配置内容生效 override fun onCreateOptionsMenu(menu: Menu?): Boolean { getMenuInflater().inflate(R.menu.menu_main, menu); return true } private fun showToast(descStr: String) { Toast.makeText(this, descStr, Toast.LENGTH_SHORT).show() } } ``` **activity_toolbar.xml** ```xml <?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent"> <android.support.v7.widget.Toolbar android:id="@+id/tb_show" android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" android:background="?attr/colorPrimary" app:popupTheme="@style/ThemeOverlay.AppCompat.Light" app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" /> </android.support.constraint.ConstraintLayout> ``` **styles.xml** ```xml <?xml version="1.0" encoding="utf-8"?> <resources> <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar"> <item name="colorPrimary">@color/colorPrimary</item> <item name="colorPrimaryDark">@color/colorPrimaryDark</item> <item name="colorControlNormal">@color/colorWhite</item> <item name="android:windowBackground">@color/colorAccent</item> </style> <!--重设Title的字体 大小,颜色--> <style name="ToolbarTitleText" parent="TextAppearance.Widget.AppCompat.Toolbar.Title"> <item name="android:textSize">16sp</item> <item name="android:textColor">@color/colorWhite</item> </style> <!--重设Subtitle的字体 大小,颜色--> <style name="ToolbarSubtitleText" parent="TextAppearance.Widget.AppCompat.Toolbar.Subtitle"> <item name="android:textSize">20sp</item> <item name="android:textColor">@color/colorYellow</item> </style> </resources> ``` **colors.xml** ```xml <?xml version="1.0" encoding="utf-8"?> <resources> <color name="colorPrimary">#3F51B5</color> <color name="colorPrimaryDark">#303F9F</color> <color name="colorAccent">#FF4081</color> <color name="colorWhite">#FFFFFF</color> <color name="colorYellow">#f4f000</color> </resources> ``` **menu_main.xml** ```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" xmlns:tools="http://schemas.android.com/tools" tools:context=".ToolbarActivity"> <item android:id="@+id/action_edit" android:title="Search" android:orderInCategory="80" android:icon="@mipmap/search" app:showAsAction="ifRoom" /> <item android:id="@+id/action_share" android:title="Share" android:orderInCategory="90" app:showAsAction="never" /> <item android:id="@+id/action_about" android:title="About" android:orderInCategory="100" app:showAsAction="never"/> <!--showAsAction:--> <!--1. always:一直显示在ToolBar上.--> <!--2. ifRoom:如果Toolbar有足够的空间,则显示在ToolBar上--> <!--3. never:永远不出现在ToolBar上,在…的item中显示--> <!--4. withText:使菜单项和它的图标,菜单文本一起显示--> </menu> ``` **遇到的问题:** Q1: Logo,Title,SubTitle不能单独设计点击事件 A1: 不使用Toolbar内置的此三个控件,或继承Toolbar后重写相关的方法 Q2: Logo, NavigationView的图片大小需要适配调整 A2: UI给出合适的尺寸适配(PS: 效果图里的图片大小是128x128,格式: PNG) Q3: Menu的more的图标显示是黑色的 A3: 自带的theme设置成Dark,popup设置成Light 参考: ```xml app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" app:popupTheme="@style/ThemeOverlay.AppCompat.Light" ``` Q4: toolbar.title在setSupportActionBar(toolbar) 设置后失效的问题 A4: 看AppCompatDelegateImpl.java的源码 在方法 setSupportActionBar里重新给mActionBar赋值的时候,会重设title,地址已发生了变化,所以直接toolbar.title失效 源码如下: ```java public void setSupportActionBar(Toolbar toolbar) { //...省略一些校验的代码 if (toolbar != null) { final ToolbarActionBar tbab = new ToolbarActionBar(toolbar, ((Activity) mOriginalWindowCallback).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(); } ``` **参考文章如下:** 1. http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2014/1118/2006.html 2. https://juejin.im/post/5a30de4051882531d828680d ## 最后 **总结:** 1. 在实际写代码的途中发现xml中配置会比在代码里生效,所以如果不是频繁修改Toolbar内容的话,建议还是在xml里配置齐全. 2. 关于Toolbar内自定义设置view跟目前已知的几个子view区别不大,所以不再进行赘述. **以上内容如果有不对的地方,请及时告知我,谢谢.如果能点个赞就更是感激不尽啦.** Last modification:July 27th, 2020 at 05:07 pm © 允许规范转载 Support If you think my article is useful to you, please feel free to appreciate ×Close Appreciate the author Sweeping payments