12Material Design-卡片式布局

虽然这个时候的项目中已经使用了很多Material Design效果,但是在主页面上还是一片空白,这个时候就用一些水果图片来填充这个区域
为了让水果图片也能Material化,这次就学习如何实现卡片式布局的效果

CardView

这个就是用于实现卡片布局效果的控件,由v7库提供,实际上CardView也是一个FrameLayout,但是会额外提供圆型和阴影的效果,看上去会有立体的感觉

  1. 基本用法,
<?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来填充整个项目,就是之前的水果列表
  1. 这里要使用到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库的依赖,这是一个强大的图片加载库,用于加载本地的图片,或者是网络上的,只要一行代码就可以实现,这里使用它加载水果图片
  1. 修改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,宽度和高度都是全屏
  1. 定义一个实体类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
  1. 需要为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
  1. 接下来要给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就完全不懂担心这种事,而且内部还对图片进行了压缩
  1. 接下来修改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,第二个是列数,
  • 运行程序就可以看到

    《12Material Design-卡片式布局》 卡片式布局效果.png

    这里就可以看到这样比之前的非常美观了,但是有一个问题,Toolbar不见了,这个是怎么回事,原来是被RecyclerVIew给挡住了,这该怎么办呢?这个时候就该使用–AppBarLayout

AppBarLayout

  • 为什么RecyclerView把Toolbar给遮住了,这是因为Toolbar和RecyclerView都是放在了CoordinatorLayout中,而CoordinatorLayout是一个加强版的FrameLayout,而FrameLayout中所有的控件在不进行明确定位的时候,都会默认摆放在布局的左上角,这样就产生了遮挡的现象
  • 怎么解决呢,这个时候使用Design Support库中的另一个工具–AppBarLayout,而这个是一个垂直方向的LinearLayout,它在内部做了很多滚动事件的封装,并应用了Material Design的设计理念
  1. 怎么使用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库提供的,
  • 运行程序,一切正常

    《12Material Design-卡片式布局》 使用AppBarLayout解决遮挡Toolbar问题.png

  1. 上面的例子完全体现不出来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还没有完全隐藏或者显示的时候,会根据当前滚动距离,自动的选择隐藏还是显示

    《12Material Design-卡片式布局》 向上滚动就会自动隐藏Toolbar.png

    原文作者:山丘i
    原文地址: https://www.jianshu.com/p/2a5d2bd55799
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