Material Design 基础

0 简介

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

1 ToolBar 控件

ToolBar 控件是Google推荐使用的,它不仅继承了ActionBar的所有功能,而且使用起来十分灵活,可以和其他控件结合使用,从而打造出Material Design 效果.

1.1 隐藏系统原有的 ActionBar

AndroidManifest.xml 文件中找到<application> 标签中的 theme 属性的值.一般是 @style/AppTheme,跳转到 AppTheme的定义(res/values/styles),如下 :

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

其中的 Theme.AppCompat.Light.DarkActionBar 是一个深色的ActionBar主题.我们可以将其替换成Theme.AppCompat.Light.NoActionBar 则可以将ActionBar隐藏. 接着我们就可以使用ToolBar了.对MainActivity的布局文件修改如下

<!-- 
    1. xmlns:app="http://schemas.android.com/apk/res-auto" 新增的命名控件,
       主要是为了兼容 Android 5.0 以前的设备.
    2. android:theme="@style/ThemeOverlay.AppCompat.Dark" 设置ToolBar主题.
       因为我们App使用的是 Light主题,因此文字是深色,再次让此控件单独使用此
       主题,效果是显示的文字是亮的.
    3. app:popupTheme="@style/ThemeOverlay.AppCompat.Light" 设置菜单主题色.
       popupTheme 属性是 5.0 新增因此需要使用app命名空间做兼容处理.
    4. android:layout_height="?attr/actionBarSize" 设置ToolBar的高度和
       ActionBar一样.
-->
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    android:id="@+id/activity_main"
    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"
    >
    <!-- ToolBar 替代 ActionBar -->
    <android.support.v7.widget.Toolbar
        android:id="@+id/tb_actionbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="@color/colorPrimary"
        android:theme="@style/ThemeOverlay.AppCompat.Dark"
        app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
        />
</RelativeLayout>

接着在Activity中进行设置

// 1. 找到Toolbar
Toolbar toolbar = (Toolbar) findViewById(R.id.tb_actionbar);
// 2. 设置ActionBar
setSupportActionBar(toolbar);

这样基本的设计就完成了.效果和ActionBar看上去一样.

1.2 为 ToolBar 添加action

  • res/ 下创建 menu 文件夹,然后在res/menu/ 下创建 toolbar_menu.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">
    <!--
        1. app:showAsAction="always" 用来设置按钮的显示位置.
            1.1 always : 永远显示在Toolbar 中.
            1.2 ifRoom : 如果空间足够则显示在ToolBar中.
            1.3 never  : 永远显示在菜单中
        2. ToolBar 中的Action只会显示图片,菜单中的action只会显示文字.
    -->
    <item android:id="@+id/tb_backup"
          android:title="BackUp"
          android:icon="@drawable/icon_backup"
          app:showAsAction="always"
        />
    <item android:id="@+id/tb_delete"
          android:title="Delete"
          android:icon="@drawable/icon_delete"
          app:showAsAction="ifRoom"
        />
    <item android:id="@+id/tb_settings"
          android:title="Settings"
          android:icon="@drawable/icon_settings"
          app:showAsAction="never"
        />
</menu>
  • 在Activity总重写和菜单相关的方法
// 加载菜单布局文件.
@Override
public boolean onCreateOptionsMenu(Menu menu) {
    getMenuInflater().inflate(R.menu.toolbar_menu,menu);
    return true;
}
// 添加菜单选项点击事件. 我们在每一个action使用Toast进行提示.
@Override
public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()){
        case R.id.tb_backup:
            Toast.makeText(MainActivity.this,"备份",Toast.LENGTH_SHORT).show();
            break;
        case R.id.tb_delete:
            Toast.makeText(MainActivity.this,"删除",Toast.LENGTH_SHORT).show();
            break;
        case R.id.tb_settings:
            Toast.makeText(MainActivity.this,"设置",Toast.LENGTH_SHORT).show();
            break;
    }
    return true;
}

运行程序效果图如下 :

ToolBar 效果图

2 滑动菜单

滑动菜单就是将一些单选项隐藏起来,而不是放在主屏幕上,然后可以通过滑动将菜单显示出来.这zhong种方式节省了空间又实现了非常好的滑动效果.Material Design 推荐使用这种方式. Google提供了DrawerLayout 控件来实现这种效果.

2.1 DrawerLayout 使用

