Material Design 控件之 Toolbar 非完全解析

一、Toolbar 出现的背景

Toolbar 是 Android5.0 中新引入的一个控件,其出现的目的就是为了取代 ActionBar。
Actionbar 在 Android3.0 推出的目的就是为了在 UI 界面中引入一个全局导航的功能,取代 Android3.0 之前的标题栏。并且在刚推出不久就发布了兼容到 API 7 的兼容包,但是很多应用都没有使用 Actionbar。
原因是 Actionbar 的界定很模糊,因为 Android 系统中把界面分成两大部分,一部分是 System UI,主要由 Statusbar 和 Android4.0 以后出现的由虚拟按键构成的导航栏为主,对于系统 UI 来说,Android 不允许应用开发者对其进行完全控制,在 Android5.0 之后甚至不让开发者控制状态栏;另一部分就是应用 UI,对于这部分 UI,开发者拥有完全控制权。
而 Actionbar 在显示上应该算是应用 UI 的一部分,但是开发者又不能对其进行完全控制,因为它毕竟是由系统创建并对其进行相关参数的初始化。所以在实际开发中,很多开发者都是用布局生成一个模拟的 Actionbar 来代替系统的 Actionbar,基于这一点,Android 在5.0后推出一个新的控件 Toolbar 来取代 ActionBar。

二、Toolbar 的使用

Material Design 控件之 Toolbar 非完全解析_第1张图片
Toolbar

1)导包

Toolbar 位于 android.widget 包下,在 Android5.0 + 版本可以直接使用,如果需要兼容 Android5.0 以下的版本,那么需要使用位于支持包 android.support.v7.widget 中的 Toobar,可以支持到 API7。
直接在 Module 的 build.gradle 文件中添加依赖

dependencies {
    compile 'com.android.support:appcompat-v7:25.3.1'
}

2)设置 Theme

要想使用 Toolbar 代替 Actionbar,必须使用 Theme.AppCompat 主题中没有 Actionbar 的主题,把 Actionbar 隐藏掉,否则会造成冲突,NoActionbar 的主题对两个 Window 属性进行了重写:


经过实际测试,发现其实只需要设置 true 即可

3)xml 文件中添加

不同于 Actionbar ,系统会自动根据设置的 Theme 创建不同样式的 Actionbar 添加到界面中,Toolbar 需要像普通控件一样在布局文件中添加:


    

但是运行之后会发现界面中 Actionbar 位置只有一个 Toobar 的android:background 属性所指定颜色的背景,其它的什么都没有,其实这个时候 Toolbar 已经出现了,只是因为我们除了 background 外没有给 Toolbar 设置任何属性,ActionBar 的属性是系统自动初始化的,Toolbar 需要我们进行设置。
给 Toolbar 设置属性有三种方式,可以结合使用:

1、调用 setSupportActionbar/setActionbar 方法和 ·getSupportActionbar/getActionbar 把 Toolbar 作为 Actionbar 处理
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
    }
}

通过 findViewById 方法获取到 xml 文件中添加的 Toolbar 控件,然后调用 setSupportActionbar 方法,如果只支持 5.0 以上系统调用 setActionbar 方法,这个方法是把 Toolbar 当作 Actionbar 来处理,把系统给 Actionbar 设置的相关初始参数设置给 Toolbar,之前关于 Actionbar 的大部分操作都会应用在 Toolbar 上。
setSupportActionbar 是 AppCompatActivity 中的方法,所以使用这种方式的 Activity 必须继承 AppCompatActivity。
这里的参数是 Toolbar 而并非 Actionbar,简单看一下源码:

AppCompatActivity#setSupportActionbar
public void setSupportActionBar(@Nullable Toolbar toolbar) {
    getDelegate().setSupportActionBar(toolbar);
}

只是简单地调用了 AppCompatDelegate 的 setSupportActionbar 方法:

AppCompatDelegateImplV7#setSupportActionbar

AppCompatDelagate 类是个抽象类,它的实现类 AppCompatDelegateImplV7 实现了 setSupportActionbar 方法:

