很多时候会遇到一种需求,列表里面有列表,像这种需求之前一般都是用多个列表控件互相嵌套来实现,但是这样很容易出现一些问题,例如滚动冲突、数据显示不全、多余的逻辑处理等。后来发现,一个recyclerview就可以实现列表嵌套的效果,这里需要用到recyclerview的多布局功能。
效果图:
recyclerview的多布局涉及到的主要方法是getItemViewType,作用是设置每个item要显示的布局类型。之前不了解的时候,都是直接用数学逻辑直接去计算,多少个position后显示什么布局,这种方式适合在逻辑简单的时候,但是一旦逻辑稍微有点复杂就果断不能用,可能会自己埋下深坑不说,还不好维护,所以这边把布局类型放在数据对象中。
1. 定义多布局对象的基类:
public class BaseMulDataModel {
protected int type;
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
}
type是该对象对应的布局类型。
2. recyclerview数据的显示放在ViewHolder中,定义Holder基类
public abstract class BaseMulViewHolder<T extends BaseMulDataModel> extends RecyclerView.ViewHolder {
public BaseMulViewHolder(View itemView) {
super(itemView);
}
protected abstract void bindData(T dataModel);
}
这里面多布局中可能涉及到的多个对象,所以基类中的对象类型使用泛型定义,必须是多布局对象基类的子类,这样在后面数据和控件绑定的时候比较方便。
3. 开始创建多布局适配器
public class MullayoutAdapter extends RecyclerView.Adapter<BaseMulViewHolder> {
/**
* 定义三种布局类型
*/
public static final int TYPE_ONE = 1;
public static final int TYPE_TWO = 2;
public static final int TYPE_THREE = 3;
/**
* 数据集合
*/
private List<BaseMulDataModel> mList;
@Override
public BaseMulViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
//根据不同的布局类型,设置创建相关的holder
switch (viewType) {
case TYPE_ONE:
return new ViewHolderOne(LayoutInflater.from(parent.getContext())
.inflate(R.layout.layout_holder1, parent, false));
case TYPE_TWO:
return new ViewHolderTwo(LayoutInflater.from(parent.getContext())
.inflate(R.layout.layout_holder2, parent, false));
case TYPE_THREE:
return new ViewHolderThree(LayoutInflater.from(parent.getContext())
.inflate(R.layout.layout_holder3, parent, false));
}
return null;
}
@Override
public void onBindViewHolder(BaseMulViewHolder holder, int position) {
//绑定数据
holder.bindData(mList.get(position));
}
@Override
public int getItemCount() {
return mList.size();
}
@Override
public int getItemViewType(int position) {
return mList.get(position).getType();
}
/**
* 设置数据
*
* @param list
*/
public void setDatas(List<BaseMulDataModel> list) {
mList = list;
notifyDataSetChanged();
}
public List<BaseMulDataModel> getDatas() {
return mList;
}
/**
* 设置第一个布局的数据
*/
class ViewHolderOne extends BaseMulViewHolder<OneModel> {
TextView textView;
public ViewHolderOne(View itemView) {
super(itemView);
textView = (TextView) itemView.findViewById(R.id.holder1_tv);
}
@Override
protected void bindData(OneModel dataModel) {
textView.setText(dataModel.getTitle());
}
}
/**
* 设置第二个布局的数据
*/
class ViewHolderTwo extends BaseMulViewHolder<TwoModel> {
ImageView imageView;
public ViewHolderTwo(View itemView) {
super(itemView);
imageView = (ImageView) itemView.findViewById(R.id.holder2_iv);
}
@Override
protected void bindData(TwoModel dataModel) {
imageView.setImageResource(dataModel.getRes());
}
}
/**
* 设置第三个布局的数据
*/
class ViewHolderThree extends BaseMulViewHolder<ThreeModel> {
TextView textView;
public ViewHolderThree(View itemView) {
super(itemView);
textView = (TextView) itemView.findViewById(R.id.holder3_tv);
}
@Override
protected void bindData(ThreeModel dataModel) {
textView.setText(dataModel.getNote());
}
}
}
首先这边涉及到布局类型:头部、内容列表、底部。定义三种类型
/**
* 定义三种布局类型
*/
public static final int TYPE_ONE = 1;
public static final int TYPE_TWO = 2;
public static final int TYPE_THREE = 3;
根据布局类型来创建对应的ViewHolder对象
public BaseMulViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
//根据不同的布局类型,设置创建相关的holder
switch (viewType) {
case TYPE_ONE:
return new ViewHolderOne(LayoutInflater.from(parent.getContext())
.inflate(R.layout.layout_holder1, parent, false));
case TYPE_TWO:
return new ViewHolderTwo(LayoutInflater.from(parent.getContext())
.inflate(R.layout.layout_holder2, parent, false));
case TYPE_THREE:
return new ViewHolderThree(LayoutInflater.from(parent.getContext())
.inflate(R.layout.layout_holder3, parent, false));
}
return null;
}
当然事先创建对应的Holder类:
/**
* 设置第一个布局的数据
*/
class ViewHolderOne extends BaseMulViewHolder<OneModel> {
TextView textView;
public ViewHolderOne(View itemView) {
super(itemView);
textView = (TextView) itemView.findViewById(R.id.holder1_tv);
}
@Override
protected void bindData(OneModel dataModel) {
textView.setText(dataModel.getTitle());
}
}
这边把泛型对象擦除,使用具体对象OneModel来作为当前的数据对象。OneModel是BaseMulDataModel的基类。
OneModel的定义:
public class OneModel extends BaseMulDataModel {
private String title;
public OneModel(String title, int type) {
this.title = title;
this.type = type;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
}
4. 进行数据处理
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
RecyclerView recycler = (RecyclerView) findViewById(R.id.recycler);
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
recycler.setLayoutManager(layoutManager);
final MullayoutAdapter adapter = new MullayoutAdapter();
recycler.setAdapter(adapter);
//数据处理
List<BaseMulDataModel> mList = new ArrayList<>();
for (int i = 0; i < 5; i++) {
mList.add(new OneModel("头部" + i, MullayoutAdapter.TYPE_ONE));
for (int j = 0; j < 3; j++) {
mList.add(new TwoModel(R.mipmap.ic_launcher, MullayoutAdapter.TYPE_TWO));
}
mList.add(new ThreeModel("底部" + i, MullayoutAdapter.TYPE_THREE));
}
adapter.setDatas(mList);
}
}
后台返回的数据一般不是我们想要的格式,所以自己进行数据的拆分处理,数据的处理方式很大程度上决定了代码编写的难易度。
这边的数据处理是把简单地需要显示的数据按顺序依次放入到数据集合list中,然后给每个对象设置type,定义它所需要的布局类型,数据的处理方式比较简单,但是能应付很多的场景。在购物车场景中,一般也是像示例一样,有头部、内容、底部。后台返回的数据可能是一个json对象包含了所有(头部、内容列表、底部),这边把他拆分成三部分,在依次放入集合中显示。