学习Android之Material Design


1 什么是Material Design?

  它在2014年Google I/O大会上重磅推出的一套全新的界面设计语言。Material Design是由Google的设计工程师们基于传统优秀的设计原则,结合丰富的创意和科学技术所开发的一套全新的界面设计语言,包含了视觉、运动、互动效果等特性。

  在2015年的Google I/O大会上推出了一个Design Support库,这个库将MaterialDesign中最具代表性的一些控件和效果进行了封装,使得开发者即使在不了解Material Design的情况下,也能非常轻松地将自己的应用Material化。后来Design Support库又改名成了Material库,用于给Google全平台类的产品提供MaterialDesign的支持。

  下面我们开始学习Material库。

1.1 Toolbar

  由AndroidX库提供的,我们知道每个Activity最顶部的那个标题栏其实就是ActionBar。不过ActionBar由于其设计的原因,被限定只能位于Activity的顶部,从而不能实现一些Material Design的效果,因此官方现在已经不再建议使用ActionBar了。更加推荐使用Toolbar。

  现在我们准备使用Toolbar来替代ActionBar,因此需要指定一个不带ActionBar的主题,res/values/styles.xml文件中修改parent即可。

    

  我们用一张图来了解一下以上重写的属性:

  不过colorAccent这个属性比较难理解,它不只是用来指定这样一个按钮的颜色,而是更多表达了一种强调的意思,比如一些控件的选中状态也会使用colorAccent的颜色。

  现在使用Toolbar来替代ActionBar。修改activity_main.xml中的代码,如下所示:


    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.appcompat.widget.Toolbar
        android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="@color/purple_200"
        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
        app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />

  第二行中,使用xmlns:app制定了一个新的命名空间。正是由于每个布局文件都会使用xmlns:android来指定一个命名空间,我们才能一直使用android:id、android: layout_width等写法。这里指定了xmlns:app,也就是说现在可以使用app:attribute这样的写法。但是为什么这里要指定一个xmlns:app的命名空间呢?这是由于许多Material属性是在新系统中新增的,老系统中并不存在,那么为了能够兼容老系统,我们就不能使用android:attribute这样的写法了,而是应该使用app:attribute。

  接下来定义了一个Toolbar控件,这个控件是由appcompat库提供的。这里高度设置为actionBar的高度。不过下面的部分就稍微有点难理解了,由于我们刚才在styles.xml中将程序的主题指定成了浅色主题,因此Toolbar现在也是浅色主题,那么Toolbar上面的各种元素就会自动使用深色系,从而和主体颜色区别开。但是之前使用ActionBar时文字都是白色的,现在变成黑色的会很难看。那么为了能让Toolbar单独使用深色主题,这里我们使用了android:theme属性,将Toolbar的主题指定成了ThemeOverlay.AppCompat.Dark.ActionBar。但是这样指定之后又会出现新的问题,如果Toolbar中有菜单按钮,那么弹出的菜单项也会变成深色主题,这样就再次变得十分难看了,于是这里又使用了app:popupTheme属性,单独将弹出的菜单项指定成了浅色主题。

   写完了布局,接下来修改MainActivity,如下:

        val toolbar = findViewById(R.id.toolbar)
        setSupportActionBar(toolbar)

  包不要导入错了,导入的是androidx库中的:

import androidx.appcompat.widget.Toolbar

  接下来是一些Toolbar常用的功能:

修改标题栏上显示的文字内容

  是在AndroidManifest.xml中指定的:

        <activity
            android:name=".MainActivity"
            android:label="MyToolbar"
            android:exported="true">
            ...
        

  给activity增加了一个android:label属性,用于指定在Toolbar中显示的文字内容,如果没有指定的话,会默认使用application中指定的label内容.

添加按钮

  在res创建一个menu文件夹。然后右击menu文件夹→New→Menu resource file,创建一个toolbar.xml文件,并编写如下代码:


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

    <item
        android:id="@+id/backup"
        android:icon="@drawable/ic_backup"
        android:title="Backup"
        app:showAsAction="always" />
    <item
        android:id="@+id/delete"
        android:icon="@drawable/ic_delete"
        android:title="Delete"
        app:showAsAction="ifRoom" />
    <item
        android:id="@+id/settings"
        android:icon="@drawable/ic_settings"
        android:title="Settings"
        app:showAsAction="never" />

  icon是指定按钮图标的,这里我随便用了。title指定按钮的文字。app:showAsAction来指定按钮的显示位置,这里之所以再次使用了app命名空间,同样是为了能够兼容低版本的系统。】

  showAsAction主要有以下几种值可选:always表示永远显示在Toolbar中,如果屏幕空间不够则不显示;ifRoom表示屏幕空间足够的情况下显示在Toolbar中,不够的话就显示在菜单当中;never则表示永远显示在菜单当中。注意,Toolbar中的action按钮只会显示图标,菜单中的action按钮只会显示文字。

  然后在MainActivity中重写相应的菜单方法,如下所示:

    override fun onCreateOptionsMenu(menu: Menu?): Boolean {
        menuInflater.inflate(R.menu.toolbar, menu)
        return true
    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        when (item.itemId) {
            R.id.backup -> Toast.makeText(this,"you clicked backup", Toast.LENGTH_SHORT).show()
            R.id.backup -> Toast.makeText(this,"you clicked delete", Toast.LENGTH_SHORT).show()
            R.id.backup -> Toast.makeText(this,"you clicked settings", Toast.LENGTH_SHORT).show()
        }
        return true
    }

 