在DrawerLayout 布局中允许添加两个直接子控件, 第一个是屏幕中显示的内容;第二个是菜单中显示的内容.现在我们对Activity布局文件进行如下修改.

<!-- 将根布局替换成 DrawLayout  -->
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout
    android:id="@+id/dl_main"
    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"
    >
    <!-- 屏幕中的内容 -->
    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <android.support.v7.widget.Toolbar
            android:id="@+id/tb_actionbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="@color/colorPrimary"
            android:theme="@style/ThemeOverlay.AppCompat.Dark"
            app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
            />
    </FrameLayout>
    <!--
        菜单中的内容
        1. android:layout_gravity="start" 该属性必须设置,
           它表示了菜单弹出的方向.
           start : 表示根据系统语言进行判断弹出方向.
           left  : 左侧弹出
           right : 右侧弹出.
     -->
    <TextView
        android:layout_width="match_parent"
        android:layout_gravity="start"
        android:text="菜单"
        android:textSize="32sp"
        android:background="#FF00FF"
        android:layout_height="match_parent"/>
</android.support.v4.widget.DrawerLayout>

此时运行程序就可以从通过边缘滑动将菜单显示出来 效果图如下.

Paste_Image.png

接下来我们将在ToolBar 中增添一个按钮通过点击该按钮来实现打开菜单,类似于QQ上的点击头像,引出菜单.我们修改Activity代码如下:

  • onCreate() 添加如下代码.

    • 我们首先通过 findViewById() 方法获取到DrawerLayout实例.
    • 通过 getSupportActionBar() 来获取到ActionBar实例,此时的ActionBar实际是一个ToolBar.
    • 通过 setDisplayHomeAsUpEnabled(true) 方法让导航按钮显示出来.
    • 通过 setHomeAsUpIndicator() 来设置导航按钮的图标.
// 实现按钮弹出菜单
mDrawerLayout = (DrawerLayout) findViewById(R.id.dl_main);
ActionBar actionBar = getSupportActionBar();
if (actionBar != null){
    // 设置显示导航按钮.也就是返回按钮
    actionBar.setDisplayHomeAsUpEnabled(true);
    // 设置返回按钮的图片
    actionBar.setHomeAsUpIndicator(R.drawable.ic_menu);
}
  • 在 onOptionsItemSelected 中添加如下代码

    • switch(item.getItemId()) 中增加一个case分支 .用于处理用户点击导航按钮时的响应处理.
    • 通过 DrawerLayout 的 openDrawer() 打开菜单. 该函数需要传入一个Gravity参数.

注意 :系统提供的导航按钮的id是 android.R.id.home

// 新增加一个case 判断
case android.R.id.home:
// 弹出菜单.
mDrawerLayout.openDrawer(GravityCompat.START);
break;

2.2 NavigationView 的使用

NavigationView 是 design support 库中提供的一个控件 ,它严格按照 Material Design 来设计的 , 它使滑动菜单的实现变的非常简单. 它一般分为上下两部分;下面是菜单选项,上面是其他信息.

注意 : 由于我们需要使用design support库和图片kuang'jia框架因此需要添加如下依赖

// 添加design support 库依赖
compile 'com.android.support:design:24.2.1'
// 添加圆形图片裁剪图第三方库依赖
compile 'de.hdodenhof:circleimageview:2.1.0'
  • res/menu/下创建菜单布局文件 nav_menu.xml 内容如下:
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <!--
        1. android:checkableBehavior="single" 设置在该组中的item是单选的.
    -->
    <group android:checkableBehavior="single">
        <item
            android:id="@+id/nav_call"
            android:title="Call"
            android:icon="@drawable/ic_menu_call"
            />
        <item
            android:id="@+id/nav_friend"
            android:title="Friend"
            android:icon="@drawable/ic_menu_friend"
            />
        <item
            android:id="@+id/nav_location"
            android:title="Location"
            android:icon="@drawable/ic_menu_location"
            />
        <item
            android:id="@+id/nav_mail"
            android:title="Mail"
            android:icon="@drawable/ic_menu_mail"
            />
        <item
            android:id="@+id/nav_task"
            android:title="Tasks"
            android:icon="@drawable/ic_menu_task"
            />
    </group>