@Override
public void setSupportActionBar(Toolbar toolbar) {
    if (!(mOriginalWindowCallback instanceof Activity)) {
        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_ACTION_BAR and set " +
                "windowActionBar to false in your theme to use a Toolbar instead.");
    }

    ToolbarActionBar tbab = new ToolbarActionBar(toolbar, ((Activity) mContext).getTitle(),
            mAppCompatWindowCallback);
    setSupportActionBar(tbab);
    mWindow.setCallback(tbab.getWrappedWindowCallback());
    tbab.invalidateOptionsMenu();
}

这段方法的逻辑非常简单,首先会判断当前的窗口回调是否是一个
Activity 对象,因为只有 Activity 才能够支持创建 ActionBar,不是的话就返回,然后就会尝试去获取当前的 ActionBar,如果发现当前 Activity 中已经有了 ActionBar 就会抛出一个异常,这就是我们要设置 Toolbar 来替代 ActionBar 那么就必须去掉原有的
ActionBar 的原因,当然如果只是将 Toolbar 作为一个普通的控件,就不是必须的了。
然后将传入的 Toolbar 对象作为入参构造了一个 ToolbarActionBar 对象,随后马上调用了 setSupportActionBar 方法并将这个
ToolbarActionBar 对象传入,来看 ToolbarActionBar 类:

ToolbarActionBar
public class ToolbarActionBar extends ActionBar {
    // ......省略一些代码......

    public ToolbarActionBar(Toolbar toolbar, CharSequence title, Window.Callback callback) {
        mDecorToolbar = new ToolbarWidgetWrapper(toolbar, false);
        mWindowCallback = new ToolbarCallbackWrapper(callback);
        mDecorToolbar.setWindowCallback(mWindowCallback);
        toolbar.setOnMenuItemClickListener(mMenuClicker);
        mDecorToolbar.setWindowTitle(title);
    }

    // ......省略大量代码......
}

在 ToolbarActionBar 的构造方法中,又将 toolbar 作为入参构造了一个 ToolbarWidgetWrapper 对象,从类名里就可以看出,这个
ToolbarWidgetWrapper 是个包装类,它持有 Toolbar 的引用,其实用心观察 ToolbarActionBar 这个类你会发现他是一个代理类,其中大部分方法都是间接由 mDecorToolbar 这个对象调用,mDecorToolbar 对象的实际类型就是刚才我们所说的
ToolbarWidgetWrapper,而引用类型则是 DecorToolbar,ToolbarActionBar 这个类以委托代理的方式将自身的功能交由
mDecorToolbar 实现:

public class ToolbarActionBar extends ActionBar {
    // ......省略一些代码......

    @Override
    public void setIcon(int resId) {
        mDecorToolbar.setIcon(resId);
    }

    @Override
    public void setIcon(Drawable icon) {
        mDecorToolbar.setIcon(icon);
    }

    @Override
    public void setLogo(int resId) {
        mDecorToolbar.setLogo(resId);
    }

    @Override
    public void setLogo(Drawable logo) {
        mDecorToolbar.setLogo(logo);
    }

    // ......省略一些代码......
}

mDecorToolbar 的方法中对持有的 Toolbar 成员变量进行相关设置。
ToolbarActionBar 里面有一些方法是空的,那么 Actionbar 的相关属性就没有被关联到 Toolbar 中,其中就包括 android:background'属性,如果我们不给 Toolbar 设置该属性的话, Toolbar 就会默认显示 Window 窗口的背景色,即 Activity 所使用的主题中 android:windowBackground 属性指定的颜色。

也可以调用 getSupportActionbar 方法获取被当做 Actionbar 处理的 Toolbar 对象,并调用 Actionbar 的相关方法对 Toolbar 进行操作,这个时候 Toolbar 就是一个 Actionbar。

2、调用 Toolbar 的相关方法

Toolbar 自身也对外提供了一系列的方法对 Toolbar 进行相关设置,这个时候不需要调用 setSupportActionbar 方法,Activity 也就不必非要继承 AppCompatActivity,而且即使不隐藏 Actionbar,也不会造成冲突报错,但是 Actionbar 会占用 Toolbar 的位置,将 Toolbar 挤到下面去,所以作为导航栏使用的话,一般还是要隐藏掉 Actionbar。

