卡片式布局也是MaterialsDesign中提出的一个新的概念,它能够让页面中的元素看起来就像在卡片中一样,并且还能拥有圆角和投影,下面咱们就开始详细学习一下。

Material-Design-实战-之第四弹-——-卡片布局以及灵动的标题栏(CardView-&-AppBarLayout)
Material-Design-实战-之第四弹-——-卡片布局以及灵动的标题栏(CardView-&-AppBarLayout)



##文章提要与总结

1. CardView(这儿用于作为recycleview的子项,用于显现生果)
    1.1 实际上,CardView也是一个FrameLayout,仅仅额外供给了圆角和阴影等作用,看上去会有立体的感觉;

    1.2 app:cardCornerRadius特点指定卡片圆角的弧度,数值越大,圆角的弧度也越大;
        app:elevation特点指定卡片的高度,
        高度值越大,投影规模也越大,可是投影作用越淡,
        高度值越小,投影规模也越小,可是投影作用越浓, FloatingActionButton同理。
    1.3 需求依靠: compile 'com.android.support:cardview-v7:25.3.1'
    本项目还需添加一个Glide库的依靠。
    compile 'com.github.bumptech.glide:glide:3.7.0'
    Glide是一个超级强大的图片加载库,它不仅能够用于加载本地图片,
    还能够加载网络图片、GIF图片、乃至是本地视频。
    最重要的是,Glide的用法十分简单,只需一行代码就能轻松完成杂乱的图片加载功用;
    1.4 在toolbar下面添加一个recycleview
        界说一个实体类Fruit,便利后边存取数据;
        为RecycleView的子项制定一个自界说布局(架构如下):
            <android.support.v7.widget.CardView
              <LinearLayout
                <ImageView/>
                <TextView/>
              </LinearLayout>
            </android.support.v7.widget.CardView>
        接下来需求为RecyclerView预备一个适配器,
            适配器中除了RecycleView的规划逻辑之外,这儿需求留意的是,
            在onBindViewHoIder()办法中运用Glide来加载生果图片。
            Glide的用法:
            首要调用Glide.with()办法并传入一个Context、Activity或Fragment参数;
            然后调用load()办法去加载图片,其参数能够是一个URL地址 或 本地途径 或 资源id;
            最终调用into()办法将图片设置到详细某一个ImageView中即可。
    1.5 在MainActivity中:
        初始化生果列表;
        实例化recyclerView ;
        newLayoutManager  & set;
        new & set adapter;
2.AppBarLayout
    2.1 将Toolbar嵌套到AppBarLayout中;
    2.2 给RecyclerView指定一个布局行为(app:layout_behavior)——appbar_scrolling_view_behavior
    2.3 在Toolbar中添加一个app:layout_scrollFlags特点,并其值指定成了scroll|enterAlways|snap。
        其中,
        scroll  表明当RecyclerView向上翻滚时,Toolbar会跟着一同向上翻滚并完成躲藏;
        enterAlways  表明当RecyclerView向下翻滚时,Toolbar会跟着一同向下翻滚并从头显现;
        snap  表明当Toolbar还没有彻底躲藏或显现时,会依据当时翻滚的间隔,主动选择是躲藏仍是显现。

Material-Design-实战-之第四弹-——-卡片布局以及灵动的标题栏(CardView-&-AppBarLayout)




#正文

####CardView

首要这儿预备用CardView来填充主题内容, CardView是用于完成卡片式布局作用的重要控件,由appcompat-v7库供给。 实际上,CardView也是一个FrameLayout,仅仅额外供给了圆角和阴影等作用,看上去会有立体的感觉。

CardView 的根本用法:

<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"
    app:cardCornerRadius="4dp">
    <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"/>
</android.support.v7.widget.CardView>

其中: app:cardCornerRadius特点指定卡片圆角的弧度,数值越大,圆角的弧度也越大; app:elevation特点指定卡片的高度, 高度值越大,投影规模也越大,可是投影作用越淡, 高度值越小,投影规模也越小,可是投影作用越浓, FloatingActionButton同理。

然后咱们在CardView布局中放置了一个TextView,这个TextView就会显现在一张卡片中了。

为充分利用屏幕的空间,咱们能够运用RecyclerView来填充MatenalTest项目的主界面部分。 这儿参考一下郭神的demo——完成生果列表,首要需求预备许多张生果图片:

Material-Design-实战-之第四弹-——-卡片布局以及灵动的标题栏(CardView-&-AppBarLayout)


然后在app/build.gradle文件中声明RecyclerView、CardView这几个控件对应的库的依靠: “` compile ‘com.android.support:recyclerview-v7:25.3.1’ compile ‘com.android.support:cardview-v7:25.3.1’ “`

留意这儿还添加了一个Glide库的依靠。compile 'com.github.bumptech.glide:glide:3.7.0' Glide是一个超级强大的图片加载库,它不仅能够用于加载本地图片,还能够加载网络图片、GIF图片、乃至是本地视频。最重要的是,Glide的用法十分简单,只需一行代码就能轻松完成杂乱的图片加载功用,因此这儿我 们预备用它来加载生果图片。 Glide的项目主页地址是:github.com/bumptech/gl…

接下来修正activity-main.xml,如下所示(在toolbar下面添加一个recycleview),

<?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.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"
            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:menu="@menu/nav_menu"
        app:headerLayout="@layout/nav_header">
    </android.support.design.widget.NavigationView>
</android.support.v4.widget.DrawerLayout>

接着界说一个实体类Fruit,便利后边存取数据:

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

类中就两个字段, name对应生果的姓名; imageId对应图片的资源id。

接下来需求为RecycleView的子项制定一个自界说布局。在layout目录下新建fruit_item.xml:

<?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"
    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,因此它没有什么便利的定位方法,这儿只好在CardView中再嵌套一个LinearLayout,然后在LinearLayout中放置详细的内容。

内容的话便是 界说了ImageView用于显现生果的图片, 界说了TextView用于显现生果的称号,并让TextView在水平方向上居中显现。

留意在ImageView中咱们运用了一个scaleType特点,这个特点能够指定图片的缩放形式。 由于各张生果图片的长宽比例可能都不共同,为了让一切的图片都能填充溢整个ImageView,这儿运用了centerCrop形式,它能够让图片保持原有比例填充溢ImageView,并将超出屏幕的部分裁剪掉。

接下来需求为RecyclerView预备一个适配器, 新建FruitAdapter类承继RecyclerView.Adapter,并将泛型指定为FruitAdapter.ViewHolder,代码如下(详细可见代码中注释):

Material-Design-实战-之第四弹-——-卡片布局以及灵动的标题栏(CardView-&-AppBarLayout)

public class FruitAdapter extends RecyclerView.Adapter<FruitAdapter.ViewHolder> {
    private Context mContext;
    private List<Fruit> mFruitList;
    //实例化子项布局各个view目标
    static class ViewHolder extends RecyclerView.ViewHolder{
        CardView cardView;
        ImageView fruitImage;
        TextView fruitName;
        public ViewHolder(View view){
            super(view);
            cardView = (CardView) view;
            fruitImage = (ImageView) view.findViewById(R.id.fruit_image);
            fruitName = (TextView) view.findViewById(R.id.fruit_name);
        }
    }
    public FruitAdapter(List<Fruit> fruitList){
        mFruitList = fruitList;
    }
    //加载子布局,将子项作为参数传给ViewHolder,在ViewHolder里边
    @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);//将子项作为参数传给ViewHolder,在ViewHolder里边面实例化子项中的各个目标
    }
    //set对应子项目标
    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        Fruit fruit = mFruitList.get(position);//get对应子项目标
        holder.fruitName.setText(fruit.getName());
        Glide.with(mContext).load(fruit.getImageId()).into(holder.fruitImage);
    }
    @Override
    public int getItemCount() {
        return mFruitList.size();
    }
}

除了RecycleView的规划逻辑之外,这儿需求留意的是,在onBindViewHoIder()办法中运用Glide来加载生果图片。

Glide的用法:

  • 首要调用Glide.with()办法并传入一个Context、Activity或Fragment参数;
  • 然后调用load()办法去加载图片,其参数能够是一个URL地址/本地途径/资源id;
  • 最终调用into()办法将图片设置到详细某一个ImageView中即可。

这儿运用Glide而不是传统的设置图片方法: 因这儿从网上找的这些生果图片像素都十分高,假如不进行紧缩直接展示,很容易就会引起内存溢出。 而运用Glide就彻底不需求担心这回事,由于Glide在内部做了许多十分杂乱的逻辑操作, 其中就包括了图片紧缩,只需求安心依照Glide的规范用法去加载图片就能够了。

这样RecyclerView的适配器便预备好了,最终修正MainActivity中的代码:

Material-Design-实战-之第四弹-——-卡片布局以及灵动的标题栏(CardView-&-AppBarLayout)
Material-Design-实战-之第四弹-——-卡片布局以及灵动的标题栏(CardView-&-AppBarLayout)
Material-Design-实战-之第四弹-——-卡片布局以及灵动的标题栏(CardView-&-AppBarLayout)

public class MainActivity extends AppCompatActivity {
    private DrawerLayout mDrawerLayout;
    //添加RecycleView后的数据和目标初始化
    private Fruit[] fruits = {new Fruit("Apple", R.drawable.apple),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),
                                new Fruit("Cherry", R.drawable.cherry),new Fruit("Mango", R.drawable.mango)};
    private List<Fruit> fruitList = new ArrayList<>();
    private FruitAdapter adapter;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        //滑动菜单 & 导航按钮
        mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
        NavigationView navView = (NavigationView) findViewById(R.id.nav_view);
        ActionBar actionBar = getSupportActionBar();
        if(actionBar != null){
            actionBar.setDisplayHomeAsUpEnabled(true);//让导航按钮显现出来
            actionBar.setHomeAsUpIndicator(R.drawable.ic_menu);//设置一个导航按钮图标
        }
        //滑动菜单布局交互设置
        navView.setCheckedItem(R.id.nav_call);//将Call菜单项设置为默许选中
        navView.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener(){
            @Override
            public boolean onNavigationItemSelected(@NonNull MenuItem item) {
                mDrawerLayout.closeDrawers();//关闭滑动菜单
                return true;
            }
        });
        //悬浮按钮点击事情
        FloatingActionButton fab = (FloatingActionButton)findViewById(R.id.fab);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
//                Toast.makeText(MainActivity.this, "FAB clickes", Toast.LENGTH_SHORT).show();
                //Snackbar
                Snackbar.make(v,"Data deleted", Snackbar.LENGTH_SHORT)
                        .setAction("Undo", new View.OnClickListener() {
                            @Override
                            public void onClick(View v) {
                                Toast.makeText(MainActivity.this, "Data restored",
                                        Toast.LENGTH_SHORT).show();
                            }
                        }).show();
            }
        });
        initFruits();
        //实例化
        RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
        //newLayoutManager  & set
        GridLayoutManager layoutManager = new GridLayoutManager(this, 2);
        recyclerView.setLayoutManager(layoutManager);
        //new & set adapter
        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);//nextInt()作用:发生[0,fruits.length)之间的int数
            fruitList.add(fruits[index]);
        }
    }
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.toolbar,menu);
        return true;
    }
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()){
            case android.R.id.home:
                mDrawerLayout.openDrawer(GravityCompat.START);
                break;
            case R.id.backup:
                Toast.makeText(this,"You clicked Backup" , Toast.LENGTH_SHORT).show();
                break;
            case R.id.delete:
                Toast.makeText(this,"You clicked Delete" , Toast.LENGTH_SHORT).show();
                break;
            case R.id.settings:
                Toast.makeText(this,"You clicked Settings" , Toast.LENGTH_SHORT).show();
                break;
            default:
        }
        return true;
    }
}

代码简析:

  • 在MainActivity中界说了一个数组,数组寄存多个Fruit的实例,每个实例代表一种生果;
  • 在initFruits()办法中,先清空fruitList中的数据,再运用一个随机函数,从刚才界说的Fruit数组中随机选择一个生果放入到fruitList傍边,这样每次翻开程序看到的生果数据都会是不同的。 另外,为了让界面上的数据多一些,这儿运用了一个循环,随机选择50个生果。
  • 之后是RecyclerView的逻辑,这儿运用GridLayoutManager布局方法。 GridLayoutManager的结构函数接收两个参数,第一个是Context,第二个是列数,这儿指定为2,表明每一行中会有两列数据。

运转作用如图:

Material-Design-实战-之第四弹-——-卡片布局以及灵动的标题栏(CardView-&-AppBarLayout)

可见Toolbar被挡住了,不急,接下来学习另外一个东西——AppBarLayout,完美处理这个问题。

####AppBarLayout

首要RecyclerView会把Toolbar给遮挡住的原因: 由于RecyclerView和Toolbar都是放置在CoordinatorLayout中的, 而前面已经说过,CoordinatorLayout便是一个加强版的FrameLayout, 而FrameLayout中的一切控件在不进行明确定位的状况下,默许都会摆放在布局的左上角,然后也就发生了遮挡的现象。
处理办法: 传统状况下,运用偏移是唯一的处理办法, 即让RecyclerView向下偏移一个Toolbar的高度,然后保证不会遮挡到Toolbar。 不过这儿运用的是DesignSupport库的CoordinatorLayout而不是FrameLayout,天然会有愈加奇妙的处理办法。

