6、布局与UI组件(下):RecyclerView与Adapter、ListView与GridView、ViewPager与Fragment、Material Design组件入门

好,咱们接着聊布局和UI组件。上一章我们把基础布局和常用控件过了一遍,这一章要上点硬菜了——列表、滑动页、还有Material Design那套现代化的设计语言。这些东西在实际项目中几乎天天用,我敢说,你学会这章,基本就能应付80%的界面需求了。

本章核心脉络:从最经典的ListView,到更灵活的RecyclerView,再到页面滑动的ViewPager,最后用Material Design组件给UI“化妆”。说白了,就是让你知道怎么展示列表数据、怎么切换页面、怎么让界面好看又规范。

第6章 布局与UI组件(下)知识体系 UI组件进阶 列表组件 ListView / GridView RecyclerView + Adapter ViewHolder / LayoutManager 页面切换 ViewPager Fragment + PagerAdapter TabLayout联动 Material Design FloatingActionButton Snackbar / CardView AppBar / NavigationView 目标:掌握列表、滑动页、Material Design核心用法

6.1 从ListView到RecyclerView:列表的进化

先说说ListView。这玩意儿是Android早期就有的列表控件,用起来简单,但坑也不少。我记得刚入行那会儿,做一个联系人列表,直接用ListView加ArrayAdapter,数据一多就开始卡顿。后来才知道,ListView默认没有强制复用View,你得自己在getView里做优化。

说白了,ListView就像个老实人,你给它什么它显示什么,但不会主动帮你省内存。而RecyclerView呢?它是Google后来推出的“升级版”,强制你使用ViewHolder模式,还自带布局管理器,想横着滑、竖着滑、网格滑都行。

我的建议:新项目直接上RecyclerView,别犹豫。ListView只适合维护老项目。如果你还在用ListView写新功能,嗯,该升级了。

6.1.1 RecyclerView的基本使用

RecyclerView的使用分三步:定义布局、创建Adapter、绑定数据。我习惯把Adapter单独写成一个内部类,这样逻辑清晰。

// 1. 在布局中添加RecyclerView
<androidx.recyclerview.widget.RecyclerView
    android:id="@+id/recycler_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

// 2. 创建Adapter
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
    private List<String> mData;

    public MyAdapter(List<String> data) {
        this.mData = data;
    }

    @NonNull
    @Override
    public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.item_text, parent, false);
        return new ViewHolder(view);
    }

    @Override
    public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
        holder.textView.setText(mData.get(position));
    }

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

    static class ViewHolder extends RecyclerView.ViewHolder {
        TextView textView;
        ViewHolder(View itemView) {
            super(itemView);
            textView = itemView.findViewById(R.id.tv_text);
        }
    }
}

// 3. 在Activity中设置
RecyclerView recyclerView = findViewById(R.id.recycler_view);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(new MyAdapter(dataList));

你看,代码结构很清晰。onCreateViewHolder负责创建View,onBindViewHolder负责填充数据。这里有个细节:ViewHolder是必须的,它缓存了子View的引用,避免每次调用findViewById。我在项目中见过有人不用ViewHolder,结果列表滑动时一卡一卡的,后来改成ViewHolder,流畅度直接翻倍。

6.1.2 LayoutManager的三种模式

RecyclerView的灵活之处在于LayoutManager。你可以换着花样展示列表:

LayoutManager 效果 适用场景
LinearLayoutManager 垂直或水平线性列表 聊天记录、新闻列表
GridLayoutManager 网格布局 相册、商品展示
StaggeredGridLayoutManager 瀑布流布局 笔记应用、图片墙

举个例子,如果你要做个类似微信朋友圈的九宫格图片,用GridLayoutManager设置spanCount为3就行。我曾经做过一个电商App,商品列表用StaggeredGridLayoutManager,每个商品卡片高度不同,看起来特别自然。

注意:使用StaggeredGridLayoutManager时,如果item高度变化太大,可能会出现item错位。我曾经遇到过图片加载完成后高度突变,导致布局乱掉。解决方案是给图片设置固定宽高比,或者用Glide的override方法预占位。

6.2 GridView:网格列表的老前辈

GridView和ListView是同一时代的产物。它把数据展示成网格,每行可以有多列。用法和ListView几乎一样,也是用Adapter。不过说实话,现在有了RecyclerView的GridLayoutManager,GridView基本被取代了。

但如果你在维护老项目,还是得会。GridView有个坑:它默认不支持点击item的动画,而且嵌套在ScrollView里会出问题。我建议,除非项目强制要求,否则别用GridView,直接用RecyclerView加GridLayoutManager,省心。

6.3 ViewPager与Fragment:页面滑动切换

ViewPager是Android里实现左右滑动页面的利器。最常见的场景就是引导页、图片轮播、或者Tab切换。ViewPager需要配合PagerAdapter使用,而FragmentPagerAdapter和FragmentStatePagerAdapter是两种常用的实现。

6.3.1 基本用法

// 创建FragmentPagerAdapter
public class MyPagerAdapter extends FragmentPagerAdapter {
    private List<Fragment> fragmentList;

