RecyclerView 实现复杂布局
大家都知道,很多App都有类似这样的布局:



那如果想实现前几行是第一张图片的布局,再几行是第二个的布局,后面的是第三种布局呢?或者说像淘宝界面一样 眼花缭乱的布局?
(阿里巴巴已经把淘宝等自家App使用的布局方案开源啦!原理也是通过 RecyclerView 实现的,想了解的可以参考这篇文章:https://mp.weixin.qq.com/s/BYtF_Kzy7OWePJRNpfoHWQ)
其实当 item 的布局方式不一样的时候,譬如想实现上面截图的布局时,需在 onCreateViewHolder 中 通过参数 viewType 判断布局类型,此 viewType 和通过 getItemViewType 中返回的类型所匹配,getItemViewType 比 onCreateViewHolder 早调用,可以在之前的文章中了解 RecyclerView Adapter 函数的调用顺序。
下面的代码,实现了如图所示的效果:

前三行是头像在左边,文字在右边,后面的都是头像在上面,文字在下面。
这里只列出了两种布局,目的是为了让大家理解这种方法即可。
首先是图片在左,文字在右 item 布局:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="1dp"
android:background="#add8e6">
<TextView
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:drawableStart="@mipmap/premeeting_login_focused"
android:gravity="center"/>
</FrameLayout>
然后是图片在上,文字在下的布局:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="1dp"
android:background="#add8e6">
<TextView
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:drawableTop="@mipmap/premeeting_login_focused"
android:gravity="center"/>
</FrameLayout>
如果是每个 item 的布局一样,例如上面举例的 前三行之后的item 都一样,但是item的列数不一样,可以通过 GridLayoutManager 来设置每个 item 有不同的列数,GridLayoutManager为我们提供了动态改变每个item所占列数的方法:
gridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
return gridManager.getSpanCount();
}
}
getSpanSize方法,返回值就表示当前item占多少列,例如如果我们列数设置的为3列,返回3就表示铺满,也就是和列表一样了。
如图所示,我们给RecyclerView设置一个列数为6的GridLayoutManager,然后再动态地为不同部位的item分别设置SpanSize为6(铺满)、3(1/2)、2(1/3)就行了。
在 Adapter 的 onAttachedToRecyclerView 方法中动态为不同position设置不同的SpanSize,下面是 Adapter 的代码:
/**
* Created by zhangyb on 2017/7/3.
*/
public class MultiGridRecycleAdapter extends RecyclerView.Adapter<MultiGridRecycleAdapter.MyViewHolder> {
public static final int TYPE_ITEM_ONE_LEFT = 0; //item一列,图片在左,文字在右
public static final int TYPE_ITEM_ONE_UP = 1; //item一列,图片在上,文字在下
public static final int TYPE_ITEM_TWO_UP = 2; //item两列,图片在上,文字在下
private List<String> mDataList;
private LayoutInflater mInflater;
public MultiGridRecycleAdapter(Context context, List<String> dataList) {
mDataList = dataList;
mInflater = LayoutInflater.from(context);
}
@Override
public MultiGridRecycleAdapter.MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view;
if (viewType == TYPE_ITEM_ONE_LEFT) {
view = mInflater.inflate(R.layout.item_picture_left, parent, false);
} else {
view = mInflater.inflate(R.layout.item_picture_up, parent, false);
}
return new MyViewHolder(view);
}
@Override
public void onBindViewHolder(MultiGridRecycleAdapter.MyViewHolder holder, int position) {
holder.mTextView.setText(mDataList.get(position));
}
@Override
public int getItemCount() {
return mDataList.size();
}
@Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);
RecyclerView.LayoutManager manager = recyclerView.getLayoutManager();
if (manager instanceof GridLayoutManager) {
final GridLayoutManager gridManager = ((GridLayoutManager) manager);
gridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
int type = getItemViewType(position);
switch (type) {
case TYPE_ITEM_ONE_LEFT:
case TYPE_ITEM_ONE_UP: //这两种方式都是一列的,所以返回6
return 6;
case TYPE_ITEM_TWO_UP: //两列,返回3
return 3;
default:
return 6;
}
}
});
}
}
@Override
public int getItemViewType(int position) {
if (position < 3) { //前三行显示 图片在左、文字在右布局
return TYPE_ITEM_ONE_LEFT;
} else if (position < 6) { //第 4、5、6 行显示 图片在上、文字在下布局
return TYPE_ITEM_ONE_UP;
} else { // 其他行显示 两列,图片在上、文字在下布局
return TYPE_ITEM_TWO_UP;
}
}
public static class MyViewHolder extends RecyclerView.ViewHolder {
TextView mTextView;
public MyViewHolder(View view) {
super(view);
mTextView = (TextView) view.findViewById(R.id.text);
}
}
}
/**
* Created by zhangyb on 2017/7/3.
*/
public class MultiGridRecycleAdapter extends RecyclerView.Adapter<MultiGridRecycleAdapter.MyViewHolder> {
public static final int TYPE_ITEM_ONE_LEFT = 0;
public static final int TYPE_ITEM_ONE_UP = 1;
public static final int TYPE_ITEM_TWO_UP = 2;
private List<String> mDataList;
private LayoutInflater mInflater;
public MultiGridRecycleAdapter(Context context, List<String> dataList) {
mDataList = dataList;
mInflater = LayoutInflater.from(context);
}
@Override
public MultiGridRecycleAdapter.MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view;
//根据 viewType 区分不同的 布局
if (viewType == TYPE_ITEM_ONE_LEFT) {
view = mInflater.inflate(R.layout.item_picture_left, parent, false);
} else {
view = mInflater.inflate(R.layout.item_picture_up, parent, false);
}
return new MyViewHolder(view);
}
@Override
public void onBindViewHolder(MultiGridRecycleAdapter.MyViewHolder holder, int position) {
holder.mTextView.setText(mDataList.get(position));
}
@Override
public int getItemCount() {
return mDataList.size();
}
//此函数在调用 RecyclerView.setAdapter 时调用
@Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);
RecyclerView.LayoutManager manager = recyclerView.getLayoutManager();
if (manager instanceof GridLayoutManager) {
GridLayoutManager gridManager = ((GridLayoutManager) manager);
gridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
int type = getItemViewType(position);
switch (type) {
case TYPE_ITEM_ONE_LEFT:
case TYPE_ITEM_ONE_UP:
return 6;
case TYPE_ITEM_TWO_UP:
return 3;
default:
return 6;
}
}
});
}
}
@Override
public int getItemViewType(int position) {
if (position < 3) { //前三行显示 图片在左、文字在右布局
return TYPE_ITEM_ONE_LEFT;
} else if (position < 6) { //第 4、5、6 行显示 图片在上、文字在下布局
return TYPE_ITEM_ONE_UP;
} else { // 其他行显示 两列,图片在上、文字在下布局
return TYPE_ITEM_TWO_UP;
}
}
public static class MyViewHolder extends RecyclerView.ViewHolder {
TextView mTextView;
public MyViewHolder(View view) {
super(view);
mTextView = (TextView) view.findViewById(R.id.text);
}
}
}
可以通过 getItemViewType 来设置不同的 item,并在 onCreateViewHolder 中加载不同的 layout,此方法还可以实现为界面添加 Header 和 Fooder:


实现上述的效果,就可以用上面的方法。
假设我们现在已经完成了RecyclerView的编写,忽然有个需求,需要在列表上加个HeaderView,此时我们该怎么办呢?
打开我们的Adapter,然后按照我们上述的原理,添加特殊的ViewType,然后修改代码完成。
这是比较常规的做法了,但是有个问题是,如果需要添加viewType,那么可能我们的Adapter需要修改的幅度就比较大了,比如getItemType、getItemCount、onBindViewHolder、onCreateViewHolder等,几乎所有的方法都要进行改变。
这样来看,出错率是非常高的。
况且一个项目中可能多个RecyclerView都需要在其列表中添加headerView。
这种情况,请参考:http://blog.csdn.net/lmj623565791/article/details/51854533
参考
在网上找的感觉比较好的文章,和这个不相关: