虽然现在 MaterialTest 中已经应用了非常多的 Material Design 效果,不过你会发现,界面上最主要的一块区域还处于空白状态。这块区域通常都是用来放置应用的主体内容的,我准备使用些精美的水果图片来填充这部分区域。

那么为了要让水果图片也能 Material 化,本节中我们将会学习如何实现卡片式布局的效果。卡片式布局也是 Material Design 中提出的一个新的概念,它可以让页面中的元素看起来就像在卡片中一样,并且还能拥有圆角和投影,下面我们就开始具体学习ー下。

12.5.1 MaterialCardView

MaterialCardView 是用于实现卡片式布局效果的重要控件,由Material 库提供。实际上,MaterialCardView 也是一个FrameLayout ,只是额外提供了圆角和阴影等效果,看上去会有立体的感觉。

我们先来看一下MaterialCardView 的基本用法吧,其实非常简单,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="wrap_content"xmlns:app="http://schemas.android.com/apk/res-auto"app:cardCornerRadius="4dp"android:elevation="5dp"><TextViewandroid:id="@+id/infoText"android:layout_width="match_parent"android:layout_height="wrap_content"/></com.google.android.material.card.MaterialCardView>

这里定义了一个 Cardview 布局,我们可以通过 app: cardCornerRadius 属性指定卡片圆角的弧度,数值越大,圆角的弧度也越大。另外还可以通过 app:elevation 属性指定卡片的高度高度值越大,投影范围也越大,但是投影效果越淡,高度值越小,投影范围也越小,但是投影效果越浓,这一点和 FloatingActionButton 是一致的。

然后我们在 MaterialCardView 布局中放置了一个 TextView,那么这个 TextView 就会显示在一张卡片当中了,MaterialCardView 的用法就是这么简单。

但是我们显然不可能在如此宽阔的一块空白区域内只放置一张卡片,为了能够充分利用屏幕的空间,这里我准备综合运用一下第4章中学到的知识,使用 RecyclerView 来填充 MaterialTest 项目的主界面部分。还记得之前实现过的水果列表效果吗?这次我们将升级一下,实现一个高配版的水果列表效果。

既然是要实现水果列表,那么首先肯定需要准备许多张水果图片,这里我从网上挑选了一些精美的水果图片,将它们复制到了项目当中。

然后由于我们还需要用到 Recycler View,因此必须在 app/build.gradle 文件中声明库的依赖:

dependencies {...implementation 'androidx.recyclerview:recyclerview:1.0.0'implementation 'com.github.bumptech.glide:glide:4.9.0'}

上诉声明的第二行是添加了Glide 库的依赖。Glide 是一个超级强大的开源图片加载库,它不仅可以用于加载本地图片,还可以加载网络图片、GIF 图片 甚至是本地视频。周重要的是,Glide 的用法非常简单,只需几行代码就能够轻松实现复杂的图片加载功能,因此这里我们准备用它来加载水果图片。Glide 的项目主页地址是:https://github.com/bumptech/glide

接下来开始具体的代码实现,修改 activity_main.xml 中的代码,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayoutxmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:id="@+id/drawerLayout"android:layout_width="match_parent"android:layout_height="match_parent"xmlns:app="http://schemas.android.com/apk/res-auto"tools:context=".MainActivity"><androidx.coordinatorlayout.widget.CoordinatorLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"><androidx.appcompat.widget.Toolbarandroid:id="@+id/toolbar"android:layout_width="match_parent"android:layout_height="?attr/actionBarSize"android:background="@color/colorPrimary"android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/><androidx.recyclerview.widget.RecyclerViewandroid:id="@+id/recyclerView"android:layout_width="match_parent"android:layout_height="match_parent"/><com.google.android.material.floatingactionbutton.FloatingActionButtonandroid: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/ic_done"app:elevation="8dp"/></androidx.coordinatorlayout.widget.CoordinatorLayout>...</androidx.drawerlayout.widget.DrawerLayout>

这里我们在 CoordinatorLayout 中添加了一个 RecyclerView,给它指定一个 id,然后将宽度和高度都设置为 match_parent,这样 RecyclerView 也就占满了整个布局的空间。

接着定义一个实体类 Fruit,代码如下所示:

class Fruit (val name:String,val imageId:Int)

Fruit 类中只有两个字段,name 表示水果的名字,imageId 表示水果对应图片的资源 id。