    public MyPagerAdapter(FragmentManager fm, List<Fragment> list) {
        super(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
        this.fragmentList = list;
    }

    @Override
    public Fragment getItem(int position) {
        return fragmentList.get(position);
    }

    @Override
    public int getCount() {
        return fragmentList.size();
    }
}

// 在Activity中设置
ViewPager viewPager = findViewById(R.id.view_pager);
viewPager.setAdapter(new MyPagerAdapter(getSupportFragmentManager(), fragments));
// 配合TabLayout
TabLayout tabLayout = findViewById(R.id.tab_layout);
tabLayout.setupWithViewPager(viewPager);

这里有个选择:FragmentPagerAdapter会把所有Fragment保存在内存中,适合页面少的情况;FragmentStatePagerAdapter会销毁不可见的Fragment,适合页面多的情况。我个人的经验是,超过4个页面就用FragmentStatePagerAdapter,否则内存容易爆。

避坑指南:我曾经在ViewPager嵌套Fragment时,遇到Fragment重叠的问题。原因是Fragment被重复添加了。解决方案是在FragmentPagerAdapter的getItem方法中不要每次都new Fragment,而是从集合中取。或者使用FragmentStatePagerAdapter,它会自动管理状态。

6.3.2 ViewPager2:更现代的替代

ViewPager2是ViewPager的升级版,基于RecyclerView实现。它解决了ViewPager的一些老问题,比如不支持RTL布局、不能竖直滑动等。用法也差不多,只是Adapter变成了RecyclerView.Adapter。

如果你是新项目,我强烈建议直接用ViewPager2。它更稳定,而且支持竖直滑动,做那种上下滑动的卡片效果特别方便。

6.4 Material Design组件入门

Material Design是Google推出的一套设计语言,说白了就是让Android App看起来更现代、更统一。Android提供了很多Material Design组件,咱们挑几个最常用的说说。

6.4.1 FloatingActionButton(悬浮按钮)

FAB就是那个圆形的、带图标的按钮,通常放在右下角。它用来触发主要操作,比如写邮件、添加新条目。

<com.google.android.material.floatingactionbutton.FloatingActionButton
    android:id="@+id/fab"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="bottom|end"
    android:src="@drawable/ic_add"
    app:backgroundTint="@color/primaryColor" />

FAB的点击事件和普通按钮一样。但要注意,FAB不要滥用,一个页面最多一个。我见过有人一个页面放三个FAB,那画面太美不敢看。

6.4.2 Snackbar:轻量级提示

Snackbar是Toast的升级版,它可以从底部弹出,还支持一个操作按钮。比如删除操作后,弹出一个“已删除,点击撤销”的Snackbar。

Snackbar.make(view, "数据已删除", Snackbar.LENGTH_LONG)
        .setAction("撤销", v -> {
            // 恢复数据
        })
        .show();

注意,Snackbar需要一个View作为父容器,通常传CoordinatorLayout。这样Snackbar才能正确显示在底部,并且不影响FAB的位置。

6.4.3 CardView:卡片式布局

CardView是一个带圆角和阴影的容器,用来展示独立的信息块。比如一条新闻、一个商品。

<com.google.android.material.card.MaterialCardView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:cardCornerRadius="8dp"
    app:cardElevation="4dp"
    app:strokeColor="@color/gray"
    app:strokeWidth="1dp">
    <!-- 内容 -->
</com.google.android.material.card.MaterialCardView>

CardView配合RecyclerView使用效果极佳。每个item都是一个卡片,看起来干净利落。我在项目中经常用CardView做列表项,用户反馈说界面看起来“很高级”。

6.4.4 AppBarLayout与CollapsingToolbarLayout

这两个组件用来实现可折叠的顶部栏。比如一个详情页,顶部有图片,向上滑动时图片慢慢缩小,最后变成普通的Toolbar。效果很炫酷,实现也不复杂。

<com.google.android.material.appbar.AppBarLayout
    android:layout_width="match_parent"
    android:layout_height="200dp">
    <com.google.android.material.appbar.CollapsingToolbarLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_scrollFlags="scroll|exitUntilCollapsed">
        <ImageView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:scaleType="centerCrop"
            app:layout_collapseMode="parallax" />
        <androidx.appcompat.widget.Toolbar
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            app:layout_collapseMode="pin" />
    </com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>

这里的关键是layout_scrollFlags和layout_collapseMode。scroll表示跟随滚动,exitUntilCollapsed表示折叠到最小高度后固定。parallax是视差效果,pin是固定住。嗯,这些参数多试几次就明白了。

6.5 实战建议:如何选择组件

学完这么多组件,你可能会问:什么时候用哪个?我总结了一个简单的选择逻辑:

  • 展示列表数据:首选RecyclerView,灵活高效。
  • 页面滑动切换:用ViewPager2,配合TabLayout。
  • 需要卡片效果:CardView + RecyclerView,绝配。
  • 需要悬浮操作按钮:FloatingActionButton,一个页面一个。
  • 需要折叠顶部栏:AppBarLayout + CollapsingToolbarLayout。

记住,组件是工具,不是目的。别为了用而用,合适才是最好的。我在项目中见过有人硬套Material Design,结果界面反而变得臃肿。保持简洁,用户看着舒服,代码也好维护。

最后一个小技巧:写RecyclerView时,记得在Adapter里实现setHasStableIds(true),并重写getItemId()。这样当数据变化时,RecyclerView能更高效地更新界面,避免闪烁。我踩过这个坑,数据刷新时整个列表闪了一下,用户体验极差。


公众号:蓝海资料掘金营,微信deep3321