</menu>
  • 创建上半部分布局文件.
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
                android:padding="10dp"
                android:background="?attr/colorPrimary"
              android:layout_height="180dp">
    <!-- 使用圆形图片框架 -->
    <de.hdodenhof.circleimageview.CircleImageView
        android:layout_width="70dp"
        android:id="@+id/iv_head_pic"
        android:src="@drawable/head_pic"
        android:layout_centerInParent="true"
        android:layout_height="70dp"/>
    <TextView
        android:layout_width="wrap_content"
        android:id="@+id/tv_mail"
        android:text="wsj_china_cpu@163.com"
        android:textSize="14sp"
        android:textColor="#FFF"
        android:layout_alignParentBottom="true"
        android:layout_height="wrap_content"/>
    <TextView
        android:layout_width="wrap_content"
        android:id="@+id/tv_name"
        android:layout_above="@id/tv_mail"
        android:text="WSJ"
        android:textColor="#FFF"
        android:textSize="14sp"
        android:layout_height="wrap_content"/>

</RelativeLayout>
  • 将MainActivity的布局文件中的TextView替换成 NavigationView
<android.support.design.widget.NavigationView
android:layout_width="match_parent"
android:id="@+id/nav_view"
android:layout_gravity="start"
app:menu="@menu/nav_menu"
app:headerLayout="@layout/header_layout"
android:layout_height="match_parent"/>
  • 在 MainActivity 的onCreate() 中添加如下代码

    • 首先获取到NavigationView实例.
    • 通过 setCheckedItem() 设置默认选择的item
    • 通过 setNavigationItemSelectedListener 设置item点击事件监听.
    • 使用Toast打印点击的选项.
    • 通过 closeDrawers() 关闭菜单.
 // 导航菜单
 NavigationView navigationView =
         (NavigationView) findViewById(R.id.nav_view);
 // 设置默认选项
 navigationView.setCheckedItem(R.id.nav_call);
 // 设置Item点击
 navigationView.setNavigationItemSelectedListener(
         new NavigationView.OnNavigationItemSelectedListener() {
     @Override
     public boolean onNavigationItemSelected(@NonNull MenuItem item) {
         switch (item.getItemId()){
             case R.id.nav_call:
                 Toast.makeText(MainActivity.this
                         ,"电话",Toast.LENGTH_SHORT).show();
                 break;
             case R.id.nav_friend:
                 Toast.makeText(MainActivity.this
                         ,"朋友",Toast.LENGTH_SHORT).show();
                 break;
             case R.id.nav_mail:
                 Toast.makeText(MainActivity.this
                         ,"邮件",Toast.LENGTH_SHORT).show();
                 break;
             case R.id.nav_location:
                 Toast.makeText(MainActivity.this
                         ,"地位",Toast.LENGTH_SHORT).show();
                 break;
             case R.id.nav_task:
                 Toast.makeText(MainActivity.this
                         ,"任务",Toast.LENGTH_SHORT).show();
                 break;
         }
         // 关闭菜单
         mDrawerLayout.closeDrawers();
         return true;
     }
 });

此时的效果图如下


Paste_Image.png

3 悬浮按钮和可交互提示

立面设计师Material Design中的一条非常重要的设计思想. 悬浮按钮就体现了立面设计的思想.这种按钮不属于主界面的一部分,而是位于另外一个纬度,给人一种悬浮的感觉.

3.1 FloatActionButton 使用

FloatActionButton 是Google提供的悬浮按钮控件.

  • 在 MainActivity中添加FloatActionButton.
<!--
    添加悬浮按钮
    1. app:elevation="8dp" 指定高度值(立体) 高度值也大投影就越大,
       投影效果也就越淡.
 -->
<android.support.design.widget.FloatingActionButton
    android:layout_width="wrap_content"
    android:id="@+id/fab"
    android:layout_gravity="bottom|end"
    android:layout_margin="16dp"
    android:src="@drawable/ic_done"
    app:elevation="8dp"
    android:layout_height="wrap_content"/>
  • 在MainActivity中添加点击事件
// 悬浮按钮的点击事件.
FloatingActionButton fab =
        (FloatingActionButton) findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Toast.makeText(MainActivity.this,"悬浮框",
                Toast.LENGTH_SHORT).show();
    }
});

3.2 SnackBar 可交互提醒控件.

该控件和Toast一样是用来提示用户的但是它增加了取消选项.也就是可交互的.但是他和Dialog也不同,他也会自动消失.

  • 修改MainActivity中悬浮按钮点击事件的处理代码
// 悬浮按钮的点击事件.
FloatingActionButton fab =
        (FloatingActionButton) findViewById(R.id.fab);
