美文网首页一起来学习Material Design
12 (上) 最佳的UI体验---Material Design

12 (上) 最佳的UI体验---Material Design

作者: 努力生活的西鱼 | 来源:发表于2017-02-22 22:36 被阅读463次

    本章的主要的知识点:

    • Toolbar
    • 滑动菜单
      1. DrawerLayout
      2. NavigationView
    • 悬浮按钮和可交互提示
      1. FloatingActionButton
      2. Snackbar
      3. CoordinatorLayout
    • 卡片式布局
      1. CardView
      2. AppBarLayout
    • 下拉刷新
    • 可折叠式标题栏
      1. CollapsingToolbarLayout
      2. 充分利用系统状态栏空间
    杀生丸.png

    12.1 什么是Material Design

    Material Design 是由谷歌的设计工程师们基于传统优秀的设计原则,结合丰富的创意和科学技术所发明的一套全新的界面设计语言,包含了视觉,运动,互动效果等特性。

    谷歌从Android5.0系统开始,就将所有内置的应用都使用Material Design风格来进行设计。

    不过,在重磅推出之后,Material Design的普及程度却不能说是特别理想。因为这只是一个推荐的设计规范,主要是面向UI设计人员的,而不是面向开发者的。很多开发者可能就搞不清楚什么样的界面和效果才叫Material Design,就算搞清楚了,实现起来也会很费劲,因为不少Material Design 的效果是很难实现的。

    在2015年的Google I/O大会上推出了一个Design Support库,这个库将Material Design中最具代表性的一些控件和效果进行了封装,使得开发者在即使不了解Material Design的情况下也能非常轻松地将自己的应用Material化。

    12.2 Toolbar

    不过ActionBar由于其设计的原因,被限定只能位于活动的顶部,从而不能实现一些Material Design的效果,因此官方现在已经不再建议使用ActionBar了。

    Toolar的强大之处在于,它不仅继承了ActionBar的所有功能,而且灵活性很高,可以配合其它控件来完成一些Material Design的效果。

    任何一个新建的项目,默认都会是显示ActionBar的,那么这个ActionBar到底是从哪里来的呢?其实这是根据项目中指定的主题来显示的,打开AndroidManifest.xml文件看一下

     <application
            android:allowBackup="true"
            android:icon="@mipmap/ic_launcher"
            android:label="@string/app_name"
            android:supportsRtl="true"
            android:theme="@style/AppTheme">
    

    可以看到,这里使用android:theme属性指定了一个AppTheme的主题。打开res/values/styles.xml文件。

    <resources>
    
        <!-- Base application theme. -->
        <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
            <!-- Customize your theme here. -->
            <item name="colorPrimary">@color/colorPrimary</item>
            <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
            <item name="colorAccent">@color/colorAccent</item>
        </style>
    
    </resources>
    
    

    这里定义了一个叫AppTheme的主题,然后指定它的parent主题是Theme.AppCompat.Light.DarkActionBar,这个DarkActionBar是一个深色的ActionBar主题,我们之前所有的项目中自带的ActionBar就是因为指定了这个主题才出现的。

    而现在我们准备使用Toolbar替代ActionBar,因此需要指定一个不带ActionBar的主题,通常有Theme.AppCompat.NoActionBar和Theme.AppCompat.Light.NoActionBar这两种主题可选。其中Theme.AppCompat.NoActionBar表示深色主题,它会将界面的主体颜色设成深色,配衬颜色设成单色。而Theme.AppCompat.Light.NoActionBar表示淡色主题,它会将界面的主体颜色设成淡色,配衬颜色设成深色。

    <resources>
    
        <!-- Base application theme. -->
        <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
            <!-- Customize your theme here. -->
            <item name="colorPrimary">@color/colorPrimary</item>
            <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
            <item name="colorAccent">@color/colorAccent</item>
        </style>
    
    </resources>
    

    然后观察一下AppTheme中的属性重写,这里重写了colorPrimary,colorPrimaryDark和colorAccent这3个属性的颜色。


    各属性指定的颜色位置

    除了上述3个属性之外,我们还可以通过textColorPrimary,windowBackground和navigationBarColor等属性来控制更多位置的颜色。不过唯独colorAccent这个属性比较难理解,它不只是用来指定这样一个按钮的颜色,而是更多表达了一个强调的意思,比如一些控件的选中状态也会使用colorAccent的颜色。

    <FrameLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:id="@+id/activity_main"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    
        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
            app:popupTheme="@style/ThemeOverlay.AppCompat.Light">
        </android.support.v7.widget.Toolbar>
    

    首先看一下第2行,这里使用xmlns:app指定了一个新的命名空间。思考一下,正是由于每个布局文件都会使用xmlns:android来指定一个命名空间,因此我们才能一直使用android:id,android:layout_width等写法。那么这里指定了xmlns:app,也就是说现在可以使用app:attribute这样的写法了。由于Material Design是在Android5.0系统中才出现的,而很多的Material属性在5.0之前的系统中并不存在,那么为了能够兼容之前的老系统我么就不能使用android:attribute这样的写法了,而是应该使用app:attribute。

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

    public class MainActivity extends AppCompatActivity
    {
    
        @Override
        protected void onCreate(Bundle savedInstanceState)
        {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
            setSupportActionBar(toolbar);
        }
    }
    

    这里关键的代码只有两句,首先通过findViewById()得到Toolbar的实例,然后调用setSupportActionBar()方法将Toolbar的实例传入,这样我们就做到即使用了Toolbar,又让它的外观与功能和ActionBar一致了。

    接下来我们再学习一些Toolbar比较常用的功能,比如修改标题栏上显示的文字内容。这段文字内容是在AndroidManifest.xml中指定的。

     <application
            android:allowBackup="true"
            android:icon="@mipmap/ic_launcher"
            android:label="@string/app_name"
            android:supportsRtl="true"
            android:theme="@style/AppTheme">
            <activity android:name=".MainActivity"
                android:label="小吴">
    

    这里给activity增加了一个android:label属性,用于指定在Toolbar中显示的文字内容,如果没有指定的话,会默认使用android:label="@string/app_name"指定的内容,也就是我们的应用名称。

    <?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/backup"
            android:icon="@drawable/backup"
            android:title="Backup"
            app:showAsAction="always"/>
        <item
            android:id="@+id/delete"
            android:icon="@drawable/delete"
            android:title="Delete"
            app:showAsAction="ifRoom"/>
        <item
            android:id="@+id/settings"
            android:icon="@drawable/setting"
            android:title="Settings"
            app:showAsAction="never"/>
        
    </menu>
    
    

    我们通过<item>标签来定义action按钮,android:id用于指定按钮的id,android:icon用于指定按钮的图标,android:title用于指定按钮的文字。

    接着使用app:showAsAction来指定按钮的显示位置,之所以这里再次使用了app命名空间,同样是为了能够兼容低版本的系统。showAsAction主要有以下几种值可选:always表示永远显示在Toolbar中,如果屏幕空间不够则不显示。ifRoom表示屏幕空间足够的情况下显示在Toolbar中,不够的话就显示在菜单当中。never则表示永远显示在菜单当中。注意Toolbar中的action按钮只会显示图标,菜单中的action按钮只会显示文字。

    @Override
        public boolean onCreateOptionsMenu(Menu menu)
        {
            getMenuInflater().inflate(R.menu.toolbar,menu);
            return true;
        }
    
        @Override
        public boolean onOptionsItemSelected(MenuItem item)
        {
            switch (item.getItemId())
            {
                case R.id.backup:
                    Toast.makeText(this, "You clicked Backup", Toast.LENGTH_SHORT).show();
                    break;
                case R.id.delete:
                    Toast.makeText(this, "You clicked Delete", Toast.LENGTH_SHORT).show();
                    break;
                case R.id.settings:
                    Toast.makeText(this, "You clicked Settings", Toast.LENGTH_SHORT).show();
                    break;
                default:
                    break;
            }
            return true;
        }
    

    12.3 滑动菜单

    滑动菜单可以说是Material Design中最常见的效果之一了。

    12.3.1 DrawerLayout

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

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

    <android.support.v4.widget.DrawerLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:id="@+id/drawer_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    
        <FrameLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">
    
            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                android:background="?attr/colorPrimary"
                android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
                app:popupTheme="@style/ThemeOverlay.AppCompat.Light">
            </android.support.v7.widget.Toolbar>
        </FrameLayout>
    
        <TextView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:text="This is menu"
            android:textSize="30sp"
            android:background="#FFF"
            android:layout_gravity="start"/>
    </android.support.v4.widget.DrawerLayout>
    
    

    这里最外层的控件使用了DrawerLayout,这个控件是由support-v4库提供的。DrawerLayout中放置了两个直接子控件,第一个子控件是FrameLayout,用于作为主屏幕中显示的内容,当然里面还有我们刚刚定义的Toolbar。第二个子控件这里使用了一个TextView,用于作为滑动菜单中显示的内容,其实使用什么都可以,DrawerLayout并没有限制只能使用固定的控件。

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

    想做滑动菜单,或者点击一下菜单以外的区域,都可以让滑动菜单关闭,从而回到主界面。无论是暂时还是隐藏滑动菜单,都是有非常流畅的动画过渡的。

    Material Design建议的做法是在Toolbar的最左边加入一个导航按钮,点击了导航按钮也会将滑动菜单的内容展示出来。这样就相当于给用户提供了两种打开滑动菜单的方式。

    public class MainActivity extends AppCompatActivity
    {
    
        private DrawerLayout mDrawerLayout;
    
        @Override
        protected void onCreate(Bundle savedInstanceState)
        {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
            setSupportActionBar(toolbar);
    
            mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
            ActionBar actionBar = getSupportActionBar();
            if (actionBar != null)
            {
                actionBar.setDisplayHomeAsUpEnabled(true);
                actionBar.setHomeAsUpIndicator(R.drawable.menu);
            }
        }
    
        @Override
        public boolean onCreateOptionsMenu(Menu menu)
        {
            getMenuInflater().inflate(R.menu.toolbar,menu);
            return true;
        }
    
        @Override
        public boolean onOptionsItemSelected(MenuItem item)
        {
            switch (item.getItemId())
            {
                case android.R.id.home:
                    mDrawerLayout.openDrawer(GravityCompat.START);
                    break;
                case R.id.backup:
                    Toast.makeText(this, "You clicked Backup", Toast.LENGTH_SHORT).show();
                    break;
                case R.id.delete:
                    Toast.makeText(this, "You clicked Delete", Toast.LENGTH_SHORT).show();
                    break;
                case R.id.settings:
                    Toast.makeText(this, "You clicked Settings", Toast.LENGTH_SHORT).show();
                    break;
                default:
                    break;
            }
            return true;
        }
    }
    
    

    首先调用findViewById()方法得到了DrawerLayout的实例,然后调用getSupportActionBar()方法得到了ActionBar的实例,虽然这个ActionBar的具体实现是由Toolbar来完成的。接着调用ActionBar的setDisplayHomeAsUpEnabled()方法让导航按钮显示出来,又调用了setHomeAsUpIndicator()方法来设置一个导航按钮图标。实际上Toolbar最左侧的这个按钮就叫做HomeAsUp按钮,它默认的图标是一个返回的箭头,含义是返回上一个活动。很明显,这里我们将它默认的样式和作用都进行了修改。

    接下来在onOptionsItemSelected()方法中对HomeAsUp按钮的点击事件进行处理,HomeAsUp按钮的id永远都是android.R.id.home。然后调用DrawerLayout的openDrawer()方法将滑动菜单显示出来,注意openDrawer()方法要求传入一个Gravity参数,为了保证这里的行为和XML中定义的一致,我们传入了GravityCompat.START.

    12.3.2 NavigationView

    事实上,你可以在滑动菜单页面定制任意的布局,不过谷歌给我们提供了一种更好的方法---使用NavigationView。NavigationView是Design Support库中提供的一个控件,它不仅是严格按照Material Design的要求来进行设计的,而且还可以将滑动菜单页面的实现变得非常简单。

    既然这个控件是Design Support库中提供的,那么我们就需要将这个库引入到项目中才行。

    compile 'com.android.support:design:25.1.1'
        compile 'de.hdodenhof:circleimageview:2.1.0'
    

    这里添加了两行依赖关系,第一行就是Design Support库,第二行是一个开源项目CircleImageView,它可以用来轻松实现图片圆形化的功能。

    在开始使用NavigationView之前,我们还需要提前准备好两个东西:menu和headerLayout。menu是用来在NavigationView中显示具体的菜单项的,headerLayout则是用来在NavigationView中显示头部布局的。

    <?xml version="1.0" encoding="utf-8"?>
    <menu xmlns:android="http://schemas.android.com/apk/res/android">
    
        <group android:checkableBehavior="single">
            <item
                android:id="@+id/nav_call"
                android:icon="@drawable/call"
                android:title="Call"/>
            <item
                android:id="@+id/nav_friends"
                android:icon="@drawable/friends"
                android:title="Friends"/>
            <item
                android:id="@+id/nav_location"
                android:icon="@drawable/location"
                android:title="Location"/>
            <item
                android:id="@+id/nav_mail"
                android:icon="@drawable/mail"
                android:title="Mail"/>
            <item
                android:id="@+id/nav_task"
                android:icon="@drawable/tasks"
                android:title="Tasks"/>
        </group>
    
    </menu>
    

    我们首先在<menu>中嵌套了一个<group>标签,然后将group的checkableBehavior属性指定为single。group表示一个组,checkableBehavior指定为single表示组中的所有菜单项只能单选。

    这里一共定义了5个item,分别使用android:id属性指定菜单项的id,android:icon属性指定菜单项的图标,android:title属性指定菜单项显示的文字。

    接下来准备headerLayout了,这是一个可以随意定制的布局。

    <RelativeLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="180dp"
        android:padding="10dp"
        android:background="?attr/colorPrimary">
    
        <de.hdodenhof.circleimageview.CircleImageView
            android:id="@+id/icon_image"
            android:layout_width="70dp"
            android:layout_height="70dp"
            android:src="@drawable/jinmu"
            android:layout_centerInParent="true"/>
    
        <TextView
            android:id="@+id/username"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:text="wumeng1993@outlook.com"
            android:textColor="#FFF"
            android:textSize="14sp"/>
    
        <TextView
            android:id="@+id/mail"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_above="@id/username"
            android:text="WuMeng"
            android:textColor="#FFF"
            android:textSize="14sp"/>
    
    </RelativeLayout>
    

    布局文件的最外层是一个RelativeLayout,我们将它的宽度设为match_parent,高度设为180dp,这是NavigationView比较适合的高度,然后指定它的背景色为colorPrimary.

    <android.support.v4.widget.DrawerLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:id="@+id/drawer_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    
        <FrameLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">
    
            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                android:background="?attr/colorPrimary"
                android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
                app:popupTheme="@style/ThemeOverlay.AppCompat.Light">
            </android.support.v7.widget.Toolbar>
        </FrameLayout>
    
        <android.support.design.widget.NavigationView
            android:id="@+id/nav_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_gravity="start"
            app:menu="@menu/nav_menu"
            app:headerLayout="@layout/nav_header">
    
        </android.support.design.widget.NavigationView>
    
    </android.support.v4.widget.DrawerLayout>
    
    
    

    我们将之前的TextView换成了NavigationView,这样滑动菜单中显示的内容也就变成了NavigationView了,这里又通过app:menu和app:headerLayout属性将我们刚才准备好的menu和headerLayout设置了进去,这样NavigationView就定义好了。

    protected void onCreate(Bundle savedInstanceState)
        {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            ``
            NavigationView navView = (NavigationView) findViewById(R.id.nav_view);
    
            ``
    
            navView.setCheckedItem(R.id.nav_call);
            navView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener()
            {
                @Override
                public boolean onNavigationItemSelected(@NonNull MenuItem item)
                {
                    switch (item.getItemId())
                    {
                        case R.id.nav_call:
                            Toast.makeText(MainActivity.this, "Call", Toast.LENGTH_SHORT).show();
                            mDrawerLayout.closeDrawers();
                            break;
                        case R.id.nav_friends:
                            Toast.makeText(MainActivity.this, "Friends", Toast.LENGTH_SHORT).show();
                            mDrawerLayout.closeDrawers();
                            break;
                        case R.id.nav_location:
                            Toast.makeText(MainActivity.this, "Location", Toast.LENGTH_SHORT).show();
                            mDrawerLayout.closeDrawers();
                            break;
                        case R.id.nav_mail:
                            Toast.makeText(MainActivity.this, "Mail", Toast.LENGTH_SHORT).show();
                            mDrawerLayout.closeDrawers();
                            break;
                        case R.id.nav_task:
                            Toast.makeText(MainActivity.this, "Task", Toast.LENGTH_SHORT).show();
                            mDrawerLayout.closeDrawers();
                            break;
                    }
                    return true;
                }
            });
        }
    
        ``
    }
    
    

    这里首先获取到了NavigationView的实例,然后调用它的setCheckedItem()方法将Call菜单设置为默认选中,接着调用了setNavigationItemSelectedListener()方法来设置一个菜单项选中事件的监听器,当用户点击了任意的菜单项时。就会回调到onNavigationItemSelected()方法中。我们可以在这个方法中写相应的逻辑处理。

    12.4 悬浮按钮和可交互提示

    立面设计是Material Design中一条非常重要的设计思想,也就是说,按照Material Design的理念,应用程序的界面不仅仅是一个平面,而应该是有立体效果的。在官方给出的示例中,最简单且最具代表性的立面设计就是悬浮按钮了,这种按钮不属于主界面平面的一部分,而是位于另外一个维度的,因此就会给人一种悬浮的感觉。

    FloatingActionButton

    FloatingActionButton是Design Support库中提供的一个控件,这个控件可以帮助我们比较轻松里实现悬浮按钮的效果。它默认会使用colorAccent来作为按钮的颜色,我们还可以通过给按钮指定一个图标来表明这个按钮的作用是什么。

    <android.support.v4.widget.DrawerLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:id="@+id/drawer_layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    
        <FrameLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">
    
            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                android:background="?attr/colorPrimary"
                android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
                app:popupTheme="@style/ThemeOverlay.AppCompat.Light">
            </android.support.v7.widget.Toolbar>
    
            <android.support.design.widget.FloatingActionButton
                android:id="@+id/fab"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="bottom|end"
                android:layout_margin="16dp"
                android:src="@drawable/done"/>
        </FrameLayout>
    
        ``
    
    </android.support.v4.widget.DrawerLayout>
    
    
    

    这里我们在主屏幕布局中加入了一个FloatingActionButton。layout_gravity属性指定将这个控件放置于屏幕的右下角,其中end的工作原理和之前的start是一样的,即如果系统语言是从左往右的,那么end就表示在右边,如果系统语言是从右往左的,那么end就表示在左边。

    如果你仔细观察的话,会发现这个悬浮按钮的下面还有一点阴影。其实这很好理解,因为FloatingActionButton是悬浮在当前界面上的,既然是悬浮,那么就理所应当会有投影,Design Support库连这种细节都帮我们考虑到了。

    说道悬浮,其实我们还可以指定FloatingActionButton的悬浮高度。

    <android.support.design.widget.FloatingActionButton
                android:id="@+id/fab"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="bottom|end"
                android:layout_margin="16dp"
                android:src="@drawable/done"
                app:elevation="8dp"/>
    

    这里使用app:elevation属性来给app:elevation指定一个高度值,高度值越大,投影范围也越大,但是投影效果越淡,高度值越小,投影范围也越小,但是投影效果越浓。当然这些效果的差异其实都不怎么明显,我个人感觉使用默认的FloatingActionButton效果就已经足够了。

    FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
    
            fab.setOnClickListener(new View.OnClickListener()
            {
                @Override
                public void onClick(View v)
                {
                    Toast.makeText(MainActivity.this, "fab clicked", Toast.LENGTH_SHORT).show();
                }
            });
    

    FloatingActionButton的点击事件就没什么好说的了。

    12.4.2 Snackbar

    Snackbar 并不是Toast的替代品,它们两者之间有着不同的应用场景。Toast的作用是告诉用户现在发生了什么事情,但同时用户只能被动接受这个事情,因为没有什么办法能让用户进行选择。而Snackbar则在这方面进行了扩展,它允许在提示当中加入一个可交互按钮,当用户点击按钮的时候可以执行一些额外的逻辑操作。

    fab.setOnClickListener(new View.OnClickListener()
            {
                @Override
                public void onClick(View v)
                {
                    Snackbar.make(v,"Data delete",Snackbar.LENGTH_SHORT)
                            .setAction("Undo", new View.OnClickListener()
                            {
                                @Override
                                public void onClick(View v)
                                {
                                    Toast.makeText(MainActivity.this, "Data restored", Toast.LENGTH_SHORT).show();
                                }
                            })
                            .show();
                }
            });
    

    这里调用了Snackbar的make()方法来创建一个Snackbar对象,make()方法的第一个参数需要传入一个View,只要是当前界面布局的任意一个View都可以,Snackbar会使用这个View来自动查找最外层的布局,用于展示Snackbar。第二个参数就是Snackbar中显示的内容,第三个参数是Snackbar显示的时长。

    接着这里用调用了一个setAction()方法来设置一个动作,从而让Snackbar不仅仅是一个提示,而是可以和用户进行交互的。

    12.4.3 CoordinatorLayout

    CoordinatorLayout可以说是一个加强版的FrameLayout,这个布局也是由Design Support库提供的。它在普通情况下的作用和FrameLayout基本一致。

    事实上,CoordinatorLayout可以监听其所有子控件的各种事件,然后自动帮助我们作出最为合理的解释。

    <android.support.design.widget.CoordinatorLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">
    
            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                android:background="?attr/colorPrimary"
                android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
                app:popupTheme="@style/ThemeOverlay.AppCompat.Light">
            </android.support.v7.widget.Toolbar>
    
            <android.support.design.widget.FloatingActionButton
                android:id="@+id/fab"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="bottom|end"
                android:layout_margin="16dp"
                android:src="@drawable/done"
                app:elevation="8dp"/>
        </android.support.design.widget.CoordinatorLayout>
    
    

    可以看到,悬浮按钮自动向上偏移了Snackbar的同等高度,从而确保不会被遮挡住,当Snackbar消失的时候,悬浮按钮会自动向下偏移回到原来的位置。

    刚才说的是CoordinatorLayout可以监听其所有子控件的各种事件,但是Snackbar并不是CoordinatorLayout的子控件,为什么它却可以被监听到呢?

    其实道理很简单,还记得我们在Snackbar的make()方法中传入的第一个参数吗?这个参数就是用来指定Snackbar是基于哪个View来触发的,刚才我们传入的是FloatingActionButton本身,而FloatingActionButton是CoordinatorLayout中的子控件,因此这个事件就理所应当能被监听到了。

    相关文章

      网友评论

        本文标题:12 (上) 最佳的UI体验---Material Design

        本文链接:https://www.haomeiwen.com/subject/ycauwttx.html