//设置导航图标
setNavigationIcon(@DrawableRes int resId)
setNavigationIcon(@Nullable Drawable icon)
setNavigationOnClickListener(OnClickListener listener)
//设置 Logo
setLogo(@DrawableRes int resId)
setLogo(Drawable drawable)
//设置标题
setTitle(@StringRes int resId)
setTitle(CharSequence title)
//设置标题文本颜色
setTitleTextColor(@ColorInt int color)
//设置标题外观,包括字体颜色、大小、样式等
setTitleTextAppearance(Context context, @StyleRes int resId)
//设置标题边距像素
setTitleMargin(int start, int top, int end, int bottom)
setTitleMarginStart(int margin)
setTitleMarginTop(int margin)
setTitleMarginEnd(int margin)
setTitleMarginBottom(int margin)
//设置副标题
setSubtitle(@StringRes int resId)
setSubtitle(CharSequence subtitle)
//设置副标题文本颜色
setSubtitleTextColor(@ColorInt int color)
//设置副标题外观,包括字体颜色、大小、样式等
setSubtitleTextAppearance(Context context, @StyleRes int resId)
//加载菜单
inflateMenu(@MenuRes int resId)
//监听菜单点击
setOnMenuItemClickListener(OnMenuItemClickListener listener)
//设置弹出菜单主题
setPopupTheme(@StyleRes int resId)
//设置溢出菜单图标
setOverflowIcon(@Nullable Drawable icon)
3、在 xml 文件中设置属性

就像普通控件一样,需要注意的是一些属性的命名空间并不是 'android',需要在根布局声明自定义的命名空间,比如这里使用 app

xmlns:app="http://schemas.android.com/apk/res-auto"

这里的属性是很多的:

        //应用图标、导航图标、收缩图标
        app:logo=""
        app:logoDescription=""
        app:navigationIcon=""
        app:navigationContentDescription=""
        app:collapseIcon=""
        app:collapseContentDescription=""
        //样式
        app:theme=""
        //弹窗样式
        app:popupTheme=""
        //标题样式
        app:title=""
        app:titleTextAppearance=""
        app:titleTextColor=""
        app:titleMargin=""
        app:titleMargins=""
        app:titleMarginStart=""
        app:titleMarginTop=""
        app:titleMarginBottom=""
        app:titleMarginEnd=""
        //副标题样式
        app:subtitle=""
        app:subtitleTextAppearance=""
        app:subtitleTextColor=""
        //按钮
        app:buttonGravity=""
        app:maxButtonHeight=""
        //一些边距
         app:contentInsetEnd=""
        app:contentInsetLeft=""
        app:contentInsetRight=""
        app:contentInsetStart=""
        app:contentInsetEndWithActions=""
        app:contentInsetStartWithNavigation=""
        app:paddingEnd=""
        app:paddingStart=""
app:popupTheme 和 app:theme

app:popupTheme 用来设置溢出菜单的样式,通常继承一个 ThemeOverlay.AppCompat 主题,当然并不是必须的,然后重写相关属性:


  • android:colorBackground 属性设置的是溢出菜单的背景颜色;
  • android:textColor 属性设置的是溢出菜单的字体颜色;
  • android:textSize 属性设置的是溢出菜单的字体大小;

而如果想要修改 Toolbar 上的菜单字体颜色和大小则需要使用 android:popupTheme 属性来设置主题,同样继承一个系统主题,然后重写相关属性:


  • android:textSize 设置菜单字体大小,如果没有在 android:popupTheme 的主题中修改字体大小,这个属性同样会作用于溢出菜单;
  • actionMenuTextColor 设置菜单的颜色,注意这个是不带命名空间的;

如果在 android:theme 的主题中重写了 android:colorBackgroundandroid:textColor 属性同样可以修改溢出菜单的样式。

自定义View

Toolbar 继承自 ViewGroup,我们可以直接在 Toolbar 中添加其它控件,添加的控件会显示在 Title、SubTitle 和 ActionMenu 之间,如果设置自定义 View 的 android:layout_width="match_parent 会覆盖掉 Title 和 SubTitle。

参考: Toolbar:上位的小三
Material Design 之 Toolbar 开发实践总结

你可能感兴趣的:(Material Design 控件之 Toolbar 非完全解析)