Android面试Android知识我爱编程

第一行代码(十二)

2018-04-17  本文已影响58人  radish520like

第十二章主要讲了 Material Design 的一些用法

一、Maternal Design 介绍

  Android 平台的界面风格长期难以统一,为了解决这个问题,Google 推出了全新的界面设计语言--Material Design.
  从 Android5.0开始,就将所有内置的应用都使用 Material Design 风格来进行设计。谷歌在2015年 Google I/O 大会上推出了 Design Support 库。

二、Toolbar

  每个活动最顶部的那个标题栏其实就是 ActionBar,不过 ActionBar 由于其设计原因,被限定只能位于活动顶部,从而不能实现一些 Material Design 的效果,因此官方已经不再建议使用 ActionBar 了。
  ToolBar 的强大之处在于,它不仅继承了 ActionBar 的所有功能,而且灵活性很高,可以配合其他控件一起来完成一些 Material Design 的效果。

注意:任何一个新建的项目,默认都会显示 ActionBar 的,这个 ActionBar 是根据项目中指定的主题来显示的。打开清单文件,找到 <application>标签中有一个 android:theme 属性,并且指定了一个 AppTheme 主题。

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

  这个 DarkActionBar 就是一个深色的 ActionBar 主题,项目中自带的 ActionBar 就是因为指定了这个主题才出现的。但是我们现在要使用 ToolBar,所以要指定一个不带 ActionBar 的主题,通常有 Theme.AppCompat.NoActionBar 和 Theme.AppCompat.Light.NoActionBar 这两种主题,其中 Theme.AppCompat.NoActionBar 表示深色主题,他会将界面的主体颜色设置成深色,陪衬颜色设成淡色。而 Theme.AppCompat.Light.NoActionBar 表示淡色主题,它会将界面的主体颜色设置成淡色,陪衬颜色设成深色。
  观察一下 AppTheme 中的属性重写,这里重写了 colorPrimary、colorPrimaryDark 和 colorAccent 这3个属性的颜色。

image.png
其他:

  使用了 NoActionBar 主题后,我们已经将 ActionBar 隐藏起来了,接下来就要使用 ToolBar 代替 ActionBar 了。

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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"
    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>

这里首先记得添加 xmlns:app 指定命名空间,因为 Material Design 是在 Android5.0系统中才出现的,而很多 Material 属性在5.0之前的系统中并不存在,为了兼容之前的老系统,我们不能使用 android:attribute 这样的写法了,就要使用 app:attribute 的写法。

我们使用了 ToolBar 控件,该控件是 appcompat-v7库提供的,高度指定为 ActionBar 的高度,背景色设置为 colorPrimary。接下来,我们设置了主题,因为我们在 style.xm里面将程序的主题设置成了淡色主题,因此 ToolBar 也是淡色主题,ToolBar 上的元素就会自动使用深色主题,看起来很难看,为了让 ToolBar 单独使用深色主题,我们就使用了 theme 为 ToolBar 单独指定了一个主题:ThemeOverlay.AppCompat.Dark.ActionBar,但是这样又会有一个问题,如果 ToolBar 中有菜单按钮,弹出的菜单项会变成深色主题,又变得很难看,于是使用了 app:popupTheme属性单独将弹出的菜单指定成了淡色主题。

        Toolbar toolBar = (Toolbar) findViewById(R.id.toolbar);
        /*
            这句话的作用是:既使用了 ToolBar,又让它的外观和功能都和 ActionBar 一致
         */
        setSupportActionBar(toolBar);
        <activity android:name=".MaterialDesignActivity"
            android:label="Material Design">
            <!--这里我们为 Activity 指定了一个 label,作用就是让在 ToolBar 中显示标题-->
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

  接下来我们给 ToolBar 添加一个 action 按钮


image.png
image.png
<?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="@mipmap/ic_launcher"
        android:title="Backup"
        app:showAsAction="always" />

    <item
        android:id="@+id/delete"
        android:icon="@mipmap/ic_launcher"
        android:title="delete"
        app:showAsAction="ifRoom" />

    <item
        android:id="@+id/settings"
        android:icon="@mipmap/ic_launcher"
        android:title="settings"
        app:showAsAction="never" />
