这里写目录标题
前言:
开发中离不开的RecyclerView,用好它让开发UI更加丝滑。通常在使用RecyclerView的时候,我们都会用到多布局,尤其是APP的首页。像那些大厂的首页,比如:京东,淘宝,美团,QQ音乐等。都不是简单的布局,有些涉及到嵌套问题。一旦使用了嵌套,会有各种问题,性能问题,滑动问题等。因此我们在开发中尽量避免多层嵌套。
当然本篇文章没有涉及到更复杂的布局使用,像列表中存在横向滑动,分页,嵌套viewpager,嵌套tablayout等,这些使用场景有同学做过优化的可以一起讨论交流。
一,基础使用
1.1 简单布局
写一个简单布局只有TextView,简单点,写代码的方式简单点。
public class TestAdapter extends RecyclerView.Adapter<TestAdapter.TestViewHolder> {
private List<String> mList = new ArrayList<>();
public TestAdapter(List<String> list) {
this.mList = list;
}
@NonNull
@Override
public TestViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_test, parent,false);
return new TestViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull TestViewHolder holder, int position) {
holder.textView.setText(mList.get(position));
}
@Override
public int getItemCount() {
return mList.size();
}
class TestViewHolder extends RecyclerView.ViewHolder {
private AppCompatTextView textView;
public TestViewHolder(@NonNull View itemView) {
super(itemView);
textView = itemView.findViewById(R.id.item_tv);
}
}
}
mRecyclerView=findViewById(R.id.activity_rv);
mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
List<String> list=new ArrayList<>();
list.add("学生");
list.add("老师");
list.add("家长");
list.add("同事");
TestAdapter testAdapter=new TestAdapter(list);
mRecyclerView.setAdapter(testAdapter);
代码很简单,但是需要注意的是一下两种布局加载方式。如果用了第二种,你会发现无法居中显示。
View view = LayoutInflater.from(context).inflate(R.layout.item_test, parent,false);
View view1=LayoutInflater.from(context).inflate(R.layout.item_test,null);
1.2 横向显示
mRecyclerView.setLayoutManager(new LinearLayoutManager(this,RecyclerView.HORIZONTAL,false));
1.3 网格显示
mRecyclerView.setLayoutManager(new GridLayoutManager(this,2));
1.4 网格显示升级版
GridLayoutManager gridLayoutManager = new GridLayoutManager(this, 4);
gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
// 显示的列数 = spanCount / spanSize ;
if (position % 5 == 4) {
//SpanSize 返回4 代表该行只显示1列 列数 = 4/4 =1
return 4;
} else {
//SpanSize 返回1代表该行显示4列 列数 = 4/1 =4
return 1;
}
}
});
二,网格布局升级版使用
如果要实现上图的效果,我们该如何去设计呢。首先需要分析上图一共有几种布局,再者由于是一维布局,我们可以采用非嵌套模式去实现。当然这里如果支持二维布局的横向滑动呀,分页滑动呀等,这种就得需要使用嵌套了,我们先不说嵌套。就是利用上文中介绍的setSpanSizeLookup方式去实现。
开始分析:上图布局种类,一共可以看成4中,当然第一个位置一般都是banner轮番图,在这里我们可以看成是一张图片。这四种布局显示类型分别是,1,2,4,5,也就是一行显示几个。我们知道在网格布局中,我们需要指定一行显示几个,比如:GridLayoutManager(this, 4),这里的4代表一行显示4个,但是我们要用setSpanSizeLookup方式去显示,怎么去计算呢,这里我找到了一个通用的做法。
通用方式:基数代表GridLayoutManager(this, 4)传入的值,这里是4
比如:基数4,可以显示几种布局呢,1,2,4
比如:基数5,可以显示几种布局呢,1,5
比如:基数6,可以显示几种布局呢,1,2,3,6
有没有发现规律,能显示几种布局,就看哪些数可以被基数整除了。
因此要实现上文中的1,2,4,5类型的布局,那就要找到被他们整除的数。当然这里是20了。
这里有同学就会问了,为什么是整数倍呢,因为上文getSpanSize方法返回的是整数。假如布局中有显示1,2,3,4,5的该怎么办呢,这种方式就不行了。
好了,我们开始写代码吧,实现上图效果。
2.1 第一部分页面
item_banner.xml内容
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:layout_width="match_parent"
android:layout_height="80dp"
android:background="#78D2DD" />
</LinearLayout>
item_icon.xml内容
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center">
<ImageView
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
android:layout_width="60dp"
android:layout_height="60dp"
android:background="#B66666" />
</LinearLayout>
item_title.xml内容
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="10dp"
android:textColor="#000000"
android:text="分类"
android:textSize="24sp" />
</LinearLayout>
item_content1.xml内容
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center">
<ImageView
android:layout_width="160dp"
android:layout_height="60dp"
android:layout_marginBottom="10dp"
android:background="#F33737" />
</LinearLayout>
item_content2.xml内容
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:gravity="center"
android:layout_height="wrap_content">
<ImageView
android:layout_width="80dp"
android:layout_height="80dp"
android:background="#CAC27B" />
</LinearLayout>
2.2 bean类和holder类
BaseItem类
public interface BaseItem {
int getType();
}
BannerItem类
public class BannerItem implements BaseItem {
@Override
public int getType() {
return ItemHolderFactory.BANNER_TYPE;
}
}
…更多省略
BaseHolder类
public abstract class BaseHolder extends RecyclerView.ViewHolder {
BaseHolder(View item) {
super(item);
}
}
BannerHolder 类
public class BannerHolder extends BaseHolder {
public BannerHolder(View item) {
super(item);
}
}
…更多省略
2.3 ItemHolderFactory 类
public class ItemHolderFactory {
public static final int BANNER_TYPE = 0;
public static final int TITLE_TYPE = 1;
public static final int ICON_TYPE = 2;
public static final int CONTENT1_TYPE = 4;
public static final int CONTENT2_TYPE = 5;
public static BaseHolder getItemHolder(ViewGroup parent, int type) {
switch (type) {
default:
case BANNER_TYPE:
return new BannerHolder(LayoutInflater
.from(parent.getContext()).inflate(R.layout.item_banner, parent, false));
case TITLE_TYPE:
return new TitleHolder(LayoutInflater
.from(parent.getContext()).inflate(R.layout.item_title, parent, false));
case ICON_TYPE:
return new IconHolder(LayoutInflater
.from(parent.getContext()).inflate(R.layout.item_icon, parent, false));
case CONTENT1_TYPE:
return new ContentHolder1(LayoutInflater
.from(parent.getContext()).inflate(R.layout.item_content1, parent, false));
case CONTENT2_TYPE:
return new ContentHolder2(LayoutInflater
.from(parent.getContext()).inflate(R.layout.item_content2, parent, false));
}
}
}
2.4 MultiViewAdapter 类
public class MultiViewAdapter extends RecyclerView.Adapter<BaseHolder> {
private List<BaseItem> mDataList;
public MultiViewAdapter(List<BaseItem> dataList) {
mDataList = dataList;
}
@NonNull
@Override
public BaseHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int type) {
return ItemHolderFactory.getItemHolder(viewGroup, type);
}
@Override
public void onBindViewHolder(@NonNull BaseHolder viewHolder, int i) {
}
@Override
public int getItemViewType(int position) {
//Get the type of item
return mDataList.get(position).getType();
}
@Override
public int getItemCount() {
return mDataList.size();
}
public void setSpanCount(GridLayoutManager layoutManager) {
layoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int i) {
int type = getItemViewType(i);
switch (type) {
default:
case ItemHolderFactory.BANNER_TYPE:
case ItemHolderFactory.TITLE_TYPE:
return 20;//一行显示1个item 20/20=1
case ItemHolderFactory.ICON_TYPE:
return 4;//一行显示5个item 20/4=5
case ItemHolderFactory.CONTENT1_TYPE:
return 10;//一行显示2个item 20/10=2
case ItemHolderFactory.CONTENT2_TYPE:
return 5;//一行显示4个item 20/5=4
}
}
});
}
}
2.5 MainActivity类
对于数据这一块,同学们需要理解一下,不要弄混。记着咱们的布局是一维的,也就是相当于只有一级分类。分类title和分类子内容是同级的,所以当我们传入一个title数据的时候,对应的就要传入要显示几个子内容。
List<BaseItem> list = new ArrayList<>();
//banner数据
list.add(new BannerItem());
//icon数据
for (int i = 0; i < 10; i++) {
list.add(new IconItem());
}
//content1数据
list.add(new ContentItem1());
list.add(new ContentItem1());
//title
list.add(new TitleItem());
//content2
for (int i=0;i<4;i++){
list.add(new ContentItem2());
}
//title
list.add(new TitleItem());
//content1
for (int i=0;i<4;i++){
list.add(new ContentItem1());
}
GridLayoutManager layoutManager = new GridLayoutManager(this, 20);
MultiViewAdapter adapter = new MultiViewAdapter(list);
adapter.setSpanCount(layoutManager);
recyclerView.setLayoutManager(layoutManager);
recyclerView.setAdapter(adapter);
最后的最后,我们看看效果图图
三,封装
一个项目中,对于多布局的使用场景不止一处。这里有个封装的很好的库,我们可以使用一下了。
implementation 'com.github.CymChad:BaseRecyclerViewAdapterHelper:3.0.2'
先来看一下实体类QuickMultipleEntity
public class QuickMultipleEntity implements MultiItemEntity {
public static final int BANNER_TYPE = 0;
public static final int TITLE_TYPE = 1;
public static final int ICON_TYPE = 2;
public static final int CONTENT1_TYPE = 4;
public static final int CONTENT2_TYPE = 5;
private int itemType;
private int spanSize;
public QuickMultipleEntity(int itemType, int spanSize) {
this.itemType = itemType;
this.spanSize = spanSize;
}
public int getSpanSize() {
return spanSize;
}
public void setSpanSize(int spanSize) {
this.spanSize = spanSize;
}
@Override
public int getItemType() {
return itemType;
}
}
再来看看adapter
public class MultiTypeAdapter extends BaseMultiItemQuickAdapter<QuickMultipleEntity, BaseViewHolder> {
public MultiTypeAdapter(List<QuickMultipleEntity> data) {
super(data);
addItemType(QuickMultipleEntity.BANNER_TYPE, R.layout.item_banner);
addItemType(QuickMultipleEntity.TITLE_TYPE, R.layout.item_title);
addItemType(QuickMultipleEntity.ICON_TYPE, R.layout.item_icon);
addItemType(QuickMultipleEntity.CONTENT1_TYPE, R.layout.item_content1);
addItemType(QuickMultipleEntity.CONTENT2_TYPE, R.layout.item_content2);
}
@Override
protected void convert(BaseViewHolder baseViewHolder, QuickMultipleEntity quickMultipleEntity) {
switch (baseViewHolder.getItemViewType()) {
case QuickMultipleEntity.BANNER_TYPE:
Log.e("MultiTypeAdapter", "convert: banner");
break;
case QuickMultipleEntity.TITLE_TYPE:
Log.e("MultiTypeAdapter", "convert: title");
break;
default:
break;
}
}
}
接下来就是数据源了
public class MainActivity extends AppCompatActivity {
private RecyclerView mRecyclerView;
private MultiTypeAdapter multipleItemAdapter;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
supportRequestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_main);
mRecyclerView = findViewById(R.id.activity_main_rv);
List<QuickMultipleEntity> data = getMultipleItemData();
multipleItemAdapter = new MultiTypeAdapter(data);
final GridLayoutManager manager = new GridLayoutManager(this, 20);
mRecyclerView.setLayoutManager(manager);
multipleItemAdapter.setGridSpanSizeLookup(new GridSpanSizeLookup() {
@Override
public int getSpanSize(GridLayoutManager gridLayoutManager, int viewType, int position) {
return data.get(position).getSpanSize();
}
});
mRecyclerView.setAdapter(multipleItemAdapter);
}
public static List<QuickMultipleEntity> getMultipleItemData() {
List<QuickMultipleEntity> list = new ArrayList<>();
//banner
list.add(new QuickMultipleEntity(QuickMultipleEntity.BANNER_TYPE, 20));
//icon
for (int i = 0; i < 10; i++) {
list.add(new QuickMultipleEntity(QuickMultipleEntity.ICON_TYPE, 4));
}
//content1
for (int i = 0; i < 2; i++) {
list.add(new QuickMultipleEntity(QuickMultipleEntity.CONTENT1_TYPE, 10));
}
//title
list.add(new QuickMultipleEntity(QuickMultipleEntity.TITLE_TYPE, 20));
//content2
for (int i = 0; i < 4; i++) {
list.add(new QuickMultipleEntity(QuickMultipleEntity.CONTENT2_TYPE, 5));
}
//title
list.add(new QuickMultipleEntity(QuickMultipleEntity.TITLE_TYPE, 20));
//content1
for (int i = 0; i < 4; i++) {
list.add(new QuickMultipleEntity(QuickMultipleEntity.CONTENT1_TYPE, 10));
}
return list;
}
}
实现效果和上图是一样的。
四,RecyclerView原理
https://blog.csdn.net/qq_29882585/article/details/108818849
https://juejin.cn/post/6844903661726859271
https://juejin.cn/post/6984974879296585764