卡片式的瀑布流效果是一种非常美观的UI设计,自从Android 5.0的 RecyclerView出现以后,实现瀑布流变得十分简单,本文将一步步带领读者去实现一个卡片式的瀑布流布局。
一、先上效果图
源码上传在git上上,下载完成后直接用Android Studio打开即可https://github.com/WangLaoSeng/waterfall
二、代码实现卡片式瀑布流
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的布局,让两列之间,上下之间能有一定的“空隙”,这才能达成视觉上的效果。