</menu>
<?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">

    <!--使用 showAsAction 来指定按钮的显示位置 ,
        这里之所以使用 app 命名空间,是为了能够
        兼容低版本-->

    <!-- always 表示永远显示在 ToolBar 上,如果屏幕空间不够则不显示 -->
    <item
        android:id="@+id/backup"
        android:icon="@mipmap/ic_launcher"
        android:title="Backup"
        app:showAsAction="always" />

    <!--ifRoom 表示如果屏幕空间足够就显示在 ToolBar 上,
        不够的话就显示在菜单当中-->

    <item
        android:id="@+id/delete"
        android:icon="@mipmap/ic_launcher"
        android:title="delete"
        app:showAsAction="ifRoom" />

    <!-- never 表示永远显示在菜单当中-->
    <item
        android:id="@+id/settings"
        android:icon="@mipmap/ic_launcher"
        android:title="settings"
        app:showAsAction="never" />
</menu>

注意: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, "backUp", Toast.LENGTH_SHORT).show();
                break;
            case R.id.delete:
                Toast.makeText(this, "delete", Toast.LENGTH_SHORT).show();
                break;
            case R.id.settings:
                Toast.makeText(this, "settings", Toast.LENGTH_SHORT).show();
                break;
            default:
                break;
        }
        return true;
    }

ToolBar 使用总结:

二、DrawerLayout(滑动菜单)

  所谓滑动菜单,就是将一些菜单隐藏起来,不放在主屏幕上,然后可以通过滑动的方式将菜单显示出来。我们可以借助 DrawerLayout 来实现这种效果。
  首先 DrawerLayout (support.v4库提供)是一个布局,在布局中允许放入两个直接子控件,第一个子控件是主屏幕中显示的内容,第二个控件是滑动菜单中显示的内容(侧边栏)。

<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout 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"
    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:layout_gravity="start"
        android:text="我是侧边栏中的 TextView"
        android:textSize="30sp"
        android:background="@android:color/white"/>

</android.support.v4.widget.DrawerLayout>
image.png

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

  这时候会有点问题,可能用户不知道可以滑动,不知道有侧边栏这个东西,Material Design 建议的做法是在 Toolbar 的左边加入一个导航按钮,点击按钮就将侧边栏展示出来。

        drawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
        /*
            通过 getSupportActionBar()方法获取 ActionBar,虽然是 ActionBar,
            但是其实具体实现是由 ToolBar 来实现的
         */
        ActionBar actionBar = getSupportActionBar();
        /*
            这里注意,其实 ToolBar 最左侧的导航按钮就叫做 HomeAsUp 按钮
            他默认的是一个返回箭头,我们修改了他的样式
         */
        if(actionBar != null){
            //让导航按钮显示出来
            actionBar.setDisplayHomeAsUpEnabled(true);
            //设置导航按钮图标
            actionBar.setHomeAsUpIndicator(R.mipmap.ic_launcher);
        }
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()){
            case R.id.backup:
                Toast.makeText(this, "backUp", Toast.LENGTH_SHORT).show();
                break;
            case R.id.delete:
                Toast.makeText(this, "delete", Toast.LENGTH_SHORT).show();
                break;
            case R.id.settings:
                Toast.makeText(this, "settings", Toast.LENGTH_SHORT).show();
                break;
            case android.R.id.home:
                /*
                    注意:这里,HomeAsUp 按钮的 id 永远都是 android.R.id.home
                 */
                drawerLayout.openDrawer(GravityCompat.START);
                break;
            default:
                break;
        }
        return true;
    }

DrawerLayout 使用总结:

三、NavigationView

  NavigationView 是 Design Support 库中提供的一个控件,所以我们需要引入这个库,而且在使用 NavigationView 之前,我们需要提前准备好两个东西:menu 和 headerLayout,menu 是用来在 NavigationView 中显示具体的菜单项的,headerLayout 则是用来在 NavigationView 中显示头布局的。

    //design 库
    compile 'com.android.support:design:24.2.1'
    //circleimageview
    compile 'de.hdodenhof:circleimageview:2.1.0'

  CircleImageView 可以用来轻松实现图片圆形化的功能,项目主页地址是:https://github.com/hdodenhof/CircleImageView
  在开始使用 NavigationView 之前,我们还需要提前准备好两个东西:menu 和 headerLayout,其中 menu 是用来在 NavigationView 中显示具体的菜单项的, headerLayout 则是用来在显示头部布局的。
  接下来在 menu 文件夹中创建一个 xml 文件,叫做nav_menu.xml

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- group表示一个组,checkableBehavior属性指定为single表示组中所有菜单项只能单选 -->
    <group android:checkableBehavior="single">
        <item android:id="@+id/nav_call"
            android:icon="@mipmap/ic_launcher"
            android:title="Call" />

        <item android:id="@+id/nav_friends"
            android:icon="@mipmap/ic_launcher"
            android:title="Friends" />

        <item android:id="@+id/nav_location"
            android:icon="@mipmap/ic_launcher"
            android:title="Location" />

        <item android:id="@+id/nav_mail"
            android:icon="@mipmap/ic_launcher"
            android:title="Mail" />

        <item android:id="@+id/nav_task"
            android:icon="@mipmap/ic_launcher"
            android:title="Task" />
    </group>
</menu>

  然后我们在 layout 文件夹中创建一个 xml 文件,命名为:nav_header.xml

<?xml version="1.0" encoding="utf-8"?>
<!-- 这里宽度设为match_parent,高度设为180dp,这是一个 NavigationView 比较合适的高度 -->
<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"
    android:orientation="vertical">

    <de.hdodenhof.circleimageview.CircleImageView
        android:id="@+id/icon_img"
        android:layout_width="70dp"
        android:layout_height="70dp"
        android:layout_centerInParent="true"
        android:src="@mipmap/ic_launcher" />

    <TextView
        android:id="@+id/mail"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:text="www.baidu.com@game.com"
        android:textColor="#ffffff"
        android:textSize="14sp" />

    <TextView
        android:id="@+id/username"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_above="@id/mail"
        android:text="Tony"
        android:textColor="#ffffff"
        android:textSize="14sp"/>

</RelativeLayout>

  然后修改 Activity 的 layout 布局文件

<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout 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"
    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:headerLayout="@layout/nav_header"
        app:menu="@menu/nav_menu">
        <!-- 这里通过 app:header 和 app:menu 属性将我们刚才准备好的
             menu 和 headerLayout 设置拉进去 -->

    </android.support.design.widget.NavigationView>

</android.support.v4.widget.DrawerLayout>
        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) {
                drawerLayout.closeDrawers();
                return true;
            }
        });
NavigationView.png

NavigationView使用总结:

四、FloatingActionButton (悬浮按钮)

  这是 Design Support 库中提供的一个控件,默认使用 colorAccent 来作为按钮的颜色。

<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout 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"
    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>

        <!-- 悬浮按钮 -->
        <!-- 这里 end 和 start 一样,如果系统语言是从左往右的,那么 end
             就在右边,如果系统语言是从右往左的,那么 end 就在左边。
             可以使用 app:elevation 属性来给按钮指定一个高度值,高度值
             越大,投影范围也越大,但是投影效果越淡,高度值越小,投影范围
             也越小,投影效果越浓 -->
        <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="@mipmap/ic_launcher"
            app:elevation="8dp"/>

    </FrameLayout>

    <!-- 侧边栏 -->
    <!-- ...... -->

</android.support.v4.widget.DrawerLayout>
        FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(ThirdActivity.this, "点击了悬浮按钮", Toast.LENGTH_SHORT).show();
            }
        });

五、Snackbar

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

        FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
//                Toast.makeText(ThirdActivity.this, "点击了悬浮按钮", Toast.LENGTH_SHORT).show();
                //使用 Snackbar
                /*
                    Snackbar 使用 make 方法来创建
                        参1:传入一个 View 对象,只要是当前界面任意一个 View 都可以,Snackbar 会使用
                             这个 View 来自动查找最外层的布局,用于展示Snackbar
                        参2:Snackbar 中显示的内容
                        参3:Snackbar 显示的时长
                 */
                Snackbar.make(v,"是你点击了悬浮按钮?",Snackbar.LENGTH_SHORT)
                        //通过 setAction 方法设置一个动作
                        .setAction("Yes", new View.OnClickListener() {
                            @Override
                            public void onClick(View v) {
                                Toast.makeText(ThirdActivity.this, "是我点击了", Toast.LENGTH_SHORT).show();
                            }
                            //调用 show() 方法让 Snackbar 显示出来
                        }).show();
            }
        });