这儿预备运用DesignSupport库中供给的另外一个东西——AppBarLayout。 AppBarLayout实际上是一个笔直方向的LinearLayout,它在内部做了许多翻滚事情的封装,并应用了一MaterialDesign的规划理念。

接下来运用AppBarLayout两步处理前面的覆盖问题: 第一步将Toolbar嵌套到AppBarLayout中, 第二步给RecyclerView指定一个布局行为(app:layout_behavior)。 修正activity_main.xml:

Material-Design-实战-之第四弹-——-卡片布局以及灵动的标题栏(CardView-&-AppBarLayout)

<?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.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"
            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:menu="@menu/nav_menu"
        app:headerLayout="@layout/nav_header">
    </android.support.design.widget.NavigationView>
</android.support.v4.widget.DrawerLayout>

改动后布局文件并没有太大改变: 首要界说一个AppBarLayout,并将Toolbar放置在AppBarLayout里边; 然后在RecyclerView中运用app:layout_behavior特点指定一个布局行为。 其中appbar_scrolling_view_behavior这个字符串也是由DesignSupport库供给的。

从头运转一下程序,可见遮挡问题就此处理了:

Material-Design-实战-之第四弹-——-卡片布局以及灵动的标题栏(CardView-&-AppBarLayout)

至此AppBarLayout已成功处理RecyclerView遮挡Toolbar的问题,可是这儿还并没有表现AppBarLayout中应用的MaterialDesign规划理念,

其实,当RecyclerView翻滚的时分就便将翻滚事情都通知给AppBarLayout了 (记得刚刚加的app:layout_behavior=”@string/appbar_scrolling_view_behavior”吗,看一下这个字符串,望文生义应该能够看出些端倪,这儿能够先笼统理解为这个特点指定了的便是RecyclerView翻滚的时分做出的行为), 仅仅上面的代码还没进行处理罢了。

当AppBarLayout接收到翻滚事情的时分,它内部的子控件是能够指定怎么去影响这些事情的, 通过app:layout_scrollFlags特点就能完成。

下面进一步优化,加一个代码看看AppBarLayout的这个Material Design作用,修正activity-main.xml:

Material-Design-实战-之第四弹-——-卡片布局以及灵动的标题栏(CardView-&-AppBarLayout)

app:layout_scrollFlags="scroll|enterAlways|snap"

这儿在Toolbar中添加一个app:layout_scrollFlags特点,并其值指定成了scroll|enterAlways|snap。 其中, scroll表明当RecyclerView向上翻滚时,Toolbar会跟着一同向上翻滚并完成躲藏; enterAlways表明当RecyclerView向下翻滚时,Toolbar会跟着一同向下翻滚并从头显现; snap表明当Toolbar还没有彻底躲藏或显现时,会依据当时翻滚的间隔,主动选择是躲藏仍是显现。

Material-Design-实战-之第四弹-——-卡片布局以及灵动的标题栏(CardView-&-AppBarLayout)
这儿要改动的其实也就这一行代码罢了,从头运转一下程序,并向上翻滚RecyclerView,作用如图:
Material-Design-实战-之第四弹-——-卡片布局以及灵动的标题栏(CardView-&-AppBarLayout)
运转程序可见, 随着咱们 向上翻滚RecyclerView会Toolbar消失掉; 向下翻滚RecyclerView,Toolbar又会从头呈现; 翻滚到Toolbar的一半时松开手指,Toolbar又会依据当时翻滚的间隔状况,做出消失或者从头呈现的反应;

这其实也是MaterialDesign中的一项重要规划思想,由于当用户在向上翻滚RecyclerView的时分,其留意力肯定是在RecyclerView的内容上面的,这个时分假如Toolbar还占据着屏幕空间,就会在必定程度上影响用户的阅览体验,而将Toolbar躲藏则能够让阅览体验达到最佳状况。当用户需求操作Toolbar上的功用时,只需求细微向下翻滚,Toolbar就会从头呈现。 这种规划方法,既保证了用户的最佳阅览作用,又不影响任何功用上的操作,Material Design考虑得便是这么细致人微。 当然了,像这种功用,假如是运用ActionBar的话,那就彻底不可能完成了,TooIbar的呈现为咱们供给了更多的可能。