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

96
deniro
0.2 2018.09.22 19:59 字数 744

我们使用 CardView 与 RecyclerView 来·实现一个各种猫的卡片式展示列表吧O(∩_∩)O~

1 CardView 控件

1.1 引入依赖库

打开 app/build.gradle,添加依赖库:

dependencies {
    ...
    compile 'com.android.support:recyclerview-v7:24.2.1'
    compile 'com.android.support:cardview-v7:24.2.1'
    compile 'com.github.bumptech.glide:glide:4.8.0'
}

最后一行引入的 Glide,它是一个快速高效的 Android 图片加载库,注重于平滑的滚动 。 Glide 支持拉取,解码和展示视频快照,图片,和 GIF 动画 。

1.2 卡片式布局

CardView 控件可实现卡片式布局。它其实是一个 FrameLayout,只不过多了圆角与阴影功能。

布局:

<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:material="http://schemas.android.com/apk/res-auto"
    android:id="@+id/drawer"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

       ...

        <android.support.v7.widget.RecyclerView
            android:id="@+id/recycler_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>

        ...

</android.support.v4.widget.DrawerLayout>



我们在 DrawerLayout 中新增了 RecyclerView,并让它占满整个空间(宽与高都是 match_parent)。

1.3 “猫”实体类

public class Cat {
    //类型
    private String type;
    //图片资源 ID
    private int imgId;

    public Cat(String type, int imgId) {
        this.type = type;
        this.imgId = imgId;
    }

    public String getType() {
        return type;
    }

    public int getImgId() {
        return imgId;
    }
}

1.4 RecyclerView 的 item 布局

在 layout 目录下新建 cat_item.xml,内容为:

<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:material="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_margin="5dp"
    material:cardCornerRadius="4dp">

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        >
        <ImageView
            android:id="@+id/cat_image"
            android:layout_width="200dp"
            android:layout_height="200dp"
            android:scaleType="centerCrop"/>
        <TextView
            android:id="@+id/cat_type"
            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>

注意:这里事先已把猫的相关图片放置在 res/drawable 目录中。

因为每张图片的长宽比例不一样,所以我们把 android:scaleType 设置为 centerCrop,让图片按照原比例充满整个 ImageView,并裁剪超出的部分。

1.5 RecyclerView 适配器

public class CatAdapter extends RecyclerView.Adapter<CatAdapter.ViewHolder> {

    private Context context;

    private List<Cat> cats = new ArrayList<>();

    public CatAdapter(List<Cat> cats) {
        this.cats = cats;
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if (context == null) {//设置上下文环境
            context = parent.getContext();
        }
        View view = LayoutInflater.from(context).inflate(R.layout.cat_item, parent, false);
        return new ViewHolder(view);
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        Cat cat = cats.get(position);
        holder.type.setText(cat.getType());
        Glide.with(context).load(cat.getImgId()).into(holder.image);
    }

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

    static class ViewHolder extends RecyclerView.ViewHolder {
        CardView cardView;
        ImageView image;
        TextView type;


        public ViewHolder(View itemView) {
            super(itemView);
            cardView = (CardView) itemView;
            image = (ImageView) itemView.findViewById(R.id.cat_image);
            type = (TextView) itemView.findViewById(R.id.cat_type);
        }
    }
}
  • 在 onBindViewHolder 中使用 Glide 来加载图片。
  • Glide.with() 可传入 Context、Activity、FragmentActivity 或 Fragment,创建RequestManager 对象。
  • RequestManager .load() 用于加载图片,可以传入 File、resourceId、URI 等类型的参数,创建 DrawableRequestBuilder 对象。
  • DrawableRequestBuilder .into() 用于指定放置图片的 ImageView。
  • Glide 本身会对图片进行压缩,所以就算是很大的图片,也可以放心使用啦O(∩_∩)O~

1.6 活动类

public class MainActivity extends AppCompatActivity {

    private DrawerLayout drawerLayout;

    private Cat[] cats = {new Cat("布偶猫", R.drawable.cat1),
            new Cat("异国短毛猫", R.drawable.cat2),
            new Cat("波斯猫", R.drawable.cat3),
            new Cat("美国短毛猫", R.drawable.cat4),
            new Cat("挪威森林猫", R.drawable.cat5),
            new Cat("日本短尾猫", R.drawable.cat6),
            new Cat("俄罗斯蓝猫", R.drawable.cat7),
            new Cat("伯曼猫", R.drawable.cat8),
            new Cat("缅因猫", R.drawable.cat9),
            new Cat("埃及猫", R.drawable.cat10)};
    private List<Cat> catList = new ArrayList<>();


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

       ...

        initCats();

        RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
        int spanCount = 2;
        GridLayoutManager layoutManager = new GridLayoutManager(this, spanCount);
        recyclerView.setLayoutManager(layoutManager);
        CatAdapter adapter = new CatAdapter(catList);
        recyclerView.setAdapter(adapter);

    }

    private void initCats() {
        catList.clear();
        for (int i = 0; i < 100; i++) {//随机添加 100 只猫的信息
            Random random = new Random();
            int index = random.nextInt(cats.length);
            catList.add(cats[index]);
        }
    }

 ...
}
  • initCats() 在 cats 数组中随机挑选了 100 只猫。
  • 使用了 GridLayoutManager 布局,它接收两个参数:Context 与总列数。
效果

卡片式布局漂亮吧?O(∩_∩)O~

2 优化

2.1 解决顶部工具栏被遮挡问题

目前设计中的卡片式布局存在一个问题,就是会遮挡顶部工具栏。

传统做法是让 RecyclerView 向下偏移顶部工具栏的高度。

AppBarLayout 是 Design Support 库提供的一个垂直方向的 LinearLayout,它封装很多滚动事件。

修改布局文件:

...
<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" />

</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"
    material:layout_behavior="@string/appbar_scrolling_view_behavior"
    />
...
  • 使用 AppBarLayout 包裹 Toolbar。
  • 为 RecyclerView 的 material:layout_behavior 指定 appbar_scrolling_view_behavior 事件。

经过这两步修改,工具栏与卡片式布局就可以和谐共处啦O(∩_∩)O~

2.2 工具栏响应滚动事件

<android.support.v7.widget.Toolbar
    ...
    material:layout_scrollFlags="scroll|enterAlways|snap"
    />

我们在 Toolbar 中新增了 material:layout_scrollFlags 属性,并设置了三种场景中的显示策略。从左到右,场景依次为向上滚动时、向下滚动时和还未完全显示与隐藏时。

属性 说明
scroll 一起滚动,会被隐藏。
enterAlways 一起滚动,一直显示。
snap 根据滚动距离,来判断是显示还是隐藏。
效果

当用户向上滑动内容时,隐藏工具栏;当用户向上滑动屏幕时,再显示工具栏。这是 Material Design 中的一条重要设计,极大地提升了用户的阅读体验O(∩_∩)O~

Android 技术
Web note ad 1