Android_Toolbar源码解析使用和问题解决

前言

为什么使用Toolbar

  • MD设计,优化整体视觉体验.

使用注意事项

  • V7包的兼容,theme的设置.
  • 版本API的兼容适配.

源码查看方式:

  1. 在线: Toolbar.java地址: (版本Android 8.1)

  2. 使用Git克隆到本地:

    git clone https://android.googlesource.com/platform/frameworks/support

官网API说明

说明: Toolbar在实际导入的时候会发现是有两个包的,一般使用v7包,因为要兼容低版本的缘故
官网的v7包下 Toolbar API说明地址 (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的流程大致如下:

Toolbar

效果图实现&&分析

项目环境: Android Studio 3.0.1 + JDK 1.8

测试手机: ALLVIEW X5_Soul_Pro Android 8.1.0,API 27

语言: Kotlin

文件配置:

build.gradle 文件

 dependencies {
     classpath 'com.android.tools.build:gradle:3.0.1'
     classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
 }

app下的build.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

效果图

Show

正确使用Toolbar的三步骤:

  1. 去除原ActionBar的显示.
  2. Xml布局里设置Toolbar的相关设置.
  3. 代码设置需要使用的Toolbar的属性和点击监听(PS: 一些设置生效是在版本21及其以上生效的).
以下是代码部分:

MainActivity.kt

 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 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 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 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 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

参考:

 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失效

源码如下:

 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区别不大,所以不再进行赘述.

以上内容如果有不对的地方,请及时告知我,谢谢.如果能点个赞就更是感激不尽啦.

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值