然后需要为 RecyclerView 的子项指定一个我们自定义的布局,在 layout 目录下新建 fruit_item.xml,代码如下所示:

<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="wrap_content"xmlns:app="http://schemas.android.com/apk/res-auto"app:cardCornerRadius="4dp"android:layout_margin="5dp"android:elevation="4dp"><LinearLayoutandroid:orientation="vertical"android:layout_width="match_parent"android:layout_height="wrap_content"><ImageViewandroid:id="@+id/fruitImage"android:layout_width="match_parent"android:layout_height="100dp"android:scaleType="centerCrop"/><TextViewandroid:id="@+id/fruitName"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center_horizontal"android:layout_margin="5dp"android:textSize="16sp"/></LinearLayout></com.google.android.material.card.MaterialCardView>

这里使用了 CardView 来作为子项的最外层布局,从而使得 RecyclerView 中的每个元素都是在卡片当中的。MaterialCardView 由于是一个 FrameLayout,因此它没有什么方便的定位方式,这里我们只好在 MaterialCardView 中再嵌套一个 LinearLayout,然后在 LinearLayout 中放置具体的内容。

内容倒也没有什么特殊的地方,就是定义了一个 ImageView 用于显示水果的图片,又定义了 TextView 用于显示水果的名称,并让 TextView 在水平方向上居中显示。注意在 ImageView 中我们使用了一个 scaleType 属性,这个属性可以指定图片的缩放模式。由于各张水果图片的长宽比例可能都不一致,为了让所有的图片都能填充满整个 ImageView,这里使用了 centerCrop 模式,它可以让图片保持原有比例填充满 ImageView,并将超出屏幕的部分裁剪掉。

接下来需要为 RecyclerView 准备一个适配器,新建 FruitAdapter 类,让这个适配器继承自 RecyclerView.Adapter,并将泛型指定为 FruitAdapter. ViewHolder,代码如下所示:

class FruitAdapter(val context:Context,val fruitList:List<Fruit>) : RecyclerView.Adapter<FruitAdapter.ViewHolder>() {class ViewHolder(view: View) : RecyclerView.ViewHolder(view){val fruitImage:ImageView = view.findViewById(R.id.fruitImage)val fruitName:TextView = view.findViewById(R.id.fruitName)}override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FruitAdapter.ViewHolder {val view = LayoutInflater.from(context).inflate(R.layout.fruit_item,parent,false)return ViewHolder(view)}override fun getItemCount(): Int = fruitList.sizeoverride fun onBindViewHolder(holder: FruitAdapter.ViewHolder, position: Int) {val fruit = fruitList[position]holder.fruitName.text = fruit.nameGlide.with(context).load(fruit.imageId).into(holder.fruitImage)}
}

上述代码相信你一定很熟悉,和我们在第 3 章中编写的 FruitAdapter 几乎一模一样。唯一需要注意的是,在 onBindViewHo lder() 方法中我们使用了 Glide 来加载水果图片。

那么这里就顺便来看一下 Glide 的用法吧,其实并没有太多好讲的,因为 Glide 的用法实在是太简单了。首先调用 Glide.with() 方法并传入一个 Context、Actlvity 或 Fragment 参数然后调用 load()方法去加载图片,可以是一个 URL 地址,也可以是一个本地路径,或者是一个资源id,最后调用 into()方法将图片设置到具体某一个 ImageView 中就可以了。

那么我们为什么要使用 Glide 而不是传统的设置图片方式呢?因为这次我从网上找的这些水果图片像素都非常高,如果不进行压缩就直接展示的话,很容易就会引起内存溢出。而使用 Glide 就完全不需要担心这回事,因为 Glide 在内部做了许多非常复杂的逻辑操作,其中就包括了图片压缩,我们只需要安心按照 Glide 的标准用法去加载图片就可以了。

这样我们就将 RecyclerView 的适配器也准备好了,最后修改 MainActivity 中的代码,如下所示:

class MainActivity : AppCompatActivity() {val fruits = mutableListOf(Fruit("Apple",R.drawable.apple),Fruit("Banana",R.drawable.banana),Fruit("Orange",R.drawable.orange),Fruit("Watermelon",R.drawable.watermelon),Fruit("Grape",R.drawable.grape),Fruit("Pineapple",R.drawable.pineapple),Fruit("Strawberry",R.drawable.strawberry),Fruit("Cherry",R.drawable.cherry),Fruit("Mango",R.drawable.mango))val fruitList = ArrayList<Fruit>()override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)...initFruits()val layoutManager = GridLayoutManager(this,2)recyclerView.layoutManager = layoutManagerval adapter = FruitAdapter(this,fruitList)recyclerView.adapter = adapter}private fun initFruits(){fruitList.clear()repeat(50){val index = (0 until fruits.size).random()fruitList.add(fruits[index])}}...
}

在 MainActivity 中我们首先定义了一个水果集合,集合里面存放了很多个 Fruit 的实例,每个实例都代表着一种水果。然后在 initFruits() 方法中,先是清空了一下 fruitList 中的数据,接着使用一个随机函数,从刚才定义的 Fruits 集合中随机挑选一个水果放入到 fruitList 当中这样每次打开程序看到的水果数据都会是不同的。另外,为了让界面上的数据多一些,这里使用了repeat 函数,随机挑选 50 个水果。

之后的用法就是 RecyclerView 的标准用法了,不过这里使用了 GridLayoutManager 这种布局方式。在第 4 章中我们已经学过了 LinearLayoutManager 和 StaggeredGridLayoutManager,现在终于将所有的布局方式都补齐了。GridLayoutManager 的用法也没有什么特别之处,它的构造函数接收两个参数,第一个是 Context,第二个是列数,这里我们希望每一行中会有两列数据。

现在重新运行一下程序,效果如图所示:

可以看到,精美的水果图片成功展示出来了。每个水果都是在一张单独的卡片当中的,并且还拥有圆角和投影,是不是非常美观?另外,由于我们是使用随机的方式来获取水果数据的,因此界面上会有一些重复的水果出现,这属于正常现象。

当你陶醉于当前精美的界面的时候,你是不是忽略了一个细节?哎呀,我们的 Toolbar 怎不见了!仔细观察一下原来是被 RecyclerView 给挡住了。这个问题又该怎么解决呢?这就需要借助到另外一个工具了——AppBarLayout。

12.5.2 AppBarLayout

首先我们来分析一下为什么 RecyclerView 会把 Toolbar 给遮挡住吧。其实并不难理解,由于 RecyclerView 和 Toolbar 都是放置在 CoordinatorLayout 中的,而前面已经说过,CoordinatorLayout 就是一个加强版的 FrameLayout,那么 FrameLayout 中的所有控件在不进行明确定位的情况下默认都会摆放在布局的左上角,从而也就产生了遮挡的现象。其实这已经不是你第一次遇到这种情况了,我们在 4.3.3 小节学习 FrameLayout  的时候就早已见识过了控件与控件之间遮挡的效果。

既然已经找到了问题的原因,那么该如何解决呢?传统情况下,使用偏移是唯一的解决办法,即让 RecyclerView 向下偏移一个 Toolbar 的高度,从而保证不会遮挡到 Toolbar。不过我们使用的并不是普通的 FrameLayout,而是 CoordinatorLayout,因此自然会有一些更加巧妙的解决办法。

这里我准备使用 Material 库中提供的另外一个工具—— AppBarLayout 。AppBarLayout 实际上是一个垂直方向的 LinearLayout,它在内部做了很多滚动事件的封装,并应用了一些 Material Design 的设计理念。

那么我们怎样使用 AppBarLayoutオ能解决前面的覆盖问题呢?其实只需要两步就可以了第一步将 Toolbar 嵌套到 AppBarLayout 中,第二步给 RecyclerView 指定一个布局行为。修改 activity_main.xml 中的代码,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayoutxmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:id="@+id/drawerLayout"android:layout_width="match_parent"android:layout_height="match_parent"xmlns:app="http://schemas.android.com/apk/res-auto"tools:context=".MainActivity"><androidx.coordinatorlayout.widget.CoordinatorLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"><com.google.android.material.appbar.AppBarLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"><androidx.appcompat.widget.Toolbarandroid:id="@+id/toolbar"android:layout_width="match_parent"android:layout_height="?attr/actionBarSize"android:background="@color/colorPrimary"android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/></com.google.android.material.appbar.AppBarLayout><androidx.recyclerview.widget.RecyclerViewandroid:id="@+id/recyclerView"android:layout_width="match_parent"android:layout_height="match_parent"app:layout_behavior="@string/appbar_scrolling_view_behavior"/><com.google.android.material.floatingactionbutton.FloatingActionButtonandroid: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/ic_done"app:elevation="8dp"/></androidx.coordinatorlayout.widget.CoordinatorLayout>...</androidx.drawerlayout.widget.DrawerLayout>