1.2 滑动菜单

   滑动菜单可以说是Material Design中最常见的效果之一了。所谓的滑动菜单,就是将一些菜单选项隐藏起来,而不是放置在主屏幕上,然后可以通过滑动的方式将菜单显示出来。这种方式既节省了屏幕空间,又实现了非常好的动画效果,是Material Design中推荐的做法。

DrawerLayout

  Google在AndroidX库中提供了一个DrawerLayout控件,借助这个控件,实现滑动菜单简单又方便。

  首先DrawerLayout是一个布局,在布局中允许放入两个直接子控件:第一个子控件是主屏幕中显示的内容,第二个子控件是滑动菜单中显示的内容。

  对activity_main.xml中的代码做如下修改:


    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/drawerLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <androidx.appcompat.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="@color/purple_200"
            android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
            app:popupTheme="@style/ThemeOverlay.AppCompat.Light" />
    

    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        android:background="#FFF"
        android:text="This is menu"
        android:textSize="30sp"/>
    

  这里最外层的控件使用了DrawerLayout。DrawerLayout中放置了两个直接子控件:第一个子控件是FrameLayout,用于作为主屏幕中显示的内容,第二个子控件是一个TextView,用于作为滑动菜单中显示的内容。这里用什么都可以,DrawerLayout并没有限制只能使用固定的控件。

  第二个子控件有一点需要注意,layout_gravity这个属性是必须指定的,因为我们需要告诉DrawerLayout滑动菜单是在屏幕的左边还是右边,指定left表示滑动菜单在左边,指定right表示滑动菜单在右边。这里指定了start,表示会根据系统语言进行判断,如果系统语言是从左往右的,比如英语、汉语,滑动菜单就在左边,如果系统语言是从右往左的,比如阿拉伯语,滑动菜单就在右边。

  现在在屏幕的左侧边缘向右拖动,就可以让滑动菜单显示出来了。为了能让用户知道这个操作,推荐做法是在Toolbar的最左边加入一个导航按钮将滑动菜单展示出来。

  下面来实现这个功能,准备一张导航按钮的图标ic_menu.png:

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val toolbar = findViewById(R.id.toolbar)
        drawerLayout = findViewById(R.id.drawerLayout)
        setSupportActionBar(toolbar)
        supportActionBar?.let {
            it.setDisplayHomeAsUpEnabled(true)
            it.setHomeAsUpIndicator(R.drawable.ic_menu)
        }
    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        when (item.itemId) {
            android.R.id.home -> drawerLayout.openDrawer(GravityCompat.START)
          ...
        }
        return true
    }    

  这里首先调用了getSupportActionBar()方法得到了ActionBar的实例,接着在ActionBar不为空的情况下调用setDisplayHomeAsUpEnabled()方法让导航按钮显示出来,调用setHomeAsUpIndicator()方法来设置一个导航按钮图标。然而,ToolBar最左侧的按钮就叫做Home按钮,它默认的图标是一个返回箭头,意思是返回上一个Activity,不过这里我们修改了它的样式和作用。

  接着,在onOptionsItemSelected()方法中对Home按钮处理点击事件,Home按钮的id永远都是android.R.id.home。然后调用DrawerLayout的openDrawer()方法将滑动菜单展示出来,这里需要传入一个Gravity参数。

1.3 NavigationView

  虽然有滑动菜单了,但是里面还很单调,现在准备在滑动菜单页面定制任意布局。Google给我们提供了一种更好的方法——NavigationView,NavigationView是Material库中提供的一个控件,可以将滑动菜单页面的实现变得非常简单。

添加依赖

  既然这个控件是Material库中提供的,那么我们就需要添加依赖:

    implementation 'com.google.android.material:material:1.5.0'
    implementation 'de.hdodenhof:circleimageview:3.0.1'

  第一行是Material库,第二行是一个开源项目CircleImageView,可以轻松实现图片圆形化的功能。

  对了,还需要去style.xml文件中修改AppTheme的parent主题:

    
    
    

  这里专门给FruitActivity定义一个主题。将android:statusBarColor属性的值指定成@android:color/transparent就可以了。

  最后,还需要让FruitActivity使用这个主题,在AndroidManifest.xml中设置:

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.MaterialTest">
        <activity
            android:name=".FruitActivity"
            android:theme="@style/FruitActivityTheme"
            android:exported="false" />
        ...
    

  终于完成了,现在的视觉体验又上升了一个档次。