fab.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        // 使用SnackBar来提示.
        Snackbar.make(v,"Data to delete!!",Snackbar.LENGTH_SHORT)
                .setAction("Undo", new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        Toast.makeText(MainActivity.this,"悬浮框",
                                Toast.LENGTH_SHORT).show();
                    }
                })
                .show();
    }
});

效果图如下,可以看到有 "UNDO" 选项.

Paste_Image.png

3.3 CoordinatorLayout

CoordinatorLayout 可以认为是加强版本的的FrameLayout ,在普通情况下他和FrameLayout 用法基本一致. 但是他可以监听子控件的各种shi'jian事件,并自动帮我们做出是适当的行为.比如上面的的SnackBar 弹出的时候会覆盖FloatActionButton,如果使用CoordinatorLayout则会自动将SnackBar上移.

  • 我们修改MainActivity的布局文件
<!-- 屏幕中的内容
    将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/tb_actionbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="@color/colorPrimary"
        android:theme="@style/ThemeOverlay.AppCompat.Dark"
        app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
        />

    <!--
        添加悬浮按钮
     -->
    <android.support.design.widget.FloatingActionButton
        android:layout_width="wrap_content"
        android:id="@+id/fab"
        android:layout_gravity="bottom|end"
        android:layout_margin="16dp"
        android:src="@drawable/ic_done"
        app:elevation="8dp"
        android:layout_height="wrap_content"/>
</android.support.design.widget.CoordinatorLayout>

效果图 ,可以看到在SnackBar弹出时,FloatActionButton上移.

Paste_Image.png

4 卡片式布局

卡片式布局也是Material Design 中提出的一个概念,他可以让页面中的元素看起来像是子啊卡片中一样,并且可以拥有圆角和投影.

4.1 CardVeiw

CardView 是Google提供的一种卡片布局控件. 现在我们要实现展示图片.

  • build.gradle 文件中添加依赖.

    • 使用RecycleView实现网格布局.
    • 使用CardView实现每一个item的图片卡展示.
    • 由于我们使用的图片可能会很大因此使用Glide框架加载图片,他内部会进行优化处理,使用起来也很简单.Glide是一个功能强大的图片加载库.Github地址 : https://github.com/bumptech/glide.
// 添加RecycleView 依赖
compile 'com.android.support:recyclerview-v7:24.2.1'
// 添加CardView依赖
compile 'com.android.support:cardview-v7:24.2.1'
// 添加Glide依赖
compile 'com.github.bumptech.glide:glide:3.7.0'
  • 修改 activity_main.xml 文件.

    • 在内容区添加 RecyclerView ,用来展示图片.
    • AppBarLayout 来包裹 ToolBar 以及设置RecycleView的 app:layout_behavior="@string/appbar_scrolling_view_behavior" 属性来避免RecycleView将ToolBar覆盖.
    • app:layout_scrollFlags="scroll|enterAlways|snap" 用来设置在RecycleView滚动时ToolBar的响应.
      1. scroll : 表示RecycleView向上滑动时,ToolBar也会向上滚动实现隐藏
      2. enterAlways : RecycleView向下滑动,ToolBar也会向下滑动,重新显示.
      3. snap : 停止时根据当前显示的比例确定是显示还是隐藏.
      
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout
    android:id="@+id/dl_main"
    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.design.widget.CoordinatorLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <!--
            1. xmlns:app="http://schemas.android.com/apk/res-auto" 新增的命名控件,
               主要是为了兼容 Android 5.0 以前的设备.
            2. android:theme="@style/ThemeOverlay.AppCompat.Dark" 设置ToolBar主题.
                因为我们App使用的是 Light主题,因此文字是深色,再次让此控件单独使用此
                主题,效果是显示的文字是亮的.
            3. app:popupTheme="@style/ThemeOverlay.AppCompat.Light" 设置菜单主题色.
            4. android:layout_height="?attr/actionBarSize" 设置ToolBar的高度和
                ActionBar一样.
        -->
        <!-- 使用 AppBarLayout 包裹来解决覆盖问题
            1. app:layout_scrollFlags="scroll|enterAlways|snap"
               子啊子控件中使用这个数据来设置内部控件如何响应滚动事件.
               scroll : 表示RecycleView向上滑动时,ToolBar也会向上滚动实现隐藏
               enterAlways : RecycleView向下滑动,ToolBar也会向下滑动,重新显示.
               snap : 停止时根据当前显示的比例确定是显示还是隐藏.
         -->
        <android.support.design.widget.AppBarLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
            <android.support.v7.widget.Toolbar
                android:id="@+id/tb_actionbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                android:background="@color/colorPrimary"
                android:theme="@style/ThemeOverlay.AppCompat.Dark"
                app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
                app:layout_scrollFlags="scroll|enterAlways|snap"
                />
        </android.support.design.widget.AppBarLayout>

        <!--
            1. 添加RecycleView
         -->
        <android.support.v7.widget.RecyclerView
            android:id="@+id/rv_show"
            android:layout_width="match_parent"
            app:layout_behavior="@string/appbar_scrolling_view_behavior"
            android:layout_height="match_parent"/>


        <!--
            添加悬浮按钮
            1. app:elevation="8dp" 指定高度值(立体) 高度值也大投影就越大,
               投影效果也就越淡.
         -->
        <android.support.design.widget.FloatingActionButton
            android:layout_width="wrap_content"
            android:id="@+id/fab"
            android:layout_gravity="bottom|end"
            android:layout_margin="16dp"
            android:src="@drawable/ic_done"
            app:elevation="8dp"
            android:layout_height="wrap_content"/>
    </android.support.design.widget.CoordinatorLayout>

    <!--
        菜单中的内容
        1. android:layout_gravity="start" 该属性必须设置,
           它表示了菜单弹出的方向.
           start : 表示根据系统语言进行判断弹出方向.
           left  : 左侧弹出
           right : 右侧弹出.
        2. 设置菜单布局 app:menu="@menu/nav_menu"
        3. 设置上面的布局 app:headerLayout="@layout/header_layout"
     -->
    <android.support.design.widget.NavigationView
        android:layout_width="match_parent"
        android:id="@+id/nav_view"
        android:layout_gravity="start"
        app:menu="@menu/nav_menu"
        app:headerLayout="@layout/header_layout"
        android:layout_height="match_parent"/>