可以看到,布局文件并没有什么太大的变化。我们首先定义了一个 AppBarLayout,并将 Toolbar 放置在了 AppBarLayout 里面,然后在 RecyclerView 中使用 app:layout_behavior 属性指定了一个布局行为。其中 appbar_scrolling_view_behavior 这个字符串也是由 Material库提供的。

现在重新运行一下程序,你就会发现一切都正常了,如图所示。

虽说使用 AppBarLayout 已经成功解决了 RecyclerView 遮挡 Toolbar 的问题,但是刚才有提到过,说 AppBarLayout 中应用了一些 Material Design 的设计理念,好像从上面的例子完全体现不出来呀。事实上,当 RecyclerView 滚动的时候就已经将滚动事件都通知给 AppBarLayout 了,只是我们还没进行处理而已。那么下面就让我们来进一步优化,看看 AppBarLayout 到底能实现什么样的 Material Design 效果。

当 AppBarLayout 接收到滚动事件的时候,它内部的子控件其实是可以指定如何去影响这些事件的,通过 app:layout_scrollFlags 属性就能实现。修改 activity_main.xml 中的代码,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayoutxmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:id="@+id/drawerLayout"android:layout_width="match_parent"android:layout_height="match_parent"xmlns:app="http://schemas.android.com/apk/res-auto"tools:context=".MainActivity"><androidx.coordinatorlayout.widget.CoordinatorLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"><com.google.android.material.appbar.AppBarLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"><androidx.appcompat.widget.Toolbarandroid:id="@+id/toolbar"android:layout_width="match_parent"android:layout_height="?attr/actionBarSize"android:background="@color/colorPrimary"android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"app:popupTheme="@style/ThemeOverlay.AppCompat.Light"app:layout_scrollFlags="scroll|enterAlways|snap"/></com.google.android.material.appbar.AppBarLayout>...</androidx.coordinatorlayout.widget.CoordinatorLayout>...</androidx.drawerlayout.widget.DrawerLayout>

这里在 Toobar 中添加了一个 app:layout_scrollFlags 属性,并将这个属性的值指定成了 scroll | enterAlways | snap。其中,scroll 表示当 RecyclerView 向上滚动的时候,Toolbar 会跟着一起向上滚动并实现隐藏;enterAways 表示当 RecyclerView 向下滚动的时候,Toolbar 会跟着一起向下滚动并重新显示。snap 表示当 Toolbar 还没有完全隐藏或显示的时候,会根据当前滚动的距离,自动选择是隐藏还是显示。

我们要改动的就只有这一行代码而已,现在重新运行一下程序,并向上滚动 RecyclerView 效果如图所示。

可以看到,随着我们向上滚动 RecyclerView, Toolbar 竟然消失了,而向下滚动 RecyclerView, Toolbar 又会重新出现。这其实也是 Material Design 中的一项重要设计思想,因为当用户在向上滚动 RecyclerView 的时候,其注意力肯定是在 RecyclerView 的内容上面的,这个时候如果 Toolbar 还占据着屏幕空间,就会在一定程度上影响用户的阅读体验,而将 Toolbar 隐藏则可以让阅读体验达到最佳状态。当用户需要操作 Toolbar 上的功能时,只需要轻微向下滚动,Toolbar 就会重新出现。这种设计方式,既保证了用户的最佳阅读效果,又不影响任何功能上的操作,Material Design 考虑得就是这么细致人微。

当然了,像这种功能,如果是使用 Actionbar 的话,那就完全不可能实现了,To0bar 的出现为我们提供了更多的可能。