Snackbar.png

  不管是点击 YES 还是过一段时间等待 Snackbar 自动消失,Snackbar都是自带动画效果的,但是你会发现一个问题,就是 Snackbar 将我们的 FloatingActionBar 给挡住了,这就需要借助 CoordinatorLayout 就行了。

六、CoordinatorLayout

  CoordinatorLayout 可以说是一个加强版的 FrameLayout,该布局也是由 Design Support 库提供的,在普通情况下和 FrameLayout 基本一致。
  事实上,CoordinatorLayout 可以监听其所有子控件的各种事件,然后自动帮我们做出最为合理的响应,举个例子:刚才弹出的 Snackbar 将悬浮按钮挡住了,如果我们让 CoordinatorLayout 监听到 Snackbar 的弹出事件,那么它会自动将内部的 FloatingActionButton 向上偏移,从而确保不会被 Snackbar 挡住。

<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout 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"
    android:id="@+id/drawer_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <!-- 主布局 -->
    <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>

        <!-- 悬浮按钮 -->
        <!-- 这里 end 和 start 一样,如果系统语言是从左往右的,那么 end
             就在右边,如果系统语言是从右往左的,那么 end 就在左边。
             可以使用 app:elevation 属性来给按钮指定一个高度值,高度值
             越大,投影范围也越大,但是投影效果越淡,高度值越小,投影范围
             也越小,投影效果越浓 -->
        <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="@mipmap/ic_launcher"
            app:elevation="8dp"/>

    </android.support.design.widget.CoordinatorLayout>

    <!-- 侧边栏 -->
    <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:headerLayout="@layout/nav_header"
        app:menu="@menu/nav_menu">

    </android.support.design.widget.NavigationView>

</android.support.v4.widget.DrawerLayout>

CoordinatorLayout.png

  问题解决了,但是有点奇怪,FloatingActionButton 是 CoordinatorLayout 的子控件没问题,但 Snackbar 并不是,为什么 CoordinatorLayout 还会监听到呢?

道理很简单,我们在 Snackbar 的 make() 方法中传入的第一个参数,就是用来指定 Snackbar 是基于哪个 View 来触发的,我们传入的是 FloatingActionBar,而 FloatingActionBar 又是 CoordinatorLayout 中的子控件,所以这个事件就能被监听到,如果给 Snackbar 的 make() 方法的第一个参数传入别的 View,比如传入 DrawerLayout,那么 Snackbar 就会再次遮挡悬浮按钮,因为 DrawerLayout 不是 CoordinatorLayout 的子控件,CoordinatorLayout 也就无法坚挺到 Snackbar 的弹出和隐藏事件了。

FloatingActionBar、Snackbar、CoordinatorLayout 使用总结

七、CardView(卡片式布局)

  CardView 由 appcompat-v7 提供,实际上 CardView 也是一个 FrameLayout,只是额外提供了圆角和阴影等效果,看上去会有立体的感觉。
  我们使用一个简单的例子来展示一下 CardView 控件

<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout 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"
    android:id="@+id/drawer_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <!-- 主布局 -->
    <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>
        <!-- 这里添加一个 RecyclerView -->
        <android.support.v7.widget.RecyclerView
            android:id="@+id/recycler_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent">

        </android.support.v7.widget.RecyclerView>

        <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="@mipmap/ic_launcher"
            app:elevation="8dp"/>

    </android.support.design.widget.CoordinatorLayout>

    <!-- 侧边栏 -->
    <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:headerLayout="@layout/nav_header"
        app:menu="@menu/nav_menu">

    </android.support.design.widget.NavigationView>

</android.support.v4.widget.DrawerLayout>
/**
 * 实体类
 */

public class Fruit {

    private String name;//水果的名字
    private int imageId;//水果对应资源的图片id

    public Fruit(){

    }