</android.support.v4.widget.DrawerLayout>
  • 创建 item_panda.xml 作为RecycleView的Item布局

    • 使用CardView作为跟布局实现卡片效果.
    • 使用 app:cardCornerRadius="4dp" 设置卡片圆角半径.
    • 使用 LinearLayout 设置子控件的位置.
<?xml version="1.0" encoding="utf-8"?>
<!--
    1. 使用 CardView作为跟布局.
    2. app:cardCornerRadius="4dp" 设置圆角半径.
    3. 使用一个LeanerLayout 来定位子控件位置
-->
<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_margin="5dp"
    app:cardCornerRadius="4dp"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">
    <LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <ImageView
            android:id="@+id/iv_pic"
            android:layout_width="match_parent"
            android:scaleType="centerCrop"
            android:layout_height="100dp"/>
        <TextView
            android:id="@+id/tv_pic"
            android:layout_gravity="center_horizontal"
            android:layout_margin="5dp"
            android:textSize="16dp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>
    </LinearLayout>
</android.support.v7.widget.CardView>
  • 创建自定义Adapter
public class PandaAdapter extends RecyclerView.Adapter<PandaAdapter.ViewHolder> {

    private Context mContext;
    private List<Panda> mPandaList;

    public PandaAdapter(List<Panda> list){
        mPandaList = list;
    }


    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if (mContext == null)
            mContext = parent.getContext();
        // 加载布局文件
        View view = LayoutInflater.from(mContext).inflate(R.layout.item_panda,parent,false);
        return new ViewHolder(view);
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        // 设置数据.
        Panda panda = mPandaList.get(position);
        holder.mTextView.setText(panda.getName());
        // 使用 Glide 加载图片,会进行内存优化.
        Glide.with(mContext).load(panda.getImageId()).into(holder.mImageView);
    }

    @Override
    public int getItemCount() {
        return mPandaList.size();
    }

    static class ViewHolder extends RecyclerView.ViewHolder{
        CardView mCardView;
        ImageView mImageView;
        TextView mTextView;

        public ViewHolder(View itemView) {
            super(itemView);
            mCardView = (CardView) itemView;
            mImageView = (ImageView) itemView.findViewById(R.id.iv_pic);
            mTextView = (TextView) itemView.findViewById(R.id.tv_pic);
        }
    }

}
  • Panda类
