12Material Design-卡片式布局
2018-04-07 本文已影响290人
何惧l
虽然这个时候的项目中已经使用了很多Material Design效果,但是在主页面上还是一片空白,这个时候就用一些水果图片来填充这个区域
为了让水果图片也能Material化,这次就学习如何实现卡片式布局的效果
CardView
这个就是用于实现卡片布局效果的控件,由v7库提供,实际上CardView也是一个FrameLayout,但是会额外提供圆型和阴影的效果,看上去会有立体的感觉
- 基本用法,
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView 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"
android:elevation="5dp"
app:cardCornerRadius="4dp">
<TextView
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</android.support.v7.widget.CardView>
- 首先定义了CardView布局,通过app:cardCornerRadius属性指定卡片圆角的弧度,数值越大弧度越大,android:elevation属性指定卡片的高度,高度值越大,投影范围越大,、
- 这里为了演示,放了一个TextView,那么这个TextView就会显示在一张卡片当中了
- 下面要使用以前学过的知识,使用RecyclerView来填充整个项目,就是之前的水果列表
- 这里要使用到RecyclerView、CardView这几个控件,因此要在app/build.gradle文件中声明这些库的依赖
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:26.1.0'
implementation 'com.android.support.constraint:constraint-layout:1.0.2'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.1'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
compile 'com.android.support:design:26.1.0'
compile 'de.hdodenhof:circleimageview:2.1.0'
compile 'com.android.support:recyclerview-v7:26.1.0'
compile 'com.android.support:cardview-v7:26.1.0'
compile 'com.github.bumptech.glide:glide:3.7.0'
}
- 注意最后一行声明的,这里添加一个Glide库的依赖,这是一个强大的图片加载库,用于加载本地的图片,或者是网络上的,只要一行代码就可以实现,这里使用它加载水果图片
- 修改activity_main.xml中的代码
<?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"
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.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="@drawable/ic_done"
android:elevation="10dp"
/>
</android.support.design.widget.CoordinatorLayout>
...
</android.support.v4.widget.DrawerLayout>
- 这里主要添加一个RecyclerView,给它指定了一个id,宽度和高度都是全屏
- 定义一个实体类Fruit,代码:
package com.example.tool;
public class Fruit {
private String name;
private int imageId;
public Fruit(String name,int imageId) {
this.name = name;
this.imageId = imageId;
}
public String getName() {
return name;
}
public int getImageId() {
return imageId;
}
}
- 这里只有两个字段,一个是名字,一个是水果对应图片的资源id
- 需要为RecyclerView的子项指定一个自定义的布局,在layout目录下新建fruit_item文件
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView 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"
android:elevation="5dp"
android:layout_margin="5dp"
app:cardCornerRadius="4dp">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/fruit_image"
android:layout_width="match_parent"
android:layout_height="100dp"
android:scaleType="centerCrop"
/>
<TextView
android:id="@+id/fruit_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>
- 这里使用了CardView来作为子项的最外层的布局,这样就可以使RecyclerView中的每个元素都是在卡片中,由于CardView是一个FrameLayout,因此它没有什么方便的定位方式,这里嵌套一个LinearLayout,在这个里面放置具体的内容
- 在这个里面,放置了一个ImageView,有一个 android:scaleType属性,这个属性可以指定图片的缩放模式,这里使用centerCrop模式,它可以让图片保持原有比例填充ImageView
- 接下来要给RecyclerView准备一个适配器,新建一个FruitAdapter类,让这个适配器继承子RecyclerView.Adapter,并将泛型指定为FruitAdapter.ViewHolder,
package com.example.tool;
public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHolder>{
private Context mContext;
private List<Fruit> mFruitList;
// 获取到自定义的子项的每个控件
static class ViewHolder extends RecyclerView.ViewHolder{
CardView cardView;
ImageView imageView;
TextView textView;
public ViewHolder(View itemView) {
super(itemView);
cardView = (CardView)itemView;
imageView = (ImageView)itemView.findViewById(R.id.fruit_image);
textView = (TextView)itemView.findViewById(R.id.fruit_name);
}
}
public FruitAdapter(List<Fruit> fruitList){
mFruitList = fruitList;
}
@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 fruit = mFruitList.get(position);
holder.textView.setText(fruit.getName());
//
Glide.with(mContext).load(fruit.getImageId()).into(holder.imageView);
}
@Override
public int getItemCount() {
return mFruitList.size();
}
}
- 这里的代码和之前的几乎一模一样,唯一要注意的是 onBindViewHolder()方法中使用了Glide的方法加载水果图片
- 首先调用 Glide.with()方法传入一个COntext或Activity参数,然后调用load()方法去加载图片,可以是URL地址,也可以是本地的图片,或者是资源id,最后使用into()方法将图片设置到具体某一个ImageView中就可以了
- 如果不使用这种方式的话,要是照片的像素过高就会造成内存溢出,使用了Glide就完全不懂担心这种事,而且内部还对图片进行了压缩
- 接下来修改MainActivity中的代码就可以了
package com.example.tool;
public class MainActivity extends AppCompatActivity {
private DrawerLayout drawerLayout;
private Fruit[] fruits = {new Fruit("apple",R.drawable.apple),
new Fruit("mango",R.drawable.mango),
new Fruit("banana",R.drawable.banana),
new Fruit("orange",R.drawable.orange),
new Fruit("watermelon",R.drawable.watermelon),
new Fruit("pear",R.drawable.pear),
new Fruit("grape",R.drawable.grape),
new Fruit("pineapple",R.drawable.pineapple),
new Fruit("strawberry",R.drawable.strawberry)
};
private List<Fruit> fruitList = new ArrayList<>();
private FruitAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initFruits();
RecyclerView recyclerView = (RecyclerView)findViewById(R.id.recycler_view);
GridLayoutManager layoutManager = new GridLayoutManager(this,2);
recyclerView.setLayoutManager(layoutManager);
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]);
}
}
}
- 首先定义了数组,数组中放了多个Fruit的实例,每一个就代表一种水果
- 在initFruits()方法中,先把数组清空,使用随机函数,这样每次打开程序看到的水果数据都不同,
- 之后就是标准的RecyclerView用法了,不过这里使用了GridLayoutManager布局的方式,这个构造函数有两个参数,第一个是Context,第二个是列数,
-
运行程序就可以看到
卡片式布局效果.png
这里就可以看到这样比之前的非常美观了,但是有一个问题,Toolbar不见了,这个是怎么回事,原来是被RecyclerVIew给挡住了,这该怎么办呢?这个时候就该使用--AppBarLayout
AppBarLayout
- 为什么RecyclerView把Toolbar给遮住了,这是因为Toolbar和RecyclerView都是放在了CoordinatorLayout中,而CoordinatorLayout是一个加强版的FrameLayout,而FrameLayout中所有的控件在不进行明确定位的时候,都会默认摆放在布局的左上角,这样就产生了遮挡的现象
- 怎么解决呢,这个时候使用Design Support库中的另一个工具--AppBarLayout,而这个是一个垂直方向的LinearLayout,它在内部做了很多滚动事件的封装,并应用了Material Design的设计理念
- 怎么使用AppBarLayout解决覆盖的问题,其实只要两步就可以,
- 第一要把Toolbar嵌套到AppBarLayout中,
- 第二要给RecyclerView指定一个布局行为
- 修改activity_main.xml中的代码
<?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"
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.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.design.widget.AppBarLayout>
<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="@drawable/ic_done"
android:elevation="10dp"
/>
</android.support.design.widget.CoordinatorLayout>
...
</android.support.v4.widget.DrawerLayout>
- 首先定义了一个AppBarLayout,并将Toolbar放置到进去
- 在RecyclerView中使用app:layout_behavior属性指定一个布局行为,其中appbar_scrolling_view_behavior这个字符也是由Design Support库提供的,
-
运行程序,一切正常
使用AppBarLayout解决遮挡Toolbar问题.png
- 上面的例子完全体现不出来AppBarLayout的强大,事实上,当RecyclerView滚动的时候已将滚动事件都通知给了AppBarLayout了,当AppBarLayout接收到滚动事件的时候,它的内部的子控件其实是可以指定如何去影响这些事件的,通过app:layout_scrollFlags属性就能实现
- 修改activity_main.xml中的代码
<?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"
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.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"
app:layout_scrollFlags="scroll|enterAlways|snap"
/>
</android.support.design.widget.AppBarLayout>
...
</android.support.design.widget.CoordinatorLayout>
...
</android.support.v4.widget.DrawerLayout>
- 这里只是在Toolbar中添加了app:layout_scrollFlags属性,并将这个属性设置为scroll|enterAlways|snap
- scroll表示当RecysleView向上滚动的时候,Toolbar就会跟着向上滚动并实现隐藏
- enterAlways表示当RecysleView向下滚动的时候,Toolbar就会跟着向下滚动并重新显示
-
snap表示当Toolbar还没有完全隐藏或者显示的时候,会根据当前滚动距离,自动的选择隐藏还是显示
向上滚动就会自动隐藏Toolbar.png