Android手把手教你实现卡片式瀑布流效果(RecyclerView+CardView,附源码)

卡片式的瀑布流效果是一种非常美观的UI设计,自从Android 5.0的 RecyclerView出现以后,实现瀑布流变得十分简单,本文将一步步带领读者去实现一个卡片式的瀑布流布局。

一、先上效果图

源码上传在git上上,下载完成后直接用Android Studio打开即可https://github.com/WangLaoSeng/waterfall

image

二、代码实现卡片式瀑布流

1. 涉及到要使用的类和控件

类或控件 介绍 作用
RecyclerView 5.0出现的类似于ListView的控件 用它的布局管理器实现两列“ListView”效果
StaggeredGridLayoutManager 交错布局管理器 实现两列“ListView”效果
RecyclerView.Adapter 数据适配器 与ListView的Adapter类似,写法上不同。
CardView 同样是5.0新出的控件 实现圆角效果,省去了自己写Drawble,这个可要可不要。
Fresco 图片开源框架 为了简化从网上下载图片的过程就使用了它做快速演示,如想了解Fresco的详情,请看本人另一篇博客:Fresco图片框架简介及使用(可做圆角图片)

2. 在Gradle中添加RecyclerView、Fresco和CardView需要的依赖

compile 'com.android.support:recyclerview-v7:24.2.0'
compile 'com.facebook.fresco:fresco:0.14.1'
compile 'com.android.support:cardview-v7:25.0.0'

3. 在Activity的布局文件中使用RecyclerView

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#F0F0F0"
    tools:context="com.testwaterfall.MainActivity"
    >

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recyclerview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scrollbars="none"
        ></android.support.v7.widget.RecyclerView>
</RelativeLayout>

4. 构建RecyclerView的子Item View,命名为recyclerview_item.xml,这个布局里面主要是一张明星的图片和明星的名字。(这里的嵌套层数有点多,不是个好案例,为了省事就这样写了。)

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:fresco="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_margin="10dp"
    android:background="#F0F0F0"
    android:orientation="vertical"
    android:padding="5dp"
    >

    <android.support.v7.widget.CardView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#FFFFFF"
        app:cardCornerRadius="5dp"
        >
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:paddingBottom="5dp"
            android:paddingLeft="6dp"
            android:paddingRight="6dp"
            android:paddingTop="10dp"
            android:orientation="vertical"
            >
            <com.facebook.drawee.view.SimpleDraweeView
                android:id="@+id/user_avatar"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                fresco:actualImageScaleType="centerCrop"
                />

            <TextView
                android:id="@+id/user_name"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:gravity="center"
                />
        </LinearLayout>
    </android.support.v7.widget.CardView>
</LinearLayout>

5. 编写RecyclerView的数据适配器WaterFallAdapter

public class WaterFallAdapter extends RecyclerView.Adapter {

    private Context mContext;
    private List<PersonCard> mData; //定义数据源

    //定义构造方法,默认传入上下文和数据源
    public WaterFallAdapter(Context context, List<PersonCard> data) {
        mContext = context;
        mData = data;
    }

    @Override  //将ItemView渲染进来,创建ViewHolder
    public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(mContext).inflate(R.layout.recyclerview_item, null);
        return new MyViewHolder(view);
    }

    @Override  //将数据源的数据绑定到相应控件上
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        MyViewHolder holder2 = (MyViewHolder) holder;
        PersonCard personCard = mData.get(position);
        Uri uri = Uri.parse(personCard.avatarUrl);
        holder2.userAvatar.setImageURI(uri);
        holder2.userAvatar.getLayoutParams().height = personCard.imgHeight; //从数据源中获取图片高度,动态设置到控件上
        holder2.userName.setText(personCard.name);
    }

    @Override
    public int getItemCount() {
        if (mData != null) {
            return mData.size();
        }
        return 0;
    }

    //定义自己的ViewHolder,将View的控件引用在成员变量上
    public class MyViewHolder extends RecyclerView.ViewHolder {
        public SimpleDraweeView userAvatar;
        public TextView userName;

        public MyViewHolder(View itemView) {
            super(itemView);
            userAvatar = (SimpleDraweeView) itemView.findViewById(R.id.user_avatar);
            userName = (TextView) itemView.findViewById(R.id.user_name);
        }
    }
}

6. 构建一个数据Model,明星,以便于传递数据。

public class PersonCard implements Serializable{
    public String avatarUrl; //明显头像的Url
    public String name;  //明显的名字
    public int imgHeight;  //头像图片的高度
}

7. 在Activity里面初始化界面和生成、绑定数据

public class MainActivity extends Activity {

    private RecyclerView mRecyclerView;
    private RecyclerView.LayoutManager mLayoutManager;
    private WaterFallAdapter mAdapter;

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

    private void init() {
        mRecyclerView = (RecyclerView) findViewById(R.id.recyclerview);
        //设置布局管理器为2列,纵向
        mLayoutManager = new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);
        mAdapter = new WaterFallAdapter(this, buildData());

        mRecyclerView.setLayoutManager(mLayoutManager);
        mRecyclerView.setAdapter(mAdapter);
    }

    //生成6个明星数据,这些Url地址都来源于网络
    private List<PersonCard> buildData() {

        String[] names = {"邓紫棋","范冰冰","杨幂","Angelababy","唐嫣","柳岩"};
        String[] imgUrs = {"https://ss0.bdstatic.com/94oJfD_bAAcT8t7mm9GUKT-xh_/timg?image&quality=100&size=b4000_4000&sec=1477122728&di=21924aeef8f7847a651fc8bf00a28f49&src=http://www.tengtv.com/file/upload/201609/18/232836341.jpg",
                "https://ss0.bdstatic.com/94oJfD_bAAcT8t7mm9GUKT-xh_/timg?image&quality=100&size=b4000_4000&sec=1477122795&di=f740bd484870f9bcb0cafe454a6465a2&src=http://tpic.home.news.cn/xhCloudNewsPic/xhpic1501/M08/28/06/wKhTlVfs1h2EBoQfAAAAAF479OI749.jpg",
                "https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=673651839,1464649612&fm=111&gp=0.jpg",
                "https://ss0.baidu.com/94o3dSag_xI4khGko9WTAnF6hhy/image/h%3D200/sign=fd90a83e900a304e4d22a7fae1c9a7c3/d01373f082025aafa480a2f1fcedab64034f1a5d.jpg",
                "https://ss1.bdstatic.com/70cFvXSh_Q1YnxGkpoWK1HF6hhy/it/u=1849074283,1272897972&fm=111&gp=0.jpg",
                "https://ss0.baidu.com/-Po3dSag_xI4khGko9WTAnF6hhy/image/h%3D200/sign=005560fc8b5494ee982208191df4e0e1/c2fdfc039245d68827b453e7a3c27d1ed21b243b.jpg",
        };

        List<PersonCard> list = new ArrayList<>();
        for(int i=0;i<6;i++) {
            PersonCard p = new PersonCard();
            p.avatarUrl = imgUrs[i];
            p.name = names[i];
            p.imgHeight = (i % 2)*100 + 400; //偶数和奇数的图片设置不同的高度,以到达错开的目的
            list.add(p);
        }

        return list;
    }

}

三、总结

瀑布流实现的关键有3点:

  • 使用RecyclerView和它的StaggeredGridLayoutManager布局管理器,

  • 其次在于列表中的Item View的高度并不一样,这样才能达到交错开来的效果

  • 子Item的布局,让两列之间,上下之间能有一定的“空隙”,这才能达成视觉上的效果。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,392评论 25 707
  • 内容抽屉菜单ListViewWebViewSwitchButton按钮点赞按钮进度条TabLayout图标下拉刷新...
    皇小弟阅读 46,700评论 22 664
  • 06年买的房,08年交付,装修入住刚好我退休。小区地处余杭,老余杭,小区很大,有5000多户。郊区楼盘如果物业不给...
    黄如阅读 218评论 0 0
  • 同大多数的年轻人一样,我也曾喜欢过一个人,喜欢到吃饭,睡覺,甚至梦里都是他。大家应该看到了,我说的这句话里有个...
    兔贝芮阅读 388评论 0 0
  • 在任何一段感情中,两个人的态度决定着能否长久的走下去,对于大部分女生而言,想要的安全感并不是多么优越的物质条件,而...
    佑夏安阅读 259评论 0 4