public class Panda {
    private static final String TAG = "Panda";
    private String name;
    private int imageId;
    public Panda(String name,int id){
        this.name = name;
        this.imageId = id;
    }

    public int getImageId() {
        return imageId;
    }

    public String getName() {
        return name;
    }
}
  • 在MainActivity 的 onCreate()中添加如下代码

    • 首先获取到RecycleView实例.
    • 创建网格布局管理器 GridLayoutManager .
    • 为RecycleView 设置布局管理器.
    • 设置适配器
// 加载图片
initPandas();
// 首先获取到RecycleView实例
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.rv_show);
// 创建网格布局管理器
GridLayoutManager layoutManager = new GridLayoutManager(this,2);
// 为RecycleView 设置布局管理器
recyclerView.setLayoutManager(layoutManager);
// 设置适配器
mAdapter = new PandaAdapter(mPandaList);
recyclerView.setAdapter(mAdapter);

效果图

Paste_Image.png

5 下拉刷新

5.1 SwipeRefreshLayout

SwipeRefreshLayout 是Google提供的实现下拉刷新的类.他是由 support-v4提供的.我们只需要将要实现下拉刷新的控件放到其中即可.

  • 使用SwipeRefreshLayout包裹RecycleView控件
 <!-- 下拉刷新 -->
 <android.support.v4.widget.SwipeRefreshLayout
     android:id="@+id/srfl"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     app:layout_behavior="@string/appbar_scrolling_view_behavior">
     <!--
          1. 添加RecycleView
      -->
     <android.support.v7.widget.RecyclerView
         android:id="@+id/rv_show"
         android:layout_width="match_parent"
         android:layout_height="match_parent"/>
 </android.support.v4.widget.SwipeRefreshLayout>
  • 在 MainActivity 的 onCreate() 方法中添加如下代码
// 实现下拉刷新
// 找到实例
mSwipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.srfl);
// 设置加载框颜色
mSwipeRefreshLayout.setColorSchemeResources(R.color.colorPrimary);
// 设置刷新监听
mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
    @Override
    public void onRefresh() {
        // 更新数据
        refreshPanda();
    }

    private void refreshPanda() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                // 延时 2 s
                SystemClock.sleep(2 * 1000);
                // 更新数据.
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        initPandas();
                        // 通知RecycleView数据变更.
                        mAdapter.notifyDataSetChanged();
                        // 停止刷新
                        mSwipeRefreshLayout.setRefreshing(false);
                    }
                });
            }
        }).start();
    }
});

继续优化

6. 可折叠式标题栏

Material Design 对标题栏的样式并没有限制在ActionBar原来的样式,因此我们完全可以定义一种新的样式.
利用CollapsingToolBarLayout我们可以实现可折叠的标题栏.先看效果图.

Android 上滑前 4.3
Android 上滑前 5.1
Android 上滑后 5.1

注意 : 由于5.0 之前是没有办法获取设置状态栏的因此,此处需要不同处理

6.1 CollapsingToolBarLayout

CollapsingToolBarLayout 是不能独立存在的他在设计的时候就被限定为只能作为AppBarLayout的子布局来使用.而AppBarLayout 又必须是CoordinateLayout的子布局,因此我们需要综合使用上面介绍的知识点.

  • 创建activity_panda.xml 布局文件,布局文件结构比较复杂,但是都有注释.

    • 使用 CoordinatorLayout 作为根跟布局,因为我们需要他的事件监听功能.
    • 标题部分的根布局使用 AppBarLayout 标题的的子布局使用了CollapsingToolbarLayout,它可以帮我们实现折叠功能.
    • 详情模块使用 NestedScrollView ,他和普通的ScrollView功能yi'zhi一致,只是增加了嵌套响应滚动事件的功能.内部使用了一个 CardView实现卡片效果.
    • 最后增加了一个悬浮按钮作为评论按钮.
<?xml version="1.0" encoding="utf-8"?>
<!--
    CoordinatorLayout 加强版的FrameLayout.
