MultiRecyclerAdapter
MultiRecyclerAdapter copied to clipboard
MultiRecyclerAdapter for RecyclerView
本库包括两个Adapter适配
MultiRecyclerAdapter for RecyclerView
Add Footer and Header for your adapter!
下面先介绍第一个MultiRecyclerAdapter
模仿b站列表的Adapter,适用于RecyclerView.
先看一下原样式效果:
像这样:
还有这样:
用法
继承项目library中的抽象类MultiBaseRecyclerAdapter<K, V>
.就像下面这样:
/**
* Created by : youngkaaa on 2016/10/9.
* Contact me : [email protected]
*/
public class MyMultiAdapter extends MultiBaseRecyclerAdapter<TitleBean,ContentBean> {
private OnItemClickedListener mItemClickedListener;
private Context mContext;
public MyMultiAdapter(Context context, Map<TitleBean, List<ContentBean>> data) {
super(context, data);
mContext=context;
}
public MyMultiAdapter(Context context, List<TitleBean> keys, Map<TitleBean, List<ContentBean>> data) {
super(context, keys, data);
mContext=context;
}
public void setItemClickedListener(OnItemClickedListener itemClickedListener) {
mItemClickedListener = itemClickedListener;
}
@Override
public int getTitleLayoutId() {
return R.layout.title_layout;
}
@Override
public int getContentLayoutId() {
return R.layout.recycler_item;
}
@Override
public void bindData(int type, BaseViewHolder holder, Object data) {
TextView textViewLeft=holder.getViewById(R.id.textViewTitleLeft);
TextView textViewRight=holder.getViewById(R.id.textViewTitleRight);
TextView textViewContent=holder.getViewById(R.id.textViewRecyclerItemContent);
ImageView imageView=holder.getViewById(R.id.imageViewContentIco);
if(type==TYPE_TITLE){
final TitleBean bean= (TitleBean) data;
textViewLeft.setText(bean.getLeftTitle());
textViewRight.setText(bean.getRightTitle());
textViewLeft.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if(mItemClickedListener!=null){
mItemClickedListener.onClick(TYPE_TITLE,bean,view);
}
}
});
textViewRight.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if(mItemClickedListener!=null){
mItemClickedListener.onClick(TYPE_TITLE,bean,view);
}
}
});
}else{
final ContentBean bean= (ContentBean) data;
textViewContent.setText(bean.getContent());
Glide.with(mContext).load(bean.getPicUrl())
.centerCrop().into(imageView);
imageView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if(mItemClickedListener!=null){
mItemClickedListener.onClick(TYPE_TITLE,bean,view);
}
}
});
}
}
}
这里我传入的K
、V
分别为:TitleBean
和ContentBean
。这两个分别是自定义的类。
注意:前者K
是用于Adapter中的每个Title
布局中的,即将用于Title
布局的赋值,下面是我的TitleBean
的代码:
/**
* Created by : youngkaaa on 2016/10/9.
* Contact me : [email protected]
*/
public class TitleBean {
private String leftTitle;
private String rightTitle;
public TitleBean() {
}
public TitleBean(String left, String right) {
this.leftTitle = left;
this.rightTitle = right;
}
public void setLeftTitle(String leftTitle) {
this.leftTitle = leftTitle;
}
public String getLeftTitle() {
return leftTitle;
}
public void setRightTitle(String rightTitle) {
this.rightTitle = rightTitle;
}
public String getRightTitle() {
return rightTitle;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
TitleBean person = (TitleBean) obj;
if (leftTitle != null && person.leftTitle != null && rightTitle != null && person.rightTitle != null
&& leftTitle.equals(person.leftTitle) && person.rightTitle.equals(rightTitle)) {
return true;
}
return true;
}
@Override
public int hashCode() {
return 7 * leftTitle.hashCode() + 13 * rightTitle.hashCode();
}
注意,要重写equals()
和hashCode()
方法哦,因为我们这里用到了Map<>
,而我们知道:当Map<>
的键是一个自定义类(在我的这个demo中是TitleBean)时,是需要重写上面这两个方法的,因为Map<>
中是通过上面那两个方法来确定键是不是一样的。所以你需要重写他们俩。而至于如何重写就不是这里的重点了,去找java有关书籍看看吧。
然后就是ContentBean
类,这个类是用来给非title
布局的布局来赋值的。这个类不需要重写上面那两个方法,因为一个是值,一个是键,只有键是自定义类时才需要重写那两个方法!
然后继承实现MultiBaseRecyclerAdapter
中的方法,下面逐个解释:
- MultiBaseRecyclerAdapter(Context context, Map<TitleBean, List<ContentBean>> data)
构造方法之一,第一个参数不用讲了吧,第二个参数就是一个Map<>
参数,其中该Map<>
的键用来给Title
布局赋值,然后每个键对应的值用来给指定的非Title
布局赋值。这样的话可以简便的实现最终的效果,但是由于Map<>
的一个特点:遍历取出来的值每次顺序都是不一样的,即不能指定顺序,所以我还提供了另外一个重载构造方法,你可以按需继承,即当你不关心最终的列表顺序,只需要显示出来内容就行,那么你就继承实现该构造方法,如果你需要指定顺序,那么你就继承下一个构造方法吧!
- MultiBaseRecyclerAdapter(Context context, List<TitleBean> keys, Map<TitleBean, List<ContentBean>> data)
构造方法之二,第一个参数不讲了。第二个参数就是你单独传入的键,用来给Title
布局赋值,然后后面的Map<>
中的对应值来给指定的非Title
布局赋值。与上面的构造方法唯一不同的就是本构造方法可以指定列表顺序,即按你传入的第二个参数的顺序来放置布局。两种常用的供不同需求使用!
- getTitleLayoutId()
重写本方法来返回Title
布局的布局资源id。所以说你可以给Title
部分添加任何你想要的样式,而不只是上面b站的样式!
- getContentLayoutId()
重写本方法来返回非Title
布局的布局资源id。所以说你可以给非Title
部分添加任何你想要的样式,而不只是上面b站的样式!
- bindData(int type, BaseViewHolder holder, Object data)
该方法就可以让你来绑定布局数据。第一个参数type
有两个可选的:TYPE_TITLE
和TYPE_OTHER
。你可以通过判断来决定具体现在是要给那个布局绑定数据,第二个参数holder
就是指定的BaseViewHolder
,你可以用它来获得指定view
,通过其getViewById()
方法。第三个参数是该布局应该绑定的数据,当type
为TYPE_TITLE
时,该data
其实就是一个TitleBean
类型的数据,即你上面重写时设置的K
的类型,你可以放心的使用类型转换,当type
为TYPE_OTHER
时,该data
其实就是一个ContentBean
类型的数据,即你上面重写时设置的V
的类型,你可以放心的使用类型转换来绑定。这里没有牵扯到position
,这些我在MultiBaseRecyclerAdapter
中都做好了,哪个位置应该绑定Map<>
中的哪个位置的数据我已经处理好直接返回给你了,你就光绑定就行了!示例代码如下:
@Override
public void bindData(int type, BaseViewHolder holder, Object data) {
TextView textViewLeft=holder.getViewById(R.id.textViewTitleLeft);
TextView textViewRight=holder.getViewById(R.id.textViewTitleRight);
TextView textViewContent=holder.getViewById(R.id.textViewRecyclerItemContent);
ImageView imageView=holder.getViewById(R.id.imageViewContentIco);
if(type==TYPE_TITLE){
final TitleBean bean= (TitleBean) data;
textViewLeft.setText(bean.getLeftTitle());
textViewRight.setText(bean.getRightTitle());
textViewLeft.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if(mItemClickedListener!=null){
mItemClickedListener.onClick(TYPE_TITLE,bean,view);
}
}
});
textViewRight.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if(mItemClickedListener!=null){
mItemClickedListener.onClick(TYPE_TITLE,bean,view);
}
}
});
}else{
final ContentBean bean= (ContentBean) data;
textViewContent.setText(bean.getContent());
Glide.with(mContext).load(bean.getPicUrl())
.centerCrop().into(imageView);
imageView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if(mItemClickedListener!=null){
mItemClickedListener.onClick(TYPE_TITLE,bean,view);
}
}
});
}
}
注意:
-
如果你使用第二个构造方法,那么要注意传入的键的数量和
Map<>
中对应的值的数量要相同,否则会抛出一个RuntimeException
,内容为:Key's size should be equal to the Map's key size! -
你如果要设置点击事件的话,你可以仿照我在项目中的实现,我内部现成的提供了一个点击事件的接口,你可以按照我的样子来设置点击事件,这样很轻松。当然你也可以自己定义使用自己定义的接口。
-
上面使用的是
GridLayoutManager
,当然你也可以使用LinearLayout
,其中LinearLayout
布局比较简单,这里就不再解释了。需要注意的是GridLayoutManager
,在使用GridLayoutManager
时,你需要做下面这一步:
gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
return mMutiAdapter.isTitleView(position)?gridLayoutManager.getSpanCount():1;
}
});
即给RecyclerView
中每个Item
设置span,显然Title
布局要占据一整行的,而非Title
就按应有的一个占用一个格子,所以上面的mMutiAdapter.isTitleView(position)?gridLayoutManager.getSpanCount():1
就不难懂了吧。而至于上面用到的isTitleView()
方法你光使用就行了,我帮你做好了判断的,不要乱继承实现该方法哦!
写的差不多了,够详细了,没写到位的请查看我提供的demo吧,里面的代码都比较简单,所以注释就没写多少,个别重要的地方有注释。
下面贴一下运行图:
更多样式请自行实现!
更新 2016-11-1 10:14:48:增加对于Border
的判断
比如我要实现时间线的效果,此时就需要获得每个Content
的开始和结束,用以控制时间线的显示或者隐藏,下面先贴一下一个实际的Demo截图:
上面这个实例是实现时间线的效果,左边会有时间线用以连接每个图标,以达到时间线
的效果。此时我们就需要把每个Content
的第一个的上面的时间线和最后一个的下面的时间线隐藏掉。所以我对原先的MultiRecyclerAdapter
进行了新的封装,新提供了一个内部类ItemResult
,它内部的实现是这样的:
public class ItemResult {
private int type;
private Object data;
private int border;
public ItemResult(int type, Object data, int border) {
this.type = type;
this.data = data;
this.border = border;
}
public int getBorder() {
return border;
}
public int getType() {
return type;
}
public Object getData() {
return data;
}
}
第一个type
和第二个data
原先的版本中存在了的,代表的意思和上面的一样。新增了一个border
属性。它有三种取值:
public static final int TOP_BORDER = 3;
public static final int BOTTOM_BORDER = 4;
public static final int OTHER_BORDER = 5;
看这个简单的图解:
上面1处就是TOP_BORDER
,2处就是BOTTOM_BORDER
,3处就是OTHER_BORDER
即当当前要绑定的item是每个Content
中的第一个时,你通过itemResult.getBorder()获得的值就是:TOP_BORDER
当时每个Content
中的最后一个时,你通过itemResult.getBorder()获得的值就是:BOTTOM_BORDER
除此之外其它的就都是OTHER_BORDER
所以在onBind()方法实现中可以是这样的:
@Override
public void bindData(BaseViewHolder holder, ItemResult result) {
if (result.getType() == TYPE_TITLE) {
//bind your data here!
} else {
final BookItem item = (BookItem) result.getData();
//bind your data here!
if(result.getBorder()==TOP_BORDER){
//TOP_BORDER
}else if(result.getBorder()==BOTTOM_BORDER){
//BOTTOM_BORDER
}else {
//OTHER_BORDER
}
}
}
这里需要注意的一点就是:当你在控制每个Item中的某些控件可见度时,需要考虑到RecyclerView的重用性哦,不然会出现item中控件可见性混乱的情况。千万注意!
下面介绍第二个HeaderFooterAdapter
下面先贴出图:
然后,用法很简单,就像下面这样(只贴出部分有关代码):
mMutiAdapter = new MyMultiAdapter(this, initListKeys(), initMapData());
headerFooterAdapter=new HeaderFooterAdapter(this,mMutiAdapter);
headerFooterAdapter.setFooterResId(R.layout.footer_layout);
headerFooterAdapter.setHeaderResId(R.layout.header_layout);
mRecyclerView.setAdapter(headerFooterAdapter);
就是先调用构造方法(上面第二句),将你已经实现了的 Adapter
传入,这样做的优点就是几乎不用修改你原来的逻辑,只需要给你原先的RecyclerAdapter
外面再包裹一个Adapter
就行了,然后设置该Adapter
给你的RecyclerView
,这样原来的逻辑都在,就多了上面两三行代码就让你的RecyclerView
有了Header
和Footer
。更多用法请查看项目内的Demo.
你需要注意的是在绑定Adapter
和RecyclerView
时,一定要记得在之前调用setFooterResId()
和setHeaderResId()
方法将你的布局传入哦!!
欢迎大神提意见,你有任何好的pr都欢迎!
如果该项目对你有用的话,给个star
以示鼓励吧!你也可以顺便围观一下我的其他项目谢谢!