12.5--卡片式布局相关推荐

  1. 说说 Android 的 Material Design 设计(四)——卡片式布局

    我们使用 CardView 与 RecyclerView 来·实现一个各种猫的卡片式展示列表吧O(∩_∩)O~ 1 CardView 控件 1.1 引入依赖库 打开 app/build.gradle, ...

  2. Android Material Design 之 CardView卡片式布局

    文章目录 效果图 一.CardView是什么? 二.使用步骤 1.布局 2.属性 总结 效果图 一.CardView是什么? CardView是一个视图容器,继承自FrameLayout,CardVi ...

  3. java GUI开发中关于卡片式布局详细步骤讲解

    java GUI开发中关于卡片式布局详细步骤讲解 JFrame frame = new JFrame("调课");//首先要申明一个JFrame. JPanel p1 = new ...

  4. Android 学习之《第一行代码》第二版 笔记(二十三)Material Design 实战 —— 卡片式布局

    实现基础: Android 学习之<第一行代码>第二版 笔记(二十二)Material Design 实战 -- 悬浮按钮和可交互提示 卡片式布局 卡片式布局是 Materials Des ...

  5. 【Ratchet】卡片式布局

    卡片式布局在手机版的网站中很常见,这一点Ratchet框架中,这一点还做得不错的. 本手机版网页的开放前端框架的搭建,在<[Ratchet]Ratchet2.0.2的下载.配置与Hellowor ...

  6. Android开发学习之卡片式布局的简单实现

    GoogleNow是Android4.1全新推出的一款应用,它可以全面了解你的使用习惯,并为你提供现在或者未来可能用到的各种信息,GoogleNow提供的信息关联度较高,几乎是瞬间返回答案,总而言之, ...

  7. HTML卡片式布局源码,html5自适应卡片式设计动态加载整站源码_

    html5自适应卡片式设计动态加载整站源码 该模板是非常容易存活的,这样的程序很容易吸引访客点击,提升ip流量和pv是非常有利的,随意挂点联盟广告都能养活程序. 本套整站源码采使用现在非常流行的全屏自 ...

  8. MaterialDesign学习篇(七),CardView卡片式布局的使用

    什么是CardView CardView顾名思义就是一个卡片型的View,它是在Android5.0引入的一个控件,作为一个容器使用,它本身继承于FrameLayout,可以说它的使用和FrameLa ...

  9. android横向卡片式布局,创建卡片式布局  |  Android 开发者  |  Android Developers

    应用通常需要在样式相似的容器中显示数据.这些容器通常在列表中用于保存每项的信息.借助系统提供的 图 1. 卡片示例 添加依赖项 CardView 微件是 AndroidX 的一部分.如需在项目中使用该 ...

  10. android卡片式网格,CardView(卡片式布局)

    效果图 CardView是Android5.0之后出现的,用它可以实现上图所展现的圆角效果.阴影效果.实现方式直接看代码: 第一步: compile 'com.android.support:card ...

最新文章

  1. Actipro WPF Studio破解
  2. CVPR2018上关于目标检测(object detection)
  3. HDU 2564 词组缩写
  4. 赠书:一本书揭开 Spring Boot 技术内幕
  5. java并发的艺术-读书笔记-第八章常用的并发工具类
  6. 以贴吧和头条为例,为什么产品都有极速版和标准版
  7. Python基础(4):类
  8. 可以查到的资料和可以淘到的原件 DIY 四轴
  9. set和multiset容器简介
  10. css-net 中华版,使用C#代码选择CSS样式(ASP.net)
  11. 数据结构——二叉树的非递归算法
  12. js点击取消按钮关闭当前弹框_UI设计中“取消按钮”的分析详解
  13. php中的缓,php中的缓存机制解释
  14. 织梦自定义图片字段和缩略图一样_织梦图片集模型自定义图片字段调用
  15. js的数组和对象的多种复制和清空, 以及区分JS数组和对象的方法
  16. android 选座系统,android 影院选座
  17. 南大计算机课程,南京大学 计算机系统基础 课程实验 2018(PA0-1)
  18. 全面复盘Android开发者容易忽视的Backup功能
  19. 使用SSM为学校医务室开发一套管理系统
  20. OSPF中ABR与ASBR的区别?

热门文章

  1. EFR32MG21 with ADXL346
  2. java switch case 参数变量使用枚举
  3. 零死角玩转stm32中级篇2-IIC总线
  4. 硬核干货合集!500+篇Java干货技术文章整理|资源|书单|工具|面试指南|强烈建议打开!
  5. java Date与 double 互转
  6. 外地人,在上海报考驾照
  7. JSON对象转java对象 JSON数组转LIST数组
  8. 忆享科技聚焦|数字经济、网络安全、5.5G、数字火炬手……热点资讯一览
  9. 集合_java集合框架
  10. 如何管理计算机中文件,如何管理电脑文件