-->
<android.support.design.widget.CoordinatorLayout
    android:id="@+id/activity_panda"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:fitsSystemWindows="true"
    android:layout_height="match_parent"
    >
    <!--
        1. AppBarLayout 只能作为CoordinatorLayout的子布局
    -->
    <!-- 标题 -->
    <android.support.design.widget.AppBarLayout
        android:id="@+id/abl_panda"
        android:fitsSystemWindows="true"
        android:layout_width="match_parent"
        android:layout_height="250dp">
        <!--
            1. CollapsingToolbarLayout 只能作为AppBarLayout 的子布局
               因此需要使用AppBarLayout包装一层
            2. contentScrim 用来指定CollapsingToolBarLayout 折叠之后的背景色.
               CollapsingToolBarLayout 折叠之后就是一个ToolBar.
            3. exitUntilCollapsed 当CollapsingToolBarLayout随着滚动完成折叠
               之后就保留在界面上.不再移出屏幕.
        -->
        <android.support.design.widget.CollapsingToolbarLayout
            android:id="@+id/ctl_panda"
            android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
            app:contentScrim="?attr/colorPrimary"
            app:layout_scrollFlags="scroll|exitUntilCollapsed"
            android:layout_width="match_parent"
            android:fitsSystemWindows="true"
            android:layout_height="match_parent">
            <!-- 开始定义标题栏 -->
            <!--
                1. layout_collapseMode : 指定当前控件在CollapsingToolbarLayout
                   折叠过程中的模式.
                   1.1 parallax : 折叠过程总会有一定的错位现象,视觉效果很好.
                   1.2 pin : 折叠过程中位置始终保持不变.
            -->
            <ImageView
                android:layout_width="match_parent"
                android:id="@+id/iv_panda_title_pic"
                android:scaleType="centerCrop"
                app:layout_collapseMode="parallax"
                android:fitsSystemWindows="true"
                android:layout_height="match_parent"/>
            <android.support.v7.widget.Toolbar
                android:layout_width="match_parent"
                app:layout_collapseMode="pin"
                android:id="@+id/tb_panda_title_txt"
                android:layout_height="?attr/actionBarSize"/>
        </android.support.design.widget.CollapsingToolbarLayout>
    </android.support.design.widget.AppBarLayout>
    <!--
        1. NestedScrollView 在ScrollView的基础上增加了嵌套响应滚动事件的功能.
        2. NestedScrollView 内部只可以有一个直接子布局,因此可以用一LinearLayout包装.
    -->
    <!-- 详情 -->
    <android.support.v4.widget.NestedScrollView
        android:layout_width="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior"
        android:layout_height="match_parent">
        <LinearLayout
            android:layout_width="match_parent"
            android:orientation="vertical"
            android:layout_height="wrap_content">
            <android.support.v7.widget.CardView
                android:layout_width="match_parent"
                android:layout_marginTop="35dp"
                android:layout_marginLeft="15dp"
                android:layout_marginRight="15dp"
                android:layout_marginBottom="15dp"
                app:cardCornerRadius="4dp"
                android:layout_height="wrap_content">
                <TextView
                    android:layout_width="wrap_content"
                    android:id="@+id/tv_panda_text"
                    android:layout_margin="10dp"
                    android:layout_height="wrap_content"/>
            </android.support.v7.widget.CardView>

        </LinearLayout>
    </android.support.v4.widget.NestedScrollView>
    <!-- 评论 -->
    <android.support.design.widget.FloatingActionButton
        android:layout_width="wrap_content"
        android:layout_margin="16dp"
        android:id="@+id/fab_comment"
        android:src="@drawable/comment"
        app:layout_anchorGravity="bottom|end"
        android:layout_height="wrap_content"/>
</android.support.design.widget.CoordinatorLayout>

  • 创建 PandaActivity

    • 获取到传进来的资料数据.
    • 设置ActionBar 为 ToolBar.
    • 设置导航按钮为返回按钮.
    • 设置相关数据.
public class PandaActivity extends AppCompatActivity {

