一句代码搞定 RecycleView 侧滑菜单、添加头部底部、加载更多
2017-09-30 11:13
471 查看
在 动手打造史上最简单的 Recycleview 侧滑菜单 中,萌生了将这种方案封装为一个开源库的想法,旨在实现调用方式最简单,且又不失可定制性。本库最大的特点的是采用了 Glide 简洁明了的链式调用方式,一句代码即可添加侧滑菜单、头部底部等。
1.自定义侧滑菜单布局
2.添加头部、底部
3.轻松实现加载更多
4.设置 item 间距
5.多种 item 类型
6.支持 LinearLayout 及 GridLayout
7.一句代码实现所有功能
左侧滑菜单、右侧滑菜单、自定义菜单布局:
头部、多头部:
底部、多底部、加载更多:
第 1 步、在工程的 build.gradle 中添加:
2
3
4
5
6
第 2 步、在应用的 build.gradle 中添加:
2
3
4
下面通过简单案例演示如何在程序中使用 SlideAdapter,假设 item.xml 为:
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
数据类为:
2
3
4
5
6
7
8
9
10
数据为:
2
3
4
2
3
2
3
4
在 itemBind 中进行数据绑定及控件的事件监听,相当于 Adapter 中的 onBindViewHolder ,实现 ItemBind 时需传入数据类型:
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
2
3
4
5
2
3
4
5
2
3
4
5
上面调用的 item 方法为 :
2
3
不添加哪一侧,就把对应参数传入 0 即可。
菜单布局为 itemView 的一部分,所以对侧滑菜单的数据绑定及事件监听直接在 ItemBind 中进行即可。
比如侧滑菜单布局 menu.xml 为下:
2
3
4
5
6
7
8
9
10
11
12
对 rightMenu_Like 控件设置点击事件监听,就像对 item 中的普通控件设置一样:
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
2
3
4
5
6
7
假设头部布局 head.xml 为下:
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
对头部的数据绑定及事件监听在 HeaderBind 中实现:
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
2
3
4
5
6
7
假设底部布局 foot.xml 为下:
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
对头部的数据绑定及事件监听在 FooterBind 中实现:
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
onBottom 方法中的 footer 为最后一个底部,若未添加底部,则 footer 为空。其中 SlideAdapter 的 loadMore() 方法专门用来加载更多数据,需在主线程中调用。
虽然这部分并不难,但毕竟是 SlideAdapter 异于其他库的一大特点,简单介绍一下,直接看代码:
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
SlideAdapter 中以 Builder load(List data) 方法为入口,然后进入 Builder 内部链式调用一系列方法,将配置属性暂时记录在 Builder 中,最后通过 SlideAdapter into(RecyclerView recyclerView) 方法将这一系列配置属性传入到 SlideAdapter 的构造函数中使其配置生效。
本库基于一种非常简单的侧滑菜单实现方案:
在 item 布局外部添加一层可以滑动的 HorizontalScrollView ,菜单布局也置于其中
也就是说,本库要做的工作是: 自动在 item 布局外部添加一层可以滑动的 HorizontalScrollView 容器。我们要考虑的问题有两个:1.什么时候添加? 2.怎么添加?首先来看第一个问题,答案很简单,当然是在 onCreateViewHolder 中新建 item 布局时添加;怎么添加呢?可以提前写一个布局文件,准备好这层可以滑动的容器,比如:
2
3
4
5
6
7
8
9
10
11
12
13
14
其中 SlideLayout 继承自 HorizontalScrollView ,就是这层可以滑动的容器,我们只要把 item 布局塞到 LinearLayout 内部就可以了,怎么塞呢?很简单:
2
3
4
这样就成功的把 item 外层裹上可以滑动的 SlideLayout 了,如果要添加侧滑菜单,同理,像 item 布局一样把菜单布局也塞进去就行了,本库是这样实现的:
2
3
4
5
6
7
8
9
10
linearLayout 的 addView 方法是按添加顺序从左到右排列的,所以可以这样实现。其中有一个问题很明显,当没添加侧滑菜单时,仍然给 item 外层添加了 SlideLayout,这的确属于一个漏洞。
最后再说一下 SlideLayout 的实现,其实十分简单,主要就是控制它的滑动,比如当滑开一半多菜单时松手的话,调用其 smoothScrollTo 方法完全打开。还有就是控制菜单打开的唯一性,我自创的方案与主流的 QQ、微信都不同,感兴趣的可以查看动手打造史上最简单的
Recycleview 侧滑菜单 或阅读源码。
侧滑菜单的初始化包括两部分,一是根据传入的 float 类型数据,初始化侧滑菜单占屏幕宽度的比例,二是初始化侧滑菜单的滑动位置,比如有左侧侧滑菜单时,需要滑动到刚好隐藏掉左侧侧滑菜单的位置。
初始化时机于 onBindViewHolder 中,关键代码如下:
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
首先看代码的第 1 行,mSlideItems 中存放了所有类型的 item 的相关数据,包括 item 的 layoutId 、菜单的 layoutId 及其宽度比例。获取到此 postion 对应的 item 类型信息后,再根据宽度比例设置菜单的宽度即可。
怎么初始化 SlideLayout 的滑动位置呢?可以看到程序将侧滑菜单的宽度记录在 SlideLayout 中,有了侧滑菜单的具体宽度,就很容易初始化 SlideLayout 的滑动位置了,重写 SlideLayout 的 onLayout 方法:
2
3
4
5
LinearLayout 和 GridLayout 的兼容包括两部分,一是 item 之间的间距,即分割线在 LinearLayout 与 GridLayout 下都能合理分布;二是头部和底部的宽度,若为 GridLayout ,头部底部宽度应该与 RecycleView 宽度一致。
首先来看第一个问题,在 SlideAdapter 构造函数中是这样设置 item 之间间距的:
2
3
SlideItemDecoration 中重写了 getItemOffsets 方法:
2
3
4
5
6
7
8
9
10
代码很简单,如果是 LinearLayout,就把分割线添加在 item 的下方,如果是 GridLayout ,就把分割线添加在 item 的四周。其实严格来说,这不能叫做分割线,只能叫间距,这种情况下如果要设置“分割线”的颜色,就只能设置 RecycleView 的背景色来实现,当然你也可以添加自己的 ItemDecoration 实现更多分割线效果。
来看第二个问题,如何兼容 item、头部底部的宽度,同样在 SlideAdapter 的构造函数中,把 item 和头部底部的宽度先确定下来:
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
代码 2 行把头部底部的宽度确定了下来,值就是 RecycleView 的宽度。如果是 LinearLayout ,item 的宽度就等同于头部底部的宽度;如果是 GridLayout ,item 的宽度就要除以列数并减去 item 间距值。
把 item 、头部底部的宽度值都确定下来以后,在 onBindViewHolder 中直接设置宽度,就成功的实现兼容 LinearLayout 和 GridLayout 了。
多头部、多底部无非就是不同的 viewType , SlideAdapter 中规定 item 的 viewType 范围为 1-99 ,头部、底部对应 viewType 的范围为:
2
相信 100 种 viewType 足够使用了。在 getItemViewType 中确定对应范围:
2
3
4
5
6
7
8
9
10
11
12
其中 getHeaderNum、getFooterNum 方法为下:
2
3
4
5
6
7
其中 mHeaders 、mFooters 中分别存储着所有的头部、所有的底部信息,包括 layoutId 和高度比例。确定好 item 、头部及底部的 viewType 后,就可以在 onCreateViewHolder 中根据不同的 viewType 创建不同的 viewHolder 了:
2
3
4
5
6
7
8
9
10
其中的 isHeader、isFooter 方法很简单,根据 viewType 的范围判断即可:
2
3
4
5
6
7
ok,在成功创建对应的头部、底部后,还要注意的就是数据的移位,此时 onBindViewHolder 中提供的 position 不能直接用来索引 data 数据,应该减去头部的数量,比如在调用 ItemBind 接口方法时:
2
3
4
实现原理的部分就介绍这么多吧,其实到这里已经把本库的关键点都拿出来剖析说明了,剩下的比如加载更多等功能都比较简单,当然如果你感兴趣的话可以阅读源码,内容并不多,最后附上本库的所有文件:
这是我的第一个开源库,功力尚浅,如果你在使用过程中遇到了什么问题,随时可以向我反馈,我会第一时间回复并改正。如果你想在此库
的基础上添加自己的功能,欢迎 fork、欢迎 star 。
源码:
源码:https://github.com/yhaolpz/SlideAdapter
特性:
1.自定义侧滑菜单布局 2.添加头部、底部
3.轻松实现加载更多
4.设置 item 间距
5.多种 item 类型
6.支持 LinearLayout 及 GridLayout
7.一句代码实现所有功能
效果:
左侧滑菜单、右侧滑菜单、自定义菜单布局:头部、多头部:
底部、多底部、加载更多:
集成:
第 1 步、在工程的 build.gradle 中添加:allprojects { repositories { ... maven { url 'https://jitpack.io' } } }1
2
3
4
5
6
第 2 步、在应用的 build.gradle 中添加:
dependencies { compile 'com.github.yhaolpz:SlideAdapter:1.0.0' }1
2
3
4
使用:
下面通过简单案例演示如何在程序中使用 SlideAdapter,假设 item.xml 为:<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:background="#fff" > <TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:padding="16dp" android:textColor="#000" android:textSize="14sp" /> </LinearLayout>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
数据类为:
public class Bean { private String info; public Bean(String info) { this.info = info; } // 省略 get、set 方法... }1
2
3
4
5
6
7
8
9
10
数据为:
final List<Bean> data = new ArrayList<>(); for (int i = 0; i < 30; i++) { data.add(new Bean("我是第" + i + "个item")); }1
2
3
4
1.基本写法 :
SlideAdapter.load(data) //加载数据 .item(R.layout.item) //指定布局 .into(recyclerView); //填充到recyclerView中1
2
3
2.数据绑定及事件监听:
SlideAdapter.load(data) .item(R.layout.item) .bind(itemBind) //视图绑定 .into(recyclerView);1
2
3
4
在 itemBind 中进行数据绑定及控件的事件监听,相当于 Adapter 中的 onBindViewHolder ,实现 ItemBind 时需传入数据类型:
ItemBind itemBind = new ItemBind<Bean>() { @Override public void onBind(ItemView itemView, Bean data, int position) { itemView.setText(R.id.textView, data.getInfo()) .setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { //点击item } }) .setOnClickListener(R.id.textView, new View.OnClickListener() { @Override public void onClick(View view) { //点击textView } }); } }; 2561f1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
3.添加 item 间距:
SlideAdapter.load(data) .item(R.layout.item) .padding(2) //item间距 .bind(itemBind) .into(recyclerView);1
2
3
4
5
4.添加侧滑菜单:
SlideAdapter.load(data) .item(R.layout.item,0,0,R.layout.menu,0.35f) //添加右侧侧滑菜单,宽为屏幕宽度的 35% .padding(2) .bind(itemBind) .into(recyclerView);1
2
3
4
5
SlideAdapter.load(data) .item(R.layout.item,R.layout.menu,0.4f,0,0) //添加左侧侧滑菜单,宽为屏幕宽度的 40% .padding(2) .bind(itemBind) .into(recyclerView);1
2
3
4
5
SlideAdapter.load(data) .item(R.layout.item,R.layout.menu,0.4f,R.layout.menu,0.35) //添加左侧和右侧侧滑菜单 .padding(2) .bind(itemBind) .into(recyclerView);1
2
3
4
5
上面调用的 item 方法为 :
item (int itemLayoutId, int leftMenuLayoutId, float leftMenuRatio, int rightMenuLayoutId, float rightMenuRatio)1
2
3
不添加哪一侧,就把对应参数传入 0 即可。
5.侧滑菜单的数据绑定及事件监听:
菜单布局为 itemView 的一部分,所以对侧滑菜单的数据绑定及事件监听直接在 ItemBind 中进行即可。比如侧滑菜单布局 menu.xml 为下:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#e61313" android:gravity="center"> <ImageView android:id="@+id/rightMenu_Like" android:layout_width="30dp" android:layout_height="30dp" android:src="@drawable/like"/> </LinearLayout>1
2
3
4
5
6
7
8
9
10
11
12
对 rightMenu_Like 控件设置点击事件监听,就像对 item 中的普通控件设置一样:
ItemBind itemBind = new ItemBind<Bean>() { @Override public void onBind(ItemView itemView, Bean data, int position) { itemView.setOnClickListener(R.id.rightMenu_Like, new View.OnClickListener() { @Override public void onClick(View view) { //点击rightMenu_Like } }); } };1
2
3
4
5
6
7
8
9
10
11
6.多种 item 布局:
SlideAdapter.load(data) .item(R.layout.item,0,0,R.layout.menu,0.35) // 添加次序为 1 .item(R.layout.item) // 添加次序为 2 .type(new ItemType<Bean>() { @Override public int getItemOrder(Bean data, int position) { // 返回 item 添加次序 return position % 2 == 0 ? 1 : 2; } }) .padding(2) .bind(itemBind) .into(recyclerView);1
2
3
4
5
6
7
8
9
10
11
12
13
7.添加头部:
SlideAdapter.load(data) .item(R.layout.item) .header(R.layout.head,0.1f) //添加一个头部,高为屏幕高度的 10% .padding(2) .bind(itemBind) .into(recyclerView);1
2
3
4
5
6
SlideAdapter.load(data) .item(R.layout.item) .header(R.layout.head) //添加第1个头部 .header(R.layout.head2,0.1f) //添加第2个头部 .padding(2) .bind(itemBind) .into(recyclerView);1
2
3
4
5
6
7
8.头部的数据绑定及事件监听:
假设头部布局 head.xml 为下:<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#fff" android:gravity="center" > <TextView android:layout_gravity="center" android:id="@+id/headText" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="头部" android:textColor="#000" android:gravity="center" /> </LinearLayout>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
对头部的数据绑定及事件监听在 HeaderBind 中实现:
SlideAdapter.load(data) .item(R.layout.item) .header(R.layout.head,0.2f) //添加第1个头部 .header(R.layout.head2) //添加第2个头部 .bind(new HeaderBind() { @Override public void onBind(ItemView header, int order) { if (order == 1) { header.setText(R.id.headText, "我是第一个头部"); } ... } }) .padding(2) .bind(itemBind) .into(recyclerView);1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
9.添加底部:
SlideAdapter.load(data) .item(R.layout.item) .footer(R.layout.foot,0.1f) //添加一个底部,高为屏幕高度的 10% .padding(2) .bind(itemBind) .into(recyclerView);1
2
3
4
5
6
SlideAdapter.load(data) .item(R.layout.item) .footer(R.layout.foot) //添加第1个底部 .footer(R.layout.foot,0.1f) //添加第2个底部 .padding(2) .bind(itemBind) .into(recyclerView);1
2
3
4
5
6
7
10.底部的数据绑定及事件监听:
假设底部布局 foot.xml 为下:<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#fff" android:gravity="center" > <TextView android:layout_gravity="center" android:id="@+id/footerText" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="底部" android:textColor="#000" android:gravity="center" /> </LinearLayout>1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
对头部的数据绑定及事件监听在 FooterBind 中实现:
SlideAdapter.load(data) .item(R.layout.item) .footer(R.layout.foot) //添加第1个底部 .footer(R.layout.foot,0.1f) //添加第2个底部 .bind(new FooterBind() { @Override public void onBind(ItemView footer, int order) { if (order == 2) { footer.setText(R.id.footerText, "我是第2个底部"); } ... } }) .padding(2) .bind(itemBind) .into(recyclerView);1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
11.加载更多:
SlideAdapter.load(data) .item(R.layout.item) .footer(R.layout.foot,0.1f) .padding(2) .bind(itemBind) .listen(new BottomListener() { @Override public void onBottom(final ItemView footer, final SlideAdapter slideAdapter) { footer.setText(R.id.footerText, "正在加载,请稍后..."); List data2 = getMoreData(); slideAdapter.loadMore(data2); footer.setText(R.id.footerText, "加载完成"); } }) .into(recyclerView);1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
onBottom 方法中的 footer 为最后一个底部,若未添加底部,则 footer 为空。其中 SlideAdapter 的 loadMore() 方法专门用来加载更多数据,需在主线程中调用。
实现原理
1.如何实现链式调用
虽然这部分并不难,但毕竟是 SlideAdapter 异于其他库的一大特点,简单介绍一下,直接看代码:public class SlideAdapter extends RecyclerView.Adapter<ItemView> { private static Builder mBuilder = null; public static class Builder { List data; int itemPadding; //省略... Builder load(List data) { this.data = data; return this; } public Builder padding(int itemPadding) { this.itemPadding = itemPadding; return this; } //省略... public SlideAdapter into(RecyclerView recyclerView) { SlideAdapter adapter = new SlideAdapter(mBuilder, recyclerView); mBuilder = null; return adapter; } } public static Builder load(List data) { return getBuilder().load(data); } private static Builder getBuilder() { if (mBuilder == null) { mBuilder = new Builder(); } return mBuilder; } }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
SlideAdapter 中以 Builder load(List data) 方法为入口,然后进入 Builder 内部链式调用一系列方法,将配置属性暂时记录在 Builder 中,最后通过 SlideAdapter into(RecyclerView recyclerView) 方法将这一系列配置属性传入到 SlideAdapter 的构造函数中使其配置生效。
2.如何实现侧滑菜单
本库基于一种非常简单的侧滑菜单实现方案:在 item 布局外部添加一层可以滑动的 HorizontalScrollView ,菜单布局也置于其中
也就是说,本库要做的工作是: 自动在 item 布局外部添加一层可以滑动的 HorizontalScrollView 容器。我们要考虑的问题有两个:1.什么时候添加? 2.怎么添加?首先来看第一个问题,答案很简单,当然是在 onCreateViewHolder 中新建 item 布局时添加;怎么添加呢?可以提前写一个布局文件,准备好这层可以滑动的容器,比如:
<?xml version="1.0" encoding="utf-8"?> <com.wyh.slideAdapter.SlideLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/slideLayout" android:layout_width="wrap_content" android:layout_height="wrap_content"> <LinearLayout android:id="@+id/linearLayout" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" /> </com.wyh.slideAdapter.SlideLayout>1
2
3
4
5
6
7
8
9
10
11
12
13
14
其中 SlideLayout 继承自 HorizontalScrollView ,就是这层可以滑动的容器,我们只要把 item 布局塞到 LinearLayout 内部就可以了,怎么塞呢?很简单:
View itemView = LayoutInflater.from(context).inflate(R.layout.layout, parent, false);//外部容器 LinearLayout linearLayout = itemView.findViewById(R.id.linearLayout); View content = LayoutInflater.from(context).inflate(itemLayoutId, linearLayout, false);//item布局 linearLayout.addView(content);1
2
3
4
这样就成功的把 item 外层裹上可以滑动的 SlideLayout 了,如果要添加侧滑菜单,同理,像 item 布局一样把菜单布局也塞进去就行了,本库是这样实现的:
if (slideItem.leftMenuLayoutId != 0) {//添加左侧侧滑菜单 leftMenu = LayoutInflater.from(context).inflate(slideItem.leftMenuLayoutId, linearLayout, false); linearLayout.addView(leftMenu); } content = LayoutInflater.from(context).inflate(slideItem.itemLayoutId, linearLayout, false); linearLayout.addView(content); if (slideItem.rightMenuLayoutId != 0) {//添加右侧侧滑菜单 rightMenu = LayoutInflater.from(context).inflate(slideItem.rightMenuLayoutId, linearLayout, false); linearLayout.addView(rightMenu); }1
2
3
4
5
6
7
8
9
10
linearLayout 的 addView 方法是按添加顺序从左到右排列的,所以可以这样实现。其中有一个问题很明显,当没添加侧滑菜单时,仍然给 item 外层添加了 SlideLayout,这的确属于一个漏洞。
最后再说一下 SlideLayout 的实现,其实十分简单,主要就是控制它的滑动,比如当滑开一半多菜单时松手的话,调用其 smoothScrollTo 方法完全打开。还有就是控制菜单打开的唯一性,我自创的方案与主流的 QQ、微信都不同,感兴趣的可以查看动手打造史上最简单的
Recycleview 侧滑菜单 或阅读源码。
3.如何初始化侧滑菜单
侧滑菜单的初始化包括两部分,一是根据传入的 float 类型数据,初始化侧滑菜单占屏幕宽度的比例,二是初始化侧滑菜单的滑动位置,比如有左侧侧滑菜单时,需要滑动到刚好隐藏掉左侧侧滑菜单的位置。初始化时机于 onBindViewHolder 中,关键代码如下:
final SlideItem item = mSlideItems.get(getItemViewType(position) - 1); View rightMenu = holder.getRightMenu(); if (rightMenu != null) { LinearLayout.LayoutParams rightMenuParams = (LinearLayout.LayoutParams) rightMenu.getLayoutParams(); rightMenuParams.width = (int) (ScreenSize.w(holder.itemView.getContext()) * item.rightMenuRatio); rightMenu.setLayoutParams(rightMenuParams); ((SlideLayout) holder.getView(R.id.yhaolpz_slideLayout)).setRightMenuWidth(rightMenuParams.width); } View leftMenu = holder.getLeftMenu(); if (leftMenu != null) { LinearLayout.LayoutParams leftMenuParams = (LinearLayout.LayoutParams) leftMenu.getLayoutParams(); leftMenuParams.width = (int) (ScreenSize.w(holder.itemView.getContext()) * item.leftMenuRatio); leftMenu.setLayoutParams(leftMenuParams); holder.getView(R.id.yhaolpz_slideLayout).scrollTo(leftMenuParams.width, 0); ((SlideLayout) holder.getView(R.id.yhaolpz_slideLayout)).setLeftMenuWidth(leftMenuParams.width); }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
首先看代码的第 1 行,mSlideItems 中存放了所有类型的 item 的相关数据,包括 item 的 layoutId 、菜单的 layoutId 及其宽度比例。获取到此 postion 对应的 item 类型信息后,再根据宽度比例设置菜单的宽度即可。
怎么初始化 SlideLayout 的滑动位置呢?可以看到程序将侧滑菜单的宽度记录在 SlideLayout 中,有了侧滑菜单的具体宽度,就很容易初始化 SlideLayout 的滑动位置了,重写 SlideLayout 的 onLayout 方法:
@Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); scrollTo(mLeftMenuWidth, 0); }1
2
3
4
5
4.如何兼容 LinearLayout 和 GridLayout
LinearLayout 和 GridLayout 的兼容包括两部分,一是 item 之间的间距,即分割线在 LinearLayout 与 GridLayout 下都能合理分布;二是头部和底部的宽度,若为 GridLayout ,头部底部宽度应该与 RecycleView 宽度一致。首先来看第一个问题,在 SlideAdapter 构造函数中是这样设置 item 之间间距的:
if (mItemPadding > 0) { mRecycleView.addItemDecoration(new SlideItemDecoration(mItemPadding)); }1
2
3
SlideItemDecoration 中重写了 getItemOffsets 方法:
@Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { RecyclerView.LayoutManager layoutManager = parent.getLayoutManager(); if (layoutManager instanceof LinearLayoutManager) { outRect.set(0, 0, 0, mItemPadding); } if (layoutManager instanceof GridLayoutManager) { outRect.set(mItemPadding, mItemPadding, mItemPadding, mItemPadding); } }1
2
3
4
5
6
7
8
9
10
代码很简单,如果是 LinearLayout,就把分割线添加在 item 的下方,如果是 GridLayout ,就把分割线添加在 item 的四周。其实严格来说,这不能叫做分割线,只能叫间距,这种情况下如果要设置“分割线”的颜色,就只能设置 RecycleView 的背景色来实现,当然你也可以添加自己的 ItemDecoration 实现更多分割线效果。
来看第二个问题,如何兼容 item、头部底部的宽度,同样在 SlideAdapter 的构造函数中,把 item 和头部底部的宽度先确定下来:
RecyclerView.LayoutManager layoutManager = mRecycleView.getLayoutManager(); mHeadFootViewWidth = ScreenSize.w(mRecycleView.getContext()) - recyclerViewMargin - recyclerViewPadding; if (layoutManager instanceof LinearLayoutManager) { mItemViewWidth = mHeadFootViewWidth; } if (layoutManager instanceof GridLayoutManager) { ((GridLayoutManager) layoutManager).setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { @Override public int getSpanSize(int position) { return isHeader(getItemViewType(position)) || isFooter(getItemViewType(position)) ? ((GridLayoutManager) layoutManager).getSpanCount() : 1; } }); mItemViewWidth = (ScreenSize.w(mRecycleView.getContext()) - recyclerViewMargin - recyclerViewPadding - mItemPadding * ((GridLayoutManager) layoutManager).getSpanCount() * 2) / ((GridLayoutManager) layoutManager).getSpanCount(); mHeadFootViewWidth -= mItemPadding * 2; }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
代码 2 行把头部底部的宽度确定了下来,值就是 RecycleView 的宽度。如果是 LinearLayout ,item 的宽度就等同于头部底部的宽度;如果是 GridLayout ,item 的宽度就要除以列数并减去 item 间距值。
把 item 、头部底部的宽度值都确定下来以后,在 onBindViewHolder 中直接设置宽度,就成功的实现兼容 LinearLayout 和 GridLayout 了。
5.如何实现多头部、多底部
多头部、多底部无非就是不同的 viewType , SlideAdapter 中规定 item 的 viewType 范围为 1-99 ,头部、底部对应 viewType 的范围为:private static final int TYPE_HEADER_ORIGIN = 101; //头部:101~200 private static final int TYPE_FOOTER_ORIGIN = 201; //底部:201~1
2
相信 100 种 viewType 足够使用了。在 getItemViewType 中确定对应范围:
@Override public int getItemViewType(int position) { if (getHeaderNum() > 0 && position < getHeaderNum()) { return TYPE_HEADER_ORIGIN + position; } if (getFooterNum() > 0 && position >= getHeaderNum() + mData.size()) { return TYPE_FOOTER_ORIGIN + position - getHeaderNum() - mData.size(); } return mIItemType == null || mSlideItems.size() == 1 ? 1 : mIItemType.type( mData.get(position - getHeaderNum()), position - getHeaderNum()); }1
2
3
4
5
6
7
8
9
10
11
12
其中 getHeaderNum、getFooterNum 方法为下:
public int getHeaderNum() { return mHeaders == null ? 0 : mHeaders.size(); } public int getFooterNum() { return mFooters == null ? 0 : mFooters.size(); }1
2
3
4
5
6
7
其中 mHeaders 、mFooters 中分别存储着所有的头部、所有的底部信息,包括 layoutId 和高度比例。确定好 item 、头部及底部的 viewType 后,就可以在 onCreateViewHolder 中根据不同的 viewType 创建不同的 viewHolder 了:
@Override public ItemView onCreateViewHolder(ViewGroup parent, int viewType) { if (isHeader(viewType)) { return ItemView.create(parent.getContext(), parent, mHeaders.get(viewType - TYPE_HEADER_ORIGIN)); } if (isFooter(viewType)) { return ItemView.create(parent.getContext(), parent, mFooters.get(viewType - TYPE_FOOTER_ORIGIN)); } return ItemView.create(parent.getContext(), parent, mSlideItems.get(viewType - 1)); }1
2
3
4
5
6
7
8
9
10
其中的 isHeader、isFooter 方法很简单,根据 viewType 的范围判断即可:
private boolean isHeader(int viewType) { return viewType >= TYPE_HEADER_ORIGIN && viewType < TYPE_FOOTER_ORIGIN; } private boolean isFooter(int viewType) { return viewType >= TYPE_FOOTER_ORIGIN; }1
2
3
4
5
6
7
ok,在成功创建对应的头部、底部后,还要注意的就是数据的移位,此时 onBindViewHolder 中提供的 position 不能直接用来索引 data 数据,应该减去头部的数量,比如在调用 ItemBind 接口方法时:
if (mIItemBind != null) { mIItemBind.bind(holder, mData.get(position - getHeaderNum()), position - getHeaderNum()); }1
2
3
4
实现原理的部分就介绍这么多吧,其实到这里已经把本库的关键点都拿出来剖析说明了,剩下的比如加载更多等功能都比较简单,当然如果你感兴趣的话可以阅读源码,内容并不多,最后附上本库的所有文件:
总结
这是我的第一个开源库,功力尚浅,如果你在使用过程中遇到了什么问题,随时可以向我反馈,我会第一时间回复并改正。如果你想在此库的基础上添加自己的功能,欢迎 fork、欢迎 star 。
源码:
public static void ddd(String text) { Log.d("YHAO", text); } public static void eee(String text) { Log.e("YHAO", text); } RecyclerView mRecyclerView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_home); mRecyclerView = (RecyclerView) findViewById(R.id.recyclerView); init(); } private void init() { // mRecyclerView.setLayoutManager(new GridLayoutManager(this, 2)); // mRecyclerView.setLayoutManager(new LinearLayoutManager(this,LinearLayoutManager.HORIZONTAL,false)); RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerView); recyclerView.setLayoutManager(new LinearLayoutManager(this)); final List<Bean> data = new ArrayList<>(); for (int i = 0; i < 30; i++) { data.add(new Bean("我是第" + i + "个item")); } final List<Bean> data2 = new ArrayList<>(); for (int i = 0; i < 5; i++) { data2.add(new Bean("我是一个新item")); } ItemBind<Bean> itemBind = new ItemBind<Bean>() { @Override public void onBind(final ItemView itemView, Bean data, int position) { itemView.setText(R.id.textView, data.getInfo()) .setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Toast.makeText(HomeActivity.this, "click", Toast.LENGTH_SHORT).show(); } }) .setOnClickListener(R.id.textView, new View.OnClickListener() { @Override public void onClick(View view) { Toast.makeText(HomeActivity.this, "textView click", Toast.LENGTH_SHORT).show(); } }) .setOnClickListener(R.id.like, new View.OnClickListener() { @Override public void onClick(View view) { Toast.makeText(HomeActivity.this, "点击喜欢菜单", Toast.LENGTH_SHORT).show(); itemView.closeMenu(); } }); } }; SlideAdapter.load(data) .item(R.layout.item, 0, 0, R.layout.menu, 0.35f) .padding(1) .header(R.layout.head, 0.1f) .footer(R.layout.foot, 0.1f) .bind(itemBind) .bind(new HeaderBind() { @Override public void onBind(ItemView header, int order) { header.setOnClickListener(R.id.headText, new View.OnClickListener() { @Override public void onClick(View view) { Toast.makeText(HomeActivity.this, "head click", Toast.LENGTH_SHORT).show(); } }); } }) .bind(new FooterBind() { @Override public void onBind(ItemView footer, int order) { footer.setOnClickListener(R.id.footerText, new View.OnClickListener() { @Override public void onClick(View view) { Toast.makeText(HomeActivity.this, "foot click", Toast.LENGTH_SHORT).show(); } }); } }) .listen(new BottomListener() { @Override public void onBottom(final ItemView footer, final SlideAdapter slideAdapter) { footer.setText(R.id.footerText, "正在加载,请稍后..."); new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } runOnUiThread(new Runnable() { @Override public void run() { slideAdapter.loadMore(data2); footer.setText(R.id.footerText, "正在加载"); } }); } }).start(); } }) .into(mRecyclerView); } }
源码:https://github.com/yhaolpz/SlideAdapter
相关文章推荐
- 一句代码搞定 RecycleView 侧滑菜单、添加头部底部、加载更多
- 可以添加头部和尾部View并且支持setOnItemClickListener和滑到底部自动加载更多的RecyclerView
- RecycleView 添加底部加载更多
- 自定义RecyclerView添加HeaderView,添加FooterView,实现滑动到底部,加载更多
- XRecyclerView:实现下拉刷新、滚动到底部加载更多以及添加header功能的RecyclerView
- recycleview滑动到底部自动加载更多数据
- 手把手教你编写swipeRefreshLayout+RecylerView+底部加载更多(没有太多的封装,写出适合自己的代码)
- Android中封装RecyclerView实现添加头部和底部示例代码
- ReCyclerView添加头部和底部加载
- RecyclerView+SwipeRefreshLayout+ViewPager实现上拉加载更多下拉刷新和添加Banner(附源码)
- 一句代码加载网络图片到ImageView——Android Picasso
- Android为RecyclerView添加头部底部的简单方法
- IOS-给UIScrollView(包括继承它的UITableView、UICollectionView)添加下拉刷新-上拉加载更多
- c#如何仅在datatgirdview控件的头部(列名处)添加右键菜单
- 打造Android集合控件数据绑定(支持添加监听,支持AbsListView与RecycleView,支持异步加载等)(一)基础篇
- 打造Android集合控件数据绑定(支持添加监听,支持AbsListView与RecycleView,支持异步加载等)(二)ORM注解以及解析
- 一句代码搞定tableView的代理方法
- Android RecyclerView添加头部和底部实例详解
- 【Android】给RecyclerView添加下拉刷新和加载更多(二)
- 动手打造史上最简单的 Recycleview 侧滑菜单