recyclerview是Android 5.0推出的,是support-v7包中的新组件,它的出现就是为了代替ListView、GridView。
recyclerview是一个很强大的,高度解耦的控件。然而我们使用时候却很麻烦,基础的adapter要继承大量的方法进行实现,这给我们使用 带来了大量的困扰,我们需要写大量重复的代码,viewholer等等,我们要自定义点击事件,item的子控件的点击事件等等。
作为一个爱copy,爱逛gayhub的程序员,很早就发现了这个强大的库,文档介绍的也比较详细,但是总感觉看着不太舒服,就自己总结下,转载一下文档(原文文档)。
先在 build.gradle(Project:XXXX) 的 repositories 添加:
allprojects {
repositories {
...
maven { url "https://jitpack.io" }
}
}
然后在 build.gradle(Module:app) 的 dependencies 添加:
dependencies {
implementation 'com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.31'
}
public class Adapter1 extends BaseQuickAdapter{
Context mcontext;
public Adapter1(Context context,int layoutResId, @Nullable List data) {
super(layoutResId, data);
mcontext=context;
}
@Override
protected void convert(BaseViewHolder helper, Student item) {
helper.setText(R.id.student_name,item.getName())
.setText(R.id.student_age,item.getAge()+"")
.setText(R.id.student_address,item.getAddress());
Glide.with(mcontext).load(item.getIcon()).into((ImageView) helper.getView(R.id.student_icon));
}
}
从上文中的实例代码我们可以看出以下几点:
1、使用: 首先需要继承BaseQuickAdapter,然后BaseQuickAdapter 《Bean,BaseViewHolder》 第一个泛型Bean是数据实体类型,第二个BaseViewHolder是ViewHolder其目的是为了支持扩展ViewHolder。
2、赋值:可以直接使用viewHolder对象点相关方法通过传入viewId和数据进行,方法支持链式调用。如果是加载网络图片或自定义view可以通过viewHolder.getView(viewId)获取该控件。
item的点击事件和长按事件已经封装到adapter里面,只需要在使用的时候用就可以了。
adapter.setOnItemClickListener(new BaseQuickAdapter.OnItemClickListener() {
@Override
public void onItemClick(BaseQuickAdapter adapter, View view, int position) {
Toast.makeText(CommonAdapterActivity.this, "点击了item", Toast.LENGTH_SHORT).show();
}
});
Item的长按事件
adapter.setOnItemLongClickListener(new BaseQuickAdapter.OnItemLongClickListener() {
@Override
public boolean onItemLongClick(BaseQuickAdapter adapter, View view, int position) {
Toast.makeText(CommonAdapterActivity.this, "长按了item", Toast.LENGTH_SHORT).show();
return false;
}
});
注意:嵌套recycleView的情况下需要使用你使用 adapter. setOnItemClickListener 来设置点击事件,如果使用recycleView.addOnItemTouchListener会累计添加的。
首先在adapter的convert方法里面通过viewHolder.addOnClickListener绑定一下的控件id,
@Override
protected void convert(BaseViewHolder helper, Student item) {
helper.setText(R.id.student_name,item.getName())
.setText(R.id.student_age,item.getAge()+"")
.setText(R.id.student_address,item.getAddress())
.addOnClickListener(R.id.student_icon)
.addOnClickListener(R.id.student_name)
.addOnLongClickListener(R.id.student_address);
Glide.with(mcontext).load(item.getIcon()).into((ImageView) helper.getView(R.id.student_icon));
}
然后在设置子控件点击事件
adapter.setOnItemChildClickListener(new BaseQuickAdapter.OnItemChildClickListener() {
@Override
public void onItemChildClick(BaseQuickAdapter adapter, View view, int position) {
switch (view.getId()) {
case R.id.student_icon:
//获取其他控件,如下,获取student_name
TextView textView=(TextView) adapter.getViewByPosition(recycleview, position, R.id.student_name);
Toast.makeText(CommonAdapterActivity.this, "点击"+textView.getText().toString()+"的头像", Toast.LENGTH_SHORT).show();
break;
case R.id.student_name:
Toast.makeText(CommonAdapterActivity.this, "点击了名字", Toast.LENGTH_SHORT).show();
break;
}
}
});
然后在设置子控件长按事件
adapter.setOnItemChildLongClickListener(new BaseQuickAdapter.OnItemChildLongClickListener() {
@Override
public boolean onItemChildLongClick(BaseQuickAdapter adapter, View view, int position) {
if (view.getId() == R.id.student_address)
Toast.makeText(CommonAdapterActivity.this, "长按了address", Toast.LENGTH_SHORT).show();
return false;
}
});
注意:设置子控件的事件,如果不在adapter中绑定,点击事件无法生效,因为无法找到你需要设置的控件。
如果需要获取item中其他控件,上述代码有表现, adapter.getViewByPosition()方法进行初始化
TextView textView=(TextView) adapter.getViewByPosition(recycleview, position, R.id.student_name);
ps:如果有header的话需要处理一下position加上 headerlayoutcount。即如果header有的话,postion的点击postion实际上为postion+headerlayoutcount.
开启动画(默认为渐显效果)
adapter.openLoadAnimation();
默认提供5种方法(渐显、缩放、从下到上,从左到右、从右到左)
public static final int ALPHAIN = 0x00000001;
public static final int SCALEIN = 0x00000002;
public static final int SLIDEIN_BOTTOM = 0x00000003;
public static final int SLIDEIN_LEFT = 0x00000004;
public static final int SLIDEIN_RIGHT = 0x00000005;
切换动画
quickAdapter.openLoadAnimation(BaseQuickAdapter.ALPHAIN);
自定义动画
quickAdapter.openLoadAnimation(new BaseAnimation() {
@Override
public Animator[] getAnimators(View view) {
return new Animator[]{
ObjectAnimator.ofFloat(view, "scaleY", 1, 1.1f, 1),
ObjectAnimator.ofFloat(view, "scaleX", 1, 1.1f, 1)
};
}
});
动画默认只执行一次,如果想重复执行可设置
mQuickAdapter.isFirstOnly(false);
因为有些人不希望第一页看到动画,或者说希望前几个条目加载不需要有动画,所以可以
设置不显示动画数量
adapter.setNotDoAnimationCount(count);
mQuickAdapter.addHeaderView(getView());
mQuickAdapter.addFooterView(getView());
删除指定view
mQuickAdapter.removeHeaderView(getView);
mQuickAdapter.removeFooterView(getView);
删除所有
mQuickAdapter.removeAllHeaderView();
mQuickAdapter.removeAllFooterView();
默认出现了头部就不会显示Empty,和尾部,配置以下方法也支持同时显示:
setHeaderAndEmpty
setHeaderFooterEmpty
默认头部尾部都是占满一行,如果需要不占满可以配置:
setHeaderViewAsFlow
setFooterViewAsFlow
此adapter封装加载更多其实并不好用,因此我更建议采用传统的根据recyclerview判断item位置进行判断,自动加载更多
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
lastVisibleItem = linearLayoutManager.findLastVisibleItemPosition();
firstVisibleItem=linearLayoutManager.findFirstVisibleItemPosition();
if (newState == RecyclerView.SCROLL_STATE_IDLE&&linearLayoutManager.getItemCount() >0&&lastVisibleItem + 1 == linearLayoutManager.getItemCount()) {
new Handler().postDelayed(() -> loadData(),300) ;
}
}
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
lastVisibleItem = linearLayoutManager.findLastVisibleItemPosition();
firstVisibleItem=linearLayoutManager.findFirstVisibleItemPosition();
}
});
LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
屏幕中最后一个可见子项的position
int lastVisibleItem = layoutManager.findLastVisibleItemPosition();
//当前屏幕所看到的子项个数
layoutManager.getChildCount();
//当前RecyclerView的所有子项个数
layoutManager.getItemCount();
//RecyclerView的滑动状态
recyclerView.getScrollState();
如果想要预加载也很简单,只需要修改当前最下方item的postion和总条目之间的差值即可。
此adapter分组布局,不是类似于那种黏性头部的分组,而是把头布局作为一个item进行多布局的封装。
头布局也支持点击事件,头布局内部的控件也支持点击事件。和普通布局一般无二。
把需要加头布局的实体类用一个新的实体类包裹起来,比如作者举例的Video,用MySection 包裹了Video,这样的MySection内部有两个构造方法,一个是头布局的构造方法,一个是实体类的构造方法,举例如下
public class MySection extends SectionEntity
你可以在构造方法中加上自己需要的字段来以便给头布局赋值;上述代码加入了一个ismore字段,用来判断是否有展开更多字段。
adapter需要实现BaseSectionQuickAdapter,convertHead用来绑定头布局数据,convert用来绑定一般数据。
public class SectionAdapter extends BaseSectionQuickAdapter {
public SectionAdapter(int layoutResId, int sectionHeadResId, List data) {
super(layoutResId, sectionHeadResId, data);
}
@Override
protected void convertHead(BaseViewHolder helper, final MySection item) {
helper.setText(R.id.header, item.header)
.setVisible(R.id.more, item.isMore())
.addOnClickListener(R.id.more);
}
@Override
protected void convert(BaseViewHolder helper, MySection item) {
Video video = (Video) item.t;
switch (helper.getLayoutPosition() %
2) {
case 0:
helper.setImageResource(R.id.iv, R.mipmap.m_img1);
break;
case 1:
helper.setImageResource(R.id.iv, R.mipmap.m_img2);
break;
}
helper.setText(R.id.tv, video.getName());
}
}
当然数据初始化的时候要注意,同一头布局下的需要写在一起:
public static List getSampleData() {
List list = new ArrayList<>();
list.add(new MySection(true, "Section 1", true));
list.add(new MySection(new Video(HTTPS_AVATARS1_GITHUBUSERCONTENT_COM_LINK, CYM_CHAD)));
list.add(new MySection(true, "Section 2", false));
list.add(new MySection(new Video(HTTPS_AVATARS1_GITHUBUSERCONTENT_COM_LINK, CYM_CHAD)));
list.add(new MySection(new Video(HTTPS_AVATARS1_GITHUBUSERCONTENT_COM_LINK, CYM_CHAD)));
list.add(new MySection(new Video(HTTPS_AVATARS1_GITHUBUSERCONTENT_COM_LINK, CYM_CHAD)));
list.add(new MySection(new Video(HTTPS_AVATARS1_GITHUBUSERCONTENT_COM_LINK, CYM_CHAD)));
list.add(new MySection(new Video(HTTPS_AVATARS1_GITHUBUSERCONTENT_COM_LINK, CYM_CHAD)));
list.add(new MySection(true, "Section 3", false));
list.add(new MySection(new Video(HTTPS_AVATARS1_GITHUBUSERCONTENT_COM_LINK, CYM_CHAD)));
list.add(new MySection(new Video(HTTPS_AVATARS1_GITHUBUSERCONTENT_COM_LINK, CYM_CHAD)));
list.add(new MySection(true, "Section 4", false));
list.add(new MySection(new Video(HTTPS_AVATARS1_GITHUBUSERCONTENT_COM_LINK, CYM_CHAD)));
list.add(new MySection(new Video(HTTPS_AVATARS1_GITHUBUSERCONTENT_COM_LINK, CYM_CHAD)));
}
实体类必须实现MultiItemEntity,在设置数据的时候,需要给每一个数据设置itemType
public class MultipleItem implements MultiItemEntity {
public static final int TEXT = 1;
public static final int IMG = 2;
private int itemType;
public MultipleItem(int itemType) {
this.itemType = itemType;
}
@Override
public int getItemType() {
return itemType;
}
}
adapter需要继承BaseMultiItemQuickAdapter,addItemType用来绑定itemType和item布局。
public class MultipleItemQuickAdapter extends BaseMultiItemQuickAdapter {
public MultipleItemQuickAdapter(List data) {
super(data);
addItemType(1, R.layout.text_view);
addItemType(2, R.layout.image_view);
}
@Override
protected void convert(BaseViewHolder helper, MultipleItem item) {
switch (helper.getItemViewType()) {
case 1:
helper.setImageUrl(R.id.tv, item.getContent());
break;
case 2:
helper.setImageUrl(R.id.iv, item.getContent());
break;
}
}
}
mQuickAdapter.setEmptyView(getView());
拖拽和滑动删除的回调方法
OnItemDragListener onItemDragListener = new OnItemDragListener() {
@Override
public void onItemDragStart(RecyclerView.ViewHolder viewHolder, int pos){}
@Override
public void onItemDragMoving(RecyclerView.ViewHolder source, int from, RecyclerView.ViewHolder target, int to) {}
@Override
public void onItemDragEnd(RecyclerView.ViewHolder viewHolder, int pos) {}
}
OnItemSwipeListener onItemSwipeListener = new OnItemSwipeListener() {
@Override
public void onItemSwipeStart(RecyclerView.ViewHolder viewHolder, int pos) {}
@Override
public void clearView(RecyclerView.ViewHolder viewHolder, int pos) {}
@Override
public void onItemSwiped(RecyclerView.ViewHolder viewHolder, int pos) {}
};
adapter需要继承BaseItemDraggableAdapter
public class ItemDragAdapter extends BaseItemDraggableAdapter {
public ItemDragAdapter(List data) {
super(R.layout.item_draggable_view, data);
}
@Override
protected void convert(BaseViewHolder helper, String item) {
helper.setText(R.id.tv, item);
}
}
Activity使用代码
mAdapter = new ItemDragAdapter(mData);
ItemDragAndSwipeCallback itemDragAndSwipeCallback = new ItemDragAndSwipeCallback(mAdapter);
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(itemDragAndSwipeCallback);
itemTouchHelper.attachToRecyclerView(mRecyclerView);
// 开启拖拽
mAdapter.enableDragItem(itemTouchHelper, R.id.textView, true);
mAdapter.setOnItemDragListener(onItemDragListener);
// 开启滑动删除
mAdapter.enableSwipeItem();
mAdapter.setOnItemSwipeListener(onItemSwipeListener);
默认不支持多个不同的 ViewType 之间进行拖拽,如果开发者有所需求:
重写ItemDragAndSwipeCallback里的onMove()方法,return true即可
例子:三级菜单
// if you don't want to extent a class, you can also use the interface IExpandable.
// AbstractExpandableItem is just a helper class.
public class Level0Item extends AbstractExpandableItem {...}
public class Level1Item extends AbstractExpandableItem {...}
public class Person {...}
adapter需要继承BaseMultiItemQuickAdapter
public class ExpandableItemAdapter extends BaseMultiItemQuickAdapter {
public ExpandableItemAdapter(List data) {
super(data);
addItemType(TYPE_LEVEL_0, R.layout.item_expandable_lv0);
addItemType(TYPE_LEVEL_1, R.layout.item_expandable_lv1);
addItemType(TYPE_PERSON, R.layout.item_text_view);
}
@Override
protected void convert(final BaseViewHolder holder, final MultiItemEntity item) {
switch (holder.getItemViewType()) {
case TYPE_LEVEL_0:
....
//set view content
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int pos = holder.getAdapterPosition();
if (lv0.isExpanded()) {
collapse(pos);
} else {
expand(pos);
}
}});
break;
case TYPE_LEVEL_1:
// similar with level 0
break;
case TYPE_PERSON:
//just set the content
break;
}
}
开启所有菜单:
adapter.expandAll();
删除某一个item(添加和修改的思路是一样的)
// 获取当前父级位置
int cp = getParentPosition(person);
// 通过父级位置找到当前list,删除指定下级
((Level1Item)getData().get(cp)).removeSubItem(person);
// 列表层删除相关位置的数据
getData().remove(holder.getLayoutPosition());
// 更新视图
notifyDataSetChanged();
自定义ViewHolder
需要继承BaseViewHolder
public class MovieViewHolder extends BaseViewHolder
然后修改adapter的第二个泛型为自定义的ViewHolder
public class DataBindingUseAdapter extends BaseQuickAdapter
注意:需要单独建一个外部类继承BaseViewHolder,否则部分机型会出现ClassCastException,如果是内部类的构造方法要是public,定义的那个类也最好是public。
keep class com.chad.library.adapter.** {
*;
}
-keep public class * extends com.chad.library.adapter.base.BaseQuickAdapter
-keep public class * extends com.chad.library.adapter.base.BaseViewHolder
-keepclassmembers class **$** extends com.chad.library.adapter.base.BaseViewHolder {
(...);
}
PinnedSectionItemDecoration:一个强大的粘性标签库
EasySwipeMenuLayout:独立的侧滑删除
EasyRefreshLayout