    // 熊猫名称
    public static final String PANDA_NAME = "panda_name";
    // 熊猫图片ID
    public static final String PANDA_IMAGE_ID = "panda_image_id";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_panda);
        // 获取数据.
        Intent intent = getIntent();
        String pandaName = intent.getStringExtra(PANDA_NAME);
        int pandaImageId = intent.getIntExtra(PANDA_IMAGE_ID,0);
        // 获取控件
        Toolbar toolbar = (Toolbar) findViewById(R.id.tb_panda_title_txt);
        CollapsingToolbarLayout collapsingToolbar =
                (CollapsingToolbarLayout) findViewById(R.id.ctl_panda);
        ImageView pandaImageView = (ImageView) findViewById(R.id.iv_panda_title_pic);
        TextView pandaTextView = (TextView) findViewById(R.id.tv_panda_text);
        // 设置 ActionBar
        setSupportActionBar(toolbar);
        // 设置显示返回导航按钮
        ActionBar actionBar = getSupportActionBar();
        if (actionBar != null)
            actionBar.setDisplayHomeAsUpEnabled(true);
        // 设置标题
        collapsingToolbar.setTitle(pandaName);
        // 设置图片
        Glide.with(this).load(pandaImageId).into(pandaImageView);
        // 设置内容
        String content = getratePandaContent(pandaName);
        pandaTextView.setText(content);
    }
    // 随机生成内容
    private String getratePandaContent(String pandaName) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < 200; i++) {
            sb.append(pandaName);
        }
        return sb.toString();
    }

    // 处理返回按钮点击事件
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()){
            case android.R.id.home:
                finish();
                return true;
        }
        return super.onOptionsItemSelected(item);
    }
}

  • 修改MainAdapter 为Item增加点击事件,修改之后就可以实现折叠功能了.
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    if (mContext == null)
        mContext = parent.getContext();
    // 加载布局文件
    View view = LayoutInflater.from(mContext).inflate(R.layout.item_panda,parent,false);
    final ViewHolder holder = new ViewHolder(view);

    // 添加点击事事件
    holder.mCardView.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            final int pos = holder.getAdapterPosition();
            Panda panda = mPandaList.get(pos);
            Intent intent = new Intent(mContext, PandaActivity.class);
            intent.putExtra(PandaActivity.PANDA_NAME,panda.getName());
            intent.putExtra(PandaActivity.PANDA_IMAGE_ID,panda.getImageId());
            mContext.startActivity(intent);
        }
    });
    return holder;
}

6.2 充分利用系统状态栏空间

在Android 5.0 之前我们无法对状态栏的颜色和背景进行操作. 在Android 5.0 之后的系统都是支持这个功能的.因此这里实现一个系统差异型的效果. 在Android 5.0 之后的系统中使用背景图和状态栏融合的模式.在之前的系统中使用普通模式.

android:fitsSystemWindows 属性可以实现系统状态栏和背景图片融合的效果.我们只需要将需要融合的控件以及他的所有的父控件的该属性设置为 true 就可以实现.

对于我们来说我们需要 ImageView 实现该效果那么就需要将,它和他的父控件的该属性都设置一下.也就是 ImageView,CollapsingToolbarLayout,AppBarLayout,CoordinateLayout .

  • 设置 fitsSystemWindows 属性
  • 设置状态栏透明.
    • res 下新建 values-v21 文件夹.
    • res/value-v21 下创建styles.xml 文件.
    <?xml version="1.0" encoding="utf-8"?>
    <resources>
        <!-- PandaActivity 主题 -->
        <style name="PandaActivityTheme" parent="AppTheme">
            <!-- 设置透明色,Android 5.0 之后加载这个 -->
            <item name="android:statusBarColor">@android:color/transparent</item>
        </style>
    </resources>
    
    • res/values/styles.xml 文件夹中增加如下代码
    <!-- PandaActivity 主题 -->
    <style name="PandaActivityTheme" parent="AppTheme">
        <!-- Android 5.0 之前的系统会加载这. -->
    </style>
    
    • AndroidManifest.xml 中修改PandaActivity配置
    <!-- 设置我们刚才定义的属性主题 -->
    <activity android:name=".PandaActivity"
        android:theme="@style/PandaActivityTheme">
    </activity>
    

大功告成

参考

  • 第一行代码(第2版)

    这本书真的很好.感谢郭霖前辈

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,544评论 25 707
  • ¥开启¥ 【iAPP实现进入界面执行逐一显】 〖2017-08-25 15:22:14〗 《//首先开一个线程,因...
    小菜c阅读 6,191评论 0 17
  • 滑动菜单可以说是Material Desgin中最常见的效果之一了,在许多著名的应用中,都有滑动菜单的功能...
    AndYMJ阅读 2,350评论 2 4
  • 大家都说美国的科技很发达,可坏人也很多。 布哥特,在小的时候,是好莱坞的大明星,可因为他进入的青春...
    许呦_阅读 350评论 0 0
  • 风火亿年摧顽石,落地成山显奇志, 千岳不比孤凌身,万峰难抵灵秀势。 登临小鲁夫子诗,踏巡东山帝王事, 峄阳早失孤桐...
    可有可无9527阅读 284评论 0 1