    public Fruit(String name, int imageId) {
        this.name = name;
        this.imageId = imageId;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getImageId() {
        return imageId;
    }

    public void setImageId(int imageId) {
        this.imageId = imageId;
    }

    @Override
    public String toString() {
        return "Fruit{" +
                "name='" + name + '\'' +
                ", imageId=" + imageId +
                '}';
    }
}
<!-- 这个是RecyclerView的item布局 -->
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView 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="wrap_content"
    android:layout_margin="5dp"
    android:orientation="vertical"
    app:cardCornerRadius="4dp">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <ImageView
            android:id="@+id/iv_fruit"
            android:layout_width="match_parent"
            android:layout_height="100dp"
            android:scaleType="centerCrop" />

        <TextView
            android:id="@+id/tv_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:layout_margin="5dp"
            android:textSize="16sp" />

    </LinearLayout>

</android.support.v7.widget.CardView>
/**
 * RecyclerView 的适配器
 */
public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHolder>{

    private List<Fruit> mFruitList;
    private Context mContext;

    public FruitAdapter(List<Fruit> list){
        this.mFruitList = list;
    }

    static class ViewHolder extends RecyclerView.ViewHolder{

        ImageView ivFruit;
        TextView tvName;

        public ViewHolder(View itemView) {
            super(itemView);
            ivFruit = itemView.findViewById(R.id.iv_fruit);
            tvName = itemView.findViewById(R.id.tv_name);
        }
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if(mContext == null){
            mContext = parent.getContext();
        }
        View view = LayoutInflater.from(mContext).inflate(R.layout.fruit_item,parent,false);
        return new ViewHolder(view);
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        Fruit fruit = mFruitList.get(position);
        holder.tvName.setText(fruit.getName());
        Glide.with(mContext).load(fruit.getImageId()).into(holder.ivFruit);
    }

    @Override
    public int getItemCount() {
        return mFruitList == null ? 0 : mFruitList.size();
    }
}

这里使用了 Glide 加载图片,Glide 项目主页地址是:https://github.com/bumptech/glide
这里为什么要用 Glide 而不用传统的方式设置图片呢?因为如果图片像素非常高的话,如果不进行压缩就直接展示,很容易就会引起内存泄露,而 Glide 在内部做了许多复杂的逻辑操作,其中就包括了图片的压缩。

    //数据
    private Fruit[] fruits = {
            new Fruit("Apple", R.mipmap.ic_launcher),
            new Fruit("Banana", R.mipmap.ic_launcher),
            new Fruit("Orange", R.mipmap.ic_launcher),
            new Fruit("Watermelon", R.mipmap.ic_launcher),
            new Fruit("Pear", R.mipmap.ic_launcher),
            new Fruit("Grape", R.mipmap.ic_launcher),
            new Fruit("Pineapple", R.mipmap.ic_launcher),
            new Fruit("Strawbeery", R.mipmap.ic_launcher),
            new Fruit("Cherry", R.mipmap.ic_launcher),
            new Fruit("Mango", R.mipmap.ic_launcher)
    };
    private List<Fruit> fruitList = new ArrayList<>();
        initFruits();

        RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
        GridLayoutManager gridLayoutManager = new GridLayoutManager(this,2);
        recyclerView.setLayoutManager(gridLayoutManager);
        FruitAdapter adapter = new FruitAdapter(fruitList);
        recyclerView.setAdapter(adapter);
    private void initFruits() {
        fruitList.clear();
        for (int i = 0; i < 50; i++) {
            Random random = new Random();
            int index = random.nextInt(fruits.length);
            fruitList.add(fruits[index]);
        }
    }
CardView.png

  CardView 的效果已经出来了,但是你自己观察一下,ToolBar 被 RecyclerView 挡住了,怎么办?这就要借助另一个工具 -- AppBarLayout了。

八、AppBarLayout

  分析下上述问题的原因,因为 ToolBar 和 RecyclerView 都放在 CoordinatorLayout 中,而 CoordinatorLayout 是一个加强版的 FrameLayout,那么肯定会遮挡了啊。那么如果我们在 CoordinatorLayout 内,给 ToolBar 和 RecyclerView 的包一层 LinearLayout 或者 RelativeLayout 是不是就可以了呢?是的,是可以了,但是,我们既然引出了 AppBarLayout,就有用 AppBarLayout 的理由。
  AppBarLayout 实际上是一个垂直方向上的 LinearLayout,而且它也是 Design Support 库中的空间,它在其内部做了非常多的滚动事件的封装。先看看如何使用:

    <!-- 主布局 -->
    <android.support.design.widget.CoordinatorLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <!-- 在 Toolbar 外层嵌套一个 AppBarLayout -->
        <android.support.design.widget.AppBarLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

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

        <!-- 给 ReccylerView 添加一个 app:layout_behavior 属性,值也是固定这么写的 -->
        <android.support.v7.widget.RecyclerView
            android:id="@+id/recycler_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layout_behavior="@string/appbar_scrolling_view_behavior">

        </android.support.v7.widget.RecyclerView>

        <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="@mipmap/ic_launcher"
            app:elevation="8dp" />

    </android.support.design.widget.CoordinatorLayout>
AppBarLayout1.png

注意:这里如果将 RecyclerView 也放在 AppBarLayout 中,则会无法滑动。如果不给 RecyclerView 添加 app:layout_behavior 属性,则会出现 RecyclerView 的上面部分被 ToolBar 遮盖了,如下图所示。

AppBarLayout2.png

  注意顶部,这个是 RecyclerView 滑动到顶部的样子,也就是说 RecyclerView 的上面部分被 ToolBar 遮盖了。
  当 AppBarLayout 接收到滚动事件的时候,它内部的子控件其实是可以指定如何去响应这些事件的,通过app:layout_scrollFlags属性就可以

    <!-- 主布局 -->
    <android.support.design.widget.CoordinatorLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <!-- 在 Toolbar 外层嵌套一个 AppBarLayout -->
        <android.support.design.widget.AppBarLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

            <!-- 给 Toolbar 添加了 app:layout_scrollFlags 属性
                 其中 scroll 表示当 RecyclerView 向上滚动的时候,Toolbar 会跟随一起向上滚动并实现隐藏;
                 enterAlways 表示当RecyclerView 向下滚动的时候,Toolbar 会跟着一起向下滚动并重新显示;
                 snap 表示当 Toolbar 还没有完全隐藏或显示的时候,会根据当前滚动的距离,自动选择是隐藏还是显示-->
            <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"
                app:layout_scrollFlags="scroll|enterAlways|snap">

            </android.support.v7.widget.Toolbar>

        </android.support.design.widget.AppBarLayout>

        <!-- 给 RecyclerView 添加一个 app:layout_behavior 属性,值也是固定这么写的 -->
        <android.support.v7.widget.RecyclerView
            android:id="@+id/recycler_view"
            app:layout_behavior="@string/appbar_scrolling_view_behavior"
            android:layout_width="match_parent"
            android:layout_height="match_parent" >

        </android.support.v7.widget.RecyclerView>

        <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="@mipmap/ic_launcher"
            app:elevation="8dp" />

    </android.support.design.widget.CoordinatorLayout>

CardView、AppBarLayout使用总结:

九、SwipeRefreshLayout(下拉刷新)

  SwipeRefreshLayout 是由 support-v4 库提供,一般下拉刷新都配合 RecyclerView 一起使用,我们只需要在 RecyclerView 外层嵌套一个 SwipeRefreshLayout 即可。

    <!-- 主布局 -->
    <android.support.design.widget.CoordinatorLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <!-- 在 Toolbar 外层嵌套一个 AppBarLayout -->
        <android.support.design.widget.AppBarLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

            <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:layout_scrollFlags="scroll|enterAlways|snap"
                app:popupTheme="@style/ThemeOverlay.AppCompat.Light">

            </android.support.v7.widget.Toolbar>

        </android.support.design.widget.AppBarLayout>

        <android.support.v4.widget.SwipeRefreshLayout
            android:id="@+id/swipe_refresh"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layout_behavior="@string/appbar_scrolling_view_behavior">

            <android.support.v7.widget.RecyclerView
                android:id="@+id/recycler_view"
                android:layout_width="match_parent"
                android:layout_height="match_parent">

            </android.support.v7.widget.RecyclerView>

        </android.support.v4.widget.SwipeRefreshLayout>

        <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="@mipmap/ic_launcher"
            app:elevation="8dp" />

    </android.support.design.widget.CoordinatorLayout>

注意:这里因为 RecyclerView 外面嵌套了一层 SwipeRefreshLayout 所以,之前设置的 app:layout_behavior 属性也必须要放到 SwipeRefreshLayout 中才行。

        swipeRefresh = (SwipeRefreshLayout) findViewById(R.id.swipe_refresh);
        //设置下拉刷新进度条的颜色,参数是可变参数
        swipeRefresh.setColorSchemeResources(R.color.colorPrimary,R.color.colorAccent);
        //设置下拉刷新的监听器,当触发了下拉刷新操作时,就会回调这个监听器的 onRefresh 方法
        swipeRefresh.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
            @Override
            public void onRefresh() {
                refreshFruits();
            }
        });
    private void refreshFruits(){
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        initFruits();
                        adapter.notifyDataSetChanged();
                        //传入 false,表示刷新事件结束,并隐藏刷新进度条
                        swipeRefresh.setRefreshing(false);
                    }
                });
            }
        }).start();
    }
swipeRefreshLayout.png

十、可折叠式标题栏

  如果我们希望根据自己的喜好随意定制标题栏的样式,比如实现一个可折叠式的标题栏的效果,就需要借助 CollapsingToolbarLayout 这个工具。
  CollapsingToolbarLayout 是一个作用于 Toolbar 基础之上的布局,它也是由 Design Support 库提供,CollapsingToolbarLayout 可以让 Toolbar 的效果变得更加丰富。

注意:CollapsingToolbarLayout 不能独立存在,它在设计的时候就被限定只能作为AppBarLayout 的直接子布局来使用,而AppBarLayout 又必须是 CoordinatorLayout 的子布局,所以嵌套的代码如下:

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout 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"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.design.widget.AppBarLayout
        android:id="@+id/appbar"
        android:layout_width="match_parent"
        android:layout_height="250dp">

        <!-- 嵌套在 AppBarLayout 中的 CollapsingToolbarLayout
              app:contentScrim 属性用于指定 CollapsingToolbarLayout 在趋于折叠状态
              以及折叠之后的背景色,其实 CollapsingToolbarLayout 在折叠后就是一个普
              通的Toolbar。
              app:layout_scrollFlags 属性中 scroll 表示 CollapsingToolbarLayout 会
              随着内容详情的滚动一起滚动,exitUntilCollapsed 表示当 CollapsingToolbarLayout
              会随着完成折叠之后就保留在界面上,不再移出屏幕-->
        <android.support.design.widget.CollapsingToolbarLayout
            android:id="@+id/collapsing_toolbar"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:contentScrim="?attr/colorPrimary"
            app:layout_scrollFlags="scroll|exitUntilCollapsed">

            <!-- app:layout_collapseMode 属性指定当前控件在 CollapsingToolbarLayout
              折叠过程中的折叠模式,pin 表示在折叠过程中位置始终保持不变,parallax 表示
              会在折叠的过程中产生一定的错位偏移,视觉效果很好 -->
            <ImageView
                android:id="@+id/iv_fruit"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:scaleType="centerCrop"
                app:layout_collapseMode="parallax"/>

            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                app:layout_collapseMode="pin">

            </android.support.v7.widget.Toolbar>

        </android.support.design.widget.CollapsingToolbarLayout>

    </android.support.design.widget.AppBarLayout>

    <!-- NestedScrollView 是在 ScrollView 的基础之上增加了嵌套
         响应滚动事件的功能,由于 CoordinatorLayout 本身已经可以
         响应滚动事件了,因此我们在内部就需要使用 NestedScrollView
         或 RecyclerView ,而且还加了 app:layout_behavior 属性,
         为了不要遮挡Toolbar -->
    <android.support.v4.widget.NestedScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">

            <android.support.v7.widget.CardView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginBottom="15dp"
                android:layout_marginLeft="15dp"
                android:layout_marginRight="15dp"
                android:layout_marginTop="35dp"
                app:cardCornerRadius="4dp">

                <TextView
                    android:id="@+id/tv_fruit_content"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_margin="10dp" />

            </android.support.v7.widget.CardView>

        </LinearLayout>

    </android.support.v4.widget.NestedScrollView>

    <!-- 使用 app:layout_anchor 属性指定一个锚点
         使用 app:layout_anchorGravity 属性将悬浮按钮定位在标题栏区域的右下角 -->
    <android.support.design.widget.FloatingActionButton
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="16dp"
        app:layout_anchor="@id/appbar"
        app:layout_anchorGravity="bottom|end"/>

</android.support.design.widget.CoordinatorLayout>

该布局分三部分,最外层是一个 CoordinatorLayout
第一部分:在 CoordinatorLayout 内嵌一个 AppBarLayout,然后在 AppBarLayout 中再内嵌一个 CollapsingToolbarLayout,然后再 CollapsingToolbarLayout 中再加上 ImageView 和 Toolbar 两个控件。
第二部分:加入 NestedScrollView 控件,和 AppBarLayout 是同级的,在 NestedScrollView 内部嵌套一个 LinearLayout,然后在 LinearLayout 中加上 CardView 布局。
第三部分:比较简单,就是加一个FloatingActionButton,和 AppBarLayout 以及 NestedScrollView 都是同级的。

        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        ActionBar actionBar = getSupportActionBar();
        if (actionBar != null) {
            actionBar.setDisplayHomeAsUpEnabled(true);
        }

        CollapsingToolbarLayout collapsingToolbar = (CollapsingToolbarLayout) findViewById(R.id.collapsing_toolbar);
        collapsingToolbar.setTitle("Apple");

        ImageView ivFruit = (ImageView) findViewById(R.id.iv_fruit);
        TextView tvFruitContent = (TextView) findViewById(R.id.tv_fruit_content);
        Glide.with(this).load(R.mipmap.ic_launcher).into(ivFruit);
        tvFruitContent.setText(generateFruitContent("Apple"));
    /**
     * 生成比较长的内容
     */
    private String generateFruitContent(String name) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < 500; i++) {
            sb.append(name);
        }
        return sb.toString();
    }
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()){
            case android.R.id.home:
                finish();
                return true;
            default:
                break;
        }
        return super.onOptionsItemSelected(item);
    }
device-2018-04-17-150418.png

CollapsingToolbarLayout使用总结:

十一、系统状态栏

  在 Android 5.0 系统之前,我们是无法对状态栏的背景或颜色进行操作的。

  想让背景图能够和系统状态栏融合,需要借助 android:fitsSystemWindows 这个属性,在控件中,将该属性指定为 true,表示该控件会出现在系统状态栏里,修改布局文件:


image.png

这里如果只给 ImageView 设置 android:fitsSystemWindows 属性是没有用的,必须将 ImageView 布局结构中的所有父布局都设置上这个属性才可以。

image.png

  有点变化,但不是我们要的效果。这时就需要将状态栏颜色指定成透明色才行。

设置成透明的方法很简单,在主题中将 android:statusBarColor 属性的值指定成 @android:color/transparent 就可以了,但是问题是,android:statusBarColor 这个属性是从 API 21(Android 5.0)开始才有的,之前的系统无法指定这个属性。

  为了解决上述问题,我们做如下操作


image.png

  然后在 values-v21 目录下创建一个styles.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <style name="FruitActivityTheme" parent="AppTheme">
        <item name="android:statusBarColor">@android:color/transparent</item>
    </style>
</resources>

由于 values-v21 目录是只有 Android 5.0 及其以上的系统才会去读取的,所以,这么写是没问题的。但是 Android 5.0 之前的系统却无法识别 FruitActivityTheme 这个主题,因此我们还需要修改 values/styles.xml 文件。

    <!-- 因为Android 5.0 之前的系统无法指定状态栏的颜色,所以这里什么都不用做 -->
    <style name="FruitActivityTheme" parent="AppTheme">

    </style>

  最后别忘了在清单文件中给 Activity 添加 Theme 属性


image.png

最后,Material Design 的官方文章:https://material.google.com

下一篇文章:https://www.jianshu.com/p/501825ee6fff

上一篇下一篇

猜你喜欢

热点阅读