1,在Project的build.gradle文件下添加:
allprojects {
repositories {
...
maven { url "https://jitpack.io" }
}
}
2,在app的build.gradle文件中添加:
dependencies {
......
compile 'com.github.CymChad:BaseRecyclerViewAdapterHelper:2.9.42'
}
此处注意虽然GitHub上最新版本为43,但是引入2.9.43会有问题,因为该版本是基于AndroidX的。
public class Model {
private String title;
private String content;
private String imgUrl;
//生成set、get方法
......
}
2,在布局文件中引入RecycleView:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</RelativeLayout>
3,列表中每一项的布局
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="5dp">
<ImageView
android:id="@+id/iv_img"
android:layout_width="80dp"
android:layout_height="80dp"
android:scaleType="centerCrop"/>
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginLeft="10dp"
android:layout_marginTop="10dp"
android:layout_toRightOf="@+id/iv_img"
android:text="我是标题"
android:textColor="#f00"
android:textSize="20sp" />
<TextView
android:id="@+id/tv_content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/tv_title"
android:layout_marginLeft="10dp"
android:layout_marginTop="10dp"
android:layout_toRightOf="@id/iv_img"
android:text="我是描述" />
</RelativeLayout>
4,编写适配器Adapter,同时支持重写onAttachedToRecyclerView方法,通过getSpanSize来实现具有不同尺寸item的动态布局。
public class MyAdapter extends BaseQuickAdapter<Model, BaseViewHolder> {
public MyAdapter(@LayoutRes int layoutResId, @Nullable List<Model> data) {
super(layoutResId, data);
}
@Override
protected void convert(BaseViewHolder helper, Model item) {
//可链式调用赋值
helper.setText(R.id.tv_title, item.getTitle())
.setText(R.id.tv_content, item.getContent());
// 设置图片
Glide.with(mContext).load(item.getImgUrl()).into((ImageView) helper.getView(R.id.iv_img));
//获取当前item的position
//int position = helper.getLayoutPosition();
}
}
继承:从上面可以看出我们需要继承BaseQuickAdapter,其中两个泛型,第一个泛型Status是数据实体类型,第二个BaseViewHolder是ViewHolder其目的是为了支持扩展ViewHolder。
赋值:可以直接使用viewHolder对象点相关方法通过传入viewId和数据进行,方法支持链式调用。如果是加载网络图片或自定义view可以通过viewHolder.getView(viewId)获取该控件,如果布局中包含第三方控件如Banner,那么也是通过该方式获取该控件Banner banner = holder.getView(R.id.banner),例如:
@Override
protected void convert(BaseViewHolder viewHolder, Status item) {
viewHolder.setText(R.id.tweetName, item.getUserName())
.setText(R.id.tweetText, item.getText())
.setText(R.id.tweetDate, item.getCreatedAt())
.setVisible(R.id.tweetRT, item.isRetweet())
.linkify(R.id.tweetText);
Banner banner = viewHolder.getView(R.id.banner); // 获取第三方banner
Glide.with(mContext).load(item.getUserAvatar()).crossFade().into((ImageView) viewHolder.getView(R.id.iv));
}
5,Activity中代码:
public class MainActivity extends AppCompatActivity {
private RecyclerView recyclerView;
private List<Model> datas;
private MyAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//初始化RecyclerView
recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
//模拟的数据(实际开发中一般是从网络获取的)
datas = new ArrayList<>();
Model model;
for (int i = 0; i < 15; i++) {
model = new Model();
model.setImgUrl("https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=2516392654,826153290&fm=26&gp=0.jpg");
model.setTitle("我是第" + i + "条标题");
model.setContent("第" + i + "条内容");
datas.add(model);
}
//创建布局管理
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
recyclerView.setLayoutManager(layoutManager);
//创建适配器
adapter = new MyAdapter(R.layout.item_rv, datas);
//给RecyclerView设置适配器
recyclerView.setAdapter(adapter);
}
}
自定义线性布局通用的分割线类LinearItemDecoration:
public class LinearItemDecoration extends RecyclerView.ItemDecoration {
private Drawable mDivider;
private boolean mShowLastLine;
private int mSpanSpace = 2;
private int mLeftPadding;
private int mRightPadding;
public LinearItemDecoration(int span,int leftPadding,int rightPadding,int color,boolean show){
mSpanSpace = span;
mShowLastLine = show;
mLeftPadding = leftPadding;
mRightPadding = rightPadding;
mDivider = new ColorDrawable(color);
}
@Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
int count = mShowLastLine ? parent.getAdapter().getItemCount() : parent.getAdapter().getItemCount() - 1;
if (isVertical(parent)) {
if (parent.getChildAdapterPosition(view) < count) {
outRect.set(0, 0, 0, mSpanSpace);
} else {
outRect.set(0, 0, 0, 0);
}
} else {
if (parent.getChildAdapterPosition(view) < count) {
outRect.set(0, 0, mSpanSpace, 0);
} else {
outRect.set(0, 0, 0, 0);
}
}
}
private boolean isVertical(RecyclerView parent) {
RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
if (layoutManager instanceof LinearLayoutManager) {
int orientation = ((LinearLayoutManager) layoutManager) .getOrientation();
return orientation == LinearLayoutManager.VERTICAL;
}
return false;
}
@Override public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
if (isVertical(parent)) {
drawVertical(c, parent);
} else {
drawHorizontal(c, parent);
}
}
private void drawVertical(Canvas c, RecyclerView parent) {
final int left = parent.getPaddingLeft() + mLeftPadding;
final int right = parent.getWidth() - parent.getPaddingRight() - mRightPadding;
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child .getLayoutParams();
final int top = child.getBottom() + params.bottomMargin + Math.round(ViewCompat.getTranslationY(child));
final int bottom = top + mSpanSpace; int count = mShowLastLine ? parent.getAdapter().getItemCount() : parent.getAdapter().getItemCount() - 1;
if (i < count) {
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
} else {
mDivider.setBounds(left, top, right, top);
mDivider.draw(c);
}
}
}
private void drawHorizontal(Canvas c, RecyclerView parent) {
final int top = parent.getPaddingTop();
final int bottom = parent.getHeight() - parent.getPaddingBottom();
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child .getLayoutParams();
final int left = child.getRight() + params.rightMargin + Math.round(ViewCompat.getTranslationX(child));
final int right = left + mSpanSpace; int count = mShowLastLine ? parent.getAdapter().getItemCount() : parent.getAdapter().getItemCount() - 1;
if (i < count) {
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
}
/**
* Builder模式
* */
public static class Builder{
private Context mContext;
private Resources mResources;
private int mSpanSpace;
private boolean mShowLastLine;
private int mLeftPadding;
private int mRightPadding;
private int mColor;
public Builder(Context context){
mContext = context;
mResources = context.getResources();
mSpanSpace = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, 1f, context.getResources().getDisplayMetrics());
mLeftPadding = 0;
mRightPadding = 0;
mShowLastLine = false;
mColor = Color.BLACK;
}
/**
* 设置分割线宽(高)度
*/
public Builder setSpan(float pixels) {
mSpanSpace = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, pixels, mResources.getDisplayMetrics());
return this;
}
/**
* 设置分割线宽(高)度
*/
public Builder setSpan(@DimenRes int resource) {
mSpanSpace = mResources.getDimensionPixelSize(resource);
return this;
}
/**
* 设置左右间距
*/
public Builder setPadding(float pixels) {
setLeftPadding(pixels);
setRightPadding(pixels);
return this;
}
/**
* 设置左右间距
*/
public Builder setPadding(@DimenRes int resource) {
setLeftPadding(resource);
setRightPadding(resource);
return this;
}
/**
* 设置左间距
*/
public Builder setLeftPadding(float pixelPadding) {
mLeftPadding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, pixelPadding, mResources.getDisplayMetrics());
return this;
}
/**
* 设置右间距
*/
public Builder setRightPadding(float pixelPadding) {
mRightPadding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, pixelPadding, mResources.getDisplayMetrics());
return this;
}
/**
* 通过资源id设置左间距
*/
public Builder setLeftPadding(@DimenRes int resource) {
mLeftPadding = mResources.getDimensionPixelSize(resource);
return this;
}
/**
* 通过资源id设置右间距
*/
public Builder setRightPadding(@DimenRes int resource) {
mRightPadding = mResources.getDimensionPixelSize(resource);
return this;
}
/**
* 通过资源id设置颜色
*/
public Builder setColorResource(@ColorRes int resource) {
setColor(ContextCompat.getColor(mContext,resource));
return this;
}
/**
* 设置颜色
*/
public Builder setColor(@ColorInt int color) {
mColor = color;
return this;
}
/**
* 是否最后一条显示分割线
* */
public Builder setShowLastLine(boolean show){
mShowLastLine = show;
return this;
}
/**
* Instantiates a LinearItemDecoration with the specified parameters.
* @return a properly initialized LinearItemDecoration instance
*/
public LinearItemDecoration build() {
return new LinearItemDecoration(mSpanSpace,mLeftPadding,mRightPadding,mColor,mShowLastLine);
}
}
}
使用如下:
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(context);
linearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
/**设置recyclerview*/
LinearItemDecoration divider = new LinearItemDecoration.Builder(context)
.setSpan(20f)
// .setPadding(R.dimen.line_width)
// .setLeftPadding(R.dimen.common_title_height)
// .setRightPadding(R.dimen.common_title_height)
.setColorResource(R.color.gray_line)
.setShowLastLine(true)
.build();
recycle_recommend.addItemDecoration(divider);
recycle_recommend.setLayoutManager(linearLayoutManager);
自定义网格布局通用的分割线GridItemDecoration类,代码如下:
public class GridItemDecoration extends RecyclerView.ItemDecoration {
private Drawable mDivider;
private boolean mShowLastLine;
private int mHorizonSpan;
private int mVerticalSpan;
private GridItemDecoration(int horizonSpan,int verticalSpan,int color,boolean showLastLine) {
this.mHorizonSpan = horizonSpan;
this.mShowLastLine = showLastLine;
this.mVerticalSpan = verticalSpan;
mDivider = new ColorDrawable(color);
}
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
drawHorizontal(c, parent);
drawVertical(c, parent);
}
private void drawHorizontal(Canvas c, RecyclerView parent) {
int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
View child = parent.getChildAt(i);
//最后一行底部横线不绘制
if (isLastRaw(parent,i,getSpanCount(parent),childCount) && !mShowLastLine){
continue;
}
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
final int left = child.getLeft() - params.leftMargin;
final int right = child.getRight() + params.rightMargin;
final int top = child.getBottom() + params.bottomMargin;
final int bottom = top + mHorizonSpan;
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
private void drawVertical(Canvas c, RecyclerView parent) {
int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
if((parent.getChildViewHolder(child).getAdapterPosition() + 1) % getSpanCount(parent) == 0){
continue;
}
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
final int top = child.getTop() - params.topMargin;
final int bottom = child.getBottom() + params.bottomMargin + mHorizonSpan;
final int left = child.getRight() + params.rightMargin;
int right = left + mVerticalSpan;
// //满足条件( 最后一行 && 不绘制 ) 将vertical多出的一部分去掉;
if (i==childCount-1) {
right -= mVerticalSpan;
}
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
/**
* 计算偏移量
* */
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
int spanCount = getSpanCount(parent);
int childCount = parent.getAdapter().getItemCount();
int itemPosition = ((RecyclerView.LayoutParams) view.getLayoutParams()).getViewLayoutPosition();
if (itemPosition < 0){
return;
}
int column = itemPosition % spanCount;
int bottom;
int left = column * mVerticalSpan / spanCount;
int right = mVerticalSpan - (column + 1) * mVerticalSpan / spanCount;
if (isLastRaw(parent, itemPosition, spanCount, childCount)){
if (mShowLastLine){
bottom = mHorizonSpan;
}else{
bottom = 0;
}
}else{
bottom = mHorizonSpan;
}
outRect.set(left, 0, right, bottom);
}
/**
* 获取列数
* */
private int getSpanCount(RecyclerView parent) {
// 列数
int mSpanCount = -1;
RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
if (layoutManager instanceof GridLayoutManager) {
mSpanCount = ((GridLayoutManager) layoutManager).getSpanCount();
} else if (layoutManager instanceof StaggeredGridLayoutManager) {
mSpanCount = ((StaggeredGridLayoutManager) layoutManager).getSpanCount();
}
return mSpanCount;
}
/**
* 是否最后一行
* @param parent RecyclerView
* @param pos 当前item的位置
* @param spanCount 每行显示的item个数
* @param childCount child个数
* */
private boolean isLastRaw(RecyclerView parent, int pos, int spanCount, int childCount) {
RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
if (layoutManager instanceof GridLayoutManager) {
return getResult(pos,spanCount,childCount);
} else if (layoutManager instanceof StaggeredGridLayoutManager) {
int orientation = ((StaggeredGridLayoutManager) layoutManager).getOrientation();
if (orientation == StaggeredGridLayoutManager.VERTICAL) {
// StaggeredGridLayoutManager 且纵向滚动
return getResult(pos,spanCount,childCount);
} else {
// StaggeredGridLayoutManager 且横向滚动
if ((pos + 1) % spanCount == 0) {
return true;
}
}
}
return false;
}
private boolean getResult(int pos,int spanCount,int childCount){
int remainCount = childCount % spanCount;//获取余数
//如果正好最后一行完整;
if (remainCount == 0){
if(pos >= childCount - spanCount){
return true; //最后一行全部不绘制;
}
}else{
if (pos >= childCount - childCount % spanCount){
return true;
}
}
return false;
}
/**
* 使用Builder构造
* */
public static class Builder {
private Context mContext;
private Resources mResources;
private boolean mShowLastLine;
private int mHorizonSpan;
private int mVerticalSpan;
private int mColor;
public Builder(Context context) {
mContext = context;
mResources = context.getResources();
mShowLastLine = true;
mHorizonSpan = 0;
mVerticalSpan = 0;
mColor = Color.WHITE;
}
/**
* 通过资源文件设置分隔线颜色
*/
public Builder setColorResource(@ColorRes int resource) {
setColor(ContextCompat.getColor(mContext, resource));
return this;
}
/**
* 设置颜色
*/
public Builder setColor(@ColorInt int color) {
mColor = color;
return this;
}
/**
* 通过dp设置垂直间距
* */
public Builder setVerticalSpan(@DimenRes int vertical) {
this.mVerticalSpan = mResources.getDimensionPixelSize(vertical);
return this;
}
/**
* 通过px设置垂直间距
* */
public Builder setVerticalSpan(float mVertical) {
this.mVerticalSpan = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, mVertical, mResources.getDisplayMetrics());
return this;
}
/**
* 通过dp设置水平间距
* */
public Builder setHorizontalSpan(@DimenRes int horizontal) {
this.mHorizonSpan = mResources.getDimensionPixelSize(horizontal);
return this;
}
/**
* 通过px设置水平间距
* */
public Builder setHorizontalSpan(float horizontal) {
this.mHorizonSpan = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, horizontal, mResources.getDisplayMetrics());
return this;
}
/**
* 是否最后一条显示分割线
* */
public GridItemDecoration.Builder setShowLastLine(boolean show){
mShowLastLine = show;
return this;
}
public GridItemDecoration build() {
return new GridItemDecoration(mHorizonSpan, mVerticalSpan, mColor,mShowLastLine);
}
}
}
使用如下:
//创建布局管理
GridLayoutManager manager = new GridLayoutManager(mActivity,3,GridLayoutManager.VERTICAL,false);
GridItemDecoration divider = new GridItemDecoration.Builder(mActivity)
.setHorizontalSpan(R.dimen.common_vew_column_padding)
.setVerticalSpan(R.dimen.common_vew_raw_padding)
.setColorResource(R.color.dark_grey) // 线的颜色
.setShowLastLine(false) // 是否显示最后一行的线
.build();
recyclerView.addItemDecoration(divider);
注意:setHorizontalSpan和setVerticalSpan用于控制线在不同方向上的宽度,必须制定一个尺寸,不然线显示不出来。
1,条目item的点击事件和长按事件:
//条目点击事件
adapter.setOnItemClickListener(new BaseQuickAdapter.OnItemClickListener() {
@Override
public void onItemClick(BaseQuickAdapter adapter, View view, int position) {
Toast.makeText(MainActivity.this, "点击了第" + (position + 1) + "条条目", Toast.LENGTH_SHORT).show();
}
});
//条目长按事件
adapter.setOnItemLongClickListener(new BaseQuickAdapter.OnItemLongClickListener() {
@Override
public boolean onItemLongClick(BaseQuickAdapter adapter, View view, int position) {
Toast.makeText(MainActivity.this, "长按了第" + (position + 1) + "条条目", Toast.LENGTH_SHORT).show();
return false;
}
});
长按事件返回false,事件会继续向下传递触发点击事件,如果返回true,则不会触发点击事件。
注意:在嵌套recycleView的情况下需要使用 adapter. setOnItemClickListener 来设置点击事件,如果使用recycleView.addOnItemTouchListener会累计添加的。
2,item子控件的点击事件和长按事件
首先需要在adapter的convert方法里面通过 helper.addOnClickListener 绑定一下子控件的控件id:
@Override
protected void convert(BaseViewHolder helper, Model item) {
//可链式调用赋值
helper.setText(R.id.tv_title, item.getTitle())
.setText(R.id.tv_content, item.getContent())
.addOnClickListener(R.id.iv_img) //给图标添加点击事件
.addOnLongClickListener(R.id.tv_title)//给标题添加长按事件
.addOnClickListener(R.id.tv_content); //给内容也添加点击事件
// 设置图片
Glide.with(mContext).load(item.getImgUrl()).into((ImageView) helper.getView(R.id.iv_img));
}
然后设置:
//条目子控件点击事件
adapter.setOnItemChildClickListener(new BaseQuickAdapter.OnItemChildClickListener() {
@Override
public void onItemChildClick(BaseQuickAdapter adapter, View view, int position) {
Toast.makeText(MainActivity.this, "点击了第" + (position + 1) + "条条目的图片", Toast.LENGTH_SHORT).show();
}
});
或者设置:
//条目子控件长按事件
adapter.setOnItemChildLongClickListener(new BaseQuickAdapter.OnItemChildLongClickListener() {
@Override
public boolean onItemChildLongClick(BaseQuickAdapter adapter, View view, int position) {
Toast.makeText(MainActivity.this, "长按了第" + (position + 1) + "条条目的图片", Toast.LENGTH_SHORT).show();
return false;
}
});
不管是设置item子控件的点击事件还是长按事件都需要先在adapter的convert方法中绑定,否则没有效果。
3,item中同时有多个子控件添加了点击事件
当然首先也需要在adapter的convert方法中绑定事件,然后作如下判断即可:
//条目子控件点击事件
adapter.setOnItemChildClickListener(new BaseQuickAdapter.OnItemChildClickListener() {
@Override
public void onItemChildClick(BaseQuickAdapter adapter, View view, int position) {
//判断id
if (view.getId() == R.id.iv_img) {
Log.i("tag", "点击了第" + position + "条条目的 图片");
} else if (view.getId() == R.id.tv_title) {
Log.i("tag", "点击了第" + position + "条条目的 标题");
}
}
});
长按事件也是相同的方法处理。
在子控件事件中获取其他子控件
//条目子控件点击事件
adapter.setOnItemChildClickListener(new BaseQuickAdapter.OnItemChildClickListener() {
@Override
public void onItemChildClick(BaseQuickAdapter adapter, View view, int position) {
Toast.makeText(MainActivity.this, "点击了第" + (position + 1) + "条条目的图片", Toast.LENGTH_SHORT).show();
// 获取标题控件
TextView tv_title = (TextView) adapter.getViewByPosition(recyclerView, position, R.id.tv_title);
Log.i("tag", "当前图片对应的 title=" + tv_title.getText());
}
});
在子控件点击事件中获取其他子控件时,需要注意position的取值,如果有header的话需要处理一下position加上 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; //从右到左
如果不想使用默认动画效果,我们可以这样来指定需要的效果:
//使用缩放动画
adapter.openLoadAnimation(BaseQuickAdapter.SCALEIN);
如果想自定义动画效果,可以这样做:
//自定义动画效果
adapter.openLoadAnimation(new BaseAnimation() {
@Override
public Animator[] getAnimators(View view) {
return new Animator[]{
ObjectAnimator.ofFloat(view, "scaleY", 1, 0.5f, 1),
ObjectAnimator.ofFloat(view, "scaleX", 1, 0.5f, 1)
};
}
});
默认动画效果只执行一次,如果想重复执行,可设置:
//设置重复执行动画
adapter.isFirstOnly(false);
设置不显示动画数量(目前还没测出效果):
由于进入界面的item都是很多的速度进来的所以不会出现滑动显示的依次执行动画效果,这个时候会一起执行动画,如果觉得这样的效果不好可以使用setNotDoAnimationCount设置第一屏item不执行动画:
adapter.setNotDoAnimationCount(count);
添加(可多次添加):
mQuickAdapter.addHeaderView(headerView);
mQuickAdapter.addFooterView(footerView);
删除指定View:
mQuickAdapter.removeHeaderView(headerView);
mQuickAdapter.removeFooterView(footerView);
删除所有:
mQuickAdapter.removeAllHeaderView();
mQuickAdapter.removeAllFooterView();
默认出现了头部就不会显示Empty,和尾部,配置以下方法也支持同时显示:
mQuickAdapter.setHeaderAndEmpty()
mQuickAdapter.setHeaderFooterEmpty();
默认头部尾部都是占满一行,如果需要不占满可以配置:
mQuickAdapter.setHeaderViewAsFlow();
mQuickAdapter.setFooterViewAsFlow();
如果需要给头部的控件添加点击事件监听,如下:
View inflate = View.inflate(mActivity, R.layout.table_oupei_titledz, null);
initHead(inflate); // 头部控件获取及初始化
adapter.addHeaderView(inflate); // 添加头
rvShow.setAdapter(adapter);
...
// 为recyclerView的头部设置点击事件监听
private void initHead(View inflate) {
RoundTextView dingzhi = inflate.findViewById(R.id.dingzhi);
dingzhi.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ToastUtil.showShort(mActivity,"点击了定制...");
}
});
}
默认上拉加载功能就是开启的,当我们第一页展示没有占满一屏时,我们会发现刚开始在加载第一页的数据时会同时展示正在加载更多视图然后自动加载更多数据,官网上说:默认第一次加载会进入加载更多的回调,如果不需要可以配置:
adapter.disableLoadMoreIfNotFullPage();
但是设置了之后程序出现闪退,查看日志说是必须先调用bindToRecyclerView方法,于是设置如下:
adapter.bindToRecyclerView(recyclerView);
adapter.disableLoadMoreIfNotFullPage();
此时发现不闪退了,但是当没有占满一屏时,第一次进去还是默认会显示加载视图然后自动加载更多数据,本来初衷是想去掉默认的加载动作的,但是发现并没有起作用,此处留给读者自己去研究。
上拉加载监听的回调,(模拟数据获取,完整代码如下):
public class MainActivity extends AppCompatActivity {
private RecyclerView recyclerView;
private List<Model> datas;
private List<Model> datas2;
private MyAdapter adapter;
private int TOTAL_COUNTER = 30; // 总的数据
private int mCurrentCounter = 15; // 每页显示的条数
private boolean isErr = true;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//初始化RecyclerView
recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
//模拟的数据(实际开发中一般是从网络获取的)
datas = new ArrayList<>();
datas2 = new ArrayList<>();
Model model;
for (int i = 0; i < 15; i++) {
model = new Model();
model.setImgUrl("https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=2516392654,826153290&fm=26&gp=0.jpg");
model.setTitle("我是第" + i + "条标题");
model.setContent("第" + i + "条内容");
datas.add(model);
}
Model model2;
for (int i = 15; i < 30; i++) {
model2 = new Model();
model2.setImgUrl("https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=2516392654,826153290&fm=26&gp=0.jpg");
model2.setTitle("我是第" + i + "条标题");
model2.setContent("第" + i + "条内容");
datas2.add(model2);
}
//创建布局管理
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
recyclerView.addItemDecoration(new DividerItemDecoration2(this,DividerItemDecoration2.VERTICAL_LIST));
recyclerView.setLayoutManager(layoutManager);
//创建适配器
adapter = new MyAdapter(R.layout.item, datas);
// adapter.setLoadMoreView(new CustomLoadMoreView());
// 并未起作用
// adapter.bindToRecyclerView(recyclerView);
// adapter.disableLoadMoreIfNotFullPage();
//给RecyclerView设置适配器
recyclerView.setAdapter(adapter);
//上拉加载(设置这个监听就表示有上拉加载功能了)
adapter.setOnLoadMoreListener(new BaseQuickAdapter.RequestLoadMoreListener() {
@Override public void onLoadMoreRequested() {
recyclerView.postDelayed(new Runnable() {
@Override
public void run() {
if (mCurrentCounter >= TOTAL_COUNTER) {
//数据全部加载完毕
adapter.loadMoreEnd();
} else {
if (isErr) {
//成功获取更多数据(可以直接往适配器添加数据)
// adapter.addData(DataServer.getSampleData(PAGE_SIZE));
adapter.addData(datas2);
mCurrentCounter = adapter.getData().size();
//主动调用加载完成,停止加载
adapter.loadMoreComplete();
} else {
//获取更多数据失败
isErr = true;
Toast.makeText(MainActivity.this, "加载失败!", Toast.LENGTH_LONG).show();
//同理,加载失败也要主动调用加载失败来停止加载(而且该方法会提示加载失败)
adapter.loadMoreFail();
}
}
}
}, 5000);
}
}, recyclerView);
}
}
从上面例子我们可以看出:
1,数据加载完成,加载失败,加载结束时都必须分别调用一次adapter的方法告诉系统,如下:
加载完成(表示本页数据加载完成,还有下页数据):
adapter.loadMoreComplete();
加载失败:
adapter.loadMoreFail();
加载结束(表示已无更多数据):
adapter.loadMoreEnd();
2,如果上拉结束后,下拉刷新需要再次开启上拉监听,需要使用setNewData方法填充数据。
打开或关闭加载(一般用于下拉的时候做处理,因为上拉下拉不能同时操作)
adapter.setEnableLoadMore(boolean);
预加载
// 当列表滑动到倒数第N个Item的时候(默认是1)回调onLoadMoreRequested方法
mQuickAdapter.setPreLoadNumber(int);
adapter.setLoadMoreView(new CustomLoadMoreView());
继承LoadMoreView实现自定义的CustomLoadMoreView:
public class CustomLoadMoreView extends LoadMoreView {
@Override
public int getLayoutId() {
// 该布局文件中同时指定了三种情况下的视图样式
return R.layout.view_load_more;
}
/**
* 如果返回true,数据全部加载完毕后会隐藏加载更多
* 如果返回false,数据全部加载完毕后会显示getLoadEndViewId()指定的布局
*/
@Override
public boolean isLoadEndGone() {
return false;
}
@Override
protected int getLoadingViewId() {
// 指定加载更多时的视图样式
return R.id.load_more_loading_view;
}
@Override
protected int getLoadFailViewId() {
// 指定加载更多失败时的视图样式
return R.id.load_more_load_fail_view;
}
/**
* isLoadEndGone()为true,可以返回0
* isLoadEndGone()为false,不能返回0
*/
@Override
protected int getLoadEndViewId() {
// return 0;
// 指定没有更多数据时的视图样式
return R.id.load_more_load_fail_view;
}
}
可以看到在上面的类中要求我们必须定义一个同时包含三种情况下视图样式的布局文件view_load_more.xml,当然名字我们可以任意指定,该布局文件的示例代码如下:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="40dp">
<!--加载中视图样式-->
<LinearLayout
android:id="@+id/load_more_loading_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="horizontal">
<ProgressBar
android:id="@+id/loading_progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="?android:attr/progressBarStyleSmall"
android:layout_marginRight="@dimen/4dp"
/>
<TextView
android:id="@+id/loading_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/4dp"
android:text="正在加载更多..."
android:textColor="#0dddb8"
android:textSize="14sp"/>
</LinearLayout>
<!--加载失败试图样式-->
<FrameLayout
android:id="@+id/load_more_load_fail_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone">
<TextView
android:id="@+id/tv_prompt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textColor="#0dddb8"
android:text="加载失败!"/>
</FrameLayout>
<!--已无更多数据的试图样式-->
<RelativeLayout
android:id="@+id/load_more_no_data"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:textColor="@color/red"
android:text="已无更多数据!"/>
</RelativeLayout>
</FrameLayout>
在我们自定义的CustomLoadMoreView类中,每种方法返回的就是布局文件中指定的对应情况下试图样式的样式id值。
建议:自定义布局文件时,根布局使用FrameLayout不要变。
该框架实现的下拉刷新功能跟我们通常的下拉刷新是有区别的,类似于查看qq聊天记录时的历史记录查看效果,不是app中那种常见的下拉刷新,因此对于实现下拉刷新效果的实现,我一般会选择使用SwipeRefreshLayout或者智能控件SmartRefreshLayout来实现,SmartRefreshLayout还是很强大的,关于SmartRefreshLayout的使用不会的读者可查看我的博客:安卓项目实战之强大的智能下拉刷新上拉加载框架SmartRefreshLayout 来了解。
此处要注意的是:
在上面讲解上拉加载更多时我们说过:
如果上拉加载结束后,下拉刷新需要再次开启上拉加载监听,需要使用setNewData方法填充数据。
swip.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
adapter.setNewData(datas3);
mCurrentCounter = 15;
Toast.makeText(MainActivity.this, "下拉刷新了!", Toast.LENGTH_SHORT).show();
//刷新完成
swip.setRefreshing(false);
}
});
官网上关于这部分的讲解,示例代码用的还是之前版本的api,如果读者想实现该效果,此处推荐一篇博客:https://blog.csdn.net/mores_zixuan/article/details/78855601,
里面有具体的实现思路,可提供参考。
作为项目中最常使用的布局形式,该适配器提供了两种方案来实现多类型的布局效果:
1,实现MultiItemEntity的方式
2,为 BaseQuickAdapter 设置代理的方式
下面我们分别来介绍两种方式的具体实现过程。
方式一:实体类实现MultiItemEntity接口的方式
1,实体类必须实现MultiItemEntity,重写getItemType方法:
public class MyMultipleItem implements MultiItemEntity {
public static final int FIRST_TYPE = 1;
public static final int SECOND_TYPE = 2;
public static final int NORMAL_TYPE = 3;
private int itemType;
private Model data;
public MyMultipleItem(int itemType, Model data) {
this.itemType = itemType;
this.data = data;
}
@Override
public int getItemType() {
return itemType;
}
public Model getData(){
return data;
}
}
2,创建适配器,在adapter中绑定type和layout之间的关系,然后convert方法中为不同的layout设置数据时,通过判断样式为不同情况下的不同控件设置不同的数据:
public class MultipleItemAdapter extends BaseMultiItemQuickAdapter<MyMultipleItem, BaseViewHolder> {
public MultipleItemAdapter(List data) {
super(data);
//必须绑定type和layout的关系
addItemType(MyMultipleItem.FIRST_TYPE, R.layout.first_type_layout);
addItemType(MyMultipleItem.SECOND_TYPE, R.layout.second_type_layout);
addItemType(MyMultipleItem.NORMAL_TYPE, R.layout.item_rv);
}
@Override
protected void convert(BaseViewHolder helper, MyMultipleItem item) {
switch (helper.getItemViewType()) {
case MyMultipleItem.FIRST_TYPE:
helper.setImageResource(R.id.iv_img, R.mipmap.ic_launcher)
.setText(R.id.tv_title, item.getData().getTitle())
.setText(R.id.tv_content, item.getData().getContent());
break;
case MyMultipleItem.SECOND_TYPE:
helper.setImageResource(R.id.iv_img, R.mipmap.ic_launcher)
.setText(R.id.tv_title, item.getData().getTitle())
.setText(R.id.tv_content, item.getData().getContent());
break;
case MyMultipleItem.NORMAL_TYPE:
helper.setImageResource(R.id.iv_img, R.mipmap.ic_launcher)
.setText(R.id.tv_title, item.getData().getTitle())
.setText(R.id.tv_content, item.getData().getContent());
break;
}
}
}
3,设置不同的布局文件样式
second_type_layout.xml布局文件如下:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="5dp">
<ImageView
android:id="@+id/iv_img"
android:layout_width="80dp"
android:layout_height="80dp"
android:scaleType="centerCrop"/>
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginLeft="10dp"
android:layout_marginTop="10dp"
android:layout_toRightOf="@+id/iv_img"
android:text="我是标题"
android:textColor="#00ff00"
android:textSize="20sp" />
<TextView
android:id="@+id/tv_content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/tv_title"
android:layout_marginLeft="10dp"
android:layout_marginTop="10dp"
android:textColor="#00ff00"
android:layout_toRightOf="@id/iv_img"
android:text="我是描述" />
</RelativeLayout>
为了简单期间我们另外两种布局只改变标题和内容文字的颜色来区分:
first_type_layout.xml:蓝色
second_type_layout.xml:绿色
item_rv.xml:红色
4,Activity中代码:
public class MainActivity extends AppCompatActivity {
private RecyclerView recyclerView;
private List<Model> datas01;
private List<MyMultipleItem> datas02;
private MultipleItemAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//初始化RecyclerView
recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
//模拟的假数据(实际开发中当然是从网络获取数据)
datas01 = new ArrayList<>();
Model model;
for (int i = 0; i < 30; i++) {
model = new Model();
model.setTitle("我是第" + i + "条标题");
model.setContent("第" + i + "条内容");
datas01.add(model);
}
datas02 = new ArrayList<>();
//这里我是随机给某一条目加载不同的布局
for (int i = 0; i < 30; i++) {
if (i % 3 == 0) {
// 蓝色:0,3,6,9,12,15,18....
datas02.add(new MyMultipleItem(MyMultipleItem.FIRST_TYPE, datas01.get(i)));
} else if (i % 4 == 0) {
// 绿色: 4,8,12,16,20,24....
datas02.add(new MyMultipleItem(MyMultipleItem.SECOND_TYPE, datas01.get(i)));
} else {
// 红色: 其余的全是红色
datas02.add(new MyMultipleItem(MyMultipleItem.NORMAL_TYPE, datas01.get(i)));
}
}
//创建布局管理
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
recyclerView.setLayoutManager(layoutManager);
//创建适配器
adapter = new MultipleItemAdapter(datas02);
//给RecyclerView设置适配器
recyclerView.setAdapter(adapter);
}
}
经过上面三步我们就已经实现了多类型的布局效果了,效果如图:
实际项目中场景:
上面的方式我们是在Activity中手动指定了对应position位置应该显式什么样的布局样式,但是一般的项目中很少会有这种情况,一般都是需要根据实体类中某个属性的值去动态判断需要显示哪种布局,接下来我们就演示一下这种情况:
1,对应的实体类如下,我们通过判断Type字段的值来和不同的布局相对应,比如我之前项目中某个页面要实现多类型布局效果,包括没有图片,一张图片和三张图片三种情况,每种情况对应不同的显示效果,其中imageUrl字段返回的是一个字符串数组,我当时就是通过判断该数组的长度返回不同的type的,此处模拟的相对简单点,直接判断type值:
public class Model2 implements MultiItemEntity{
public static final int FIRST_TYPE = 1;
public static final int SECOND_TYPE = 2;
public static final int NORMAL_TYPE = 3;
private String title;
private String content;
private String imgUrl;
private int type;
@Override
public int getItemType() {
if(type == 1){
return FIRST_TYPE; // 蓝色
}else if(type == 2){
return SECOND_TYPE; // 绿色
}else{
return NORMAL_TYPE; // 红色
}
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getImgUrl() {
return imgUrl;
}
public void setImgUrl(String imgUrl) {
this.imgUrl = imgUrl;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
}
2,适配器和三种布局文件的代码保持不变,接下来我们来看activity的代码:
public class MainActivity extends AppCompatActivity {
private RecyclerView recyclerView;
private List<Model2> data;
private MultipleItemAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//初始化RecyclerView
recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
//模拟的假数据(实际开发中当然是从网络获取数据)
data = new ArrayList<>();
Model2 model2;
for (int i = 0; i < 30; i++) {
model2 = new Model2();
model2.setTitle("我是第" + i + "条标题");
model2.setContent("第" + i + "条内容");
// 根据不同position设置不同的type值去加载不同的视图,模拟的是网络数据指定属性的值动态变化的场景
if (i % 3 == 0) {
// 蓝色:0,3,6,9,12,15,18....
model2.setType(1);
} else if (i % 4 == 0) {
// 绿色: 4,8,12,16,20,24....
model2.setType(2);
} else {
// 红色: 其余的全是红色
model2.setType(3);
}
data.add(model2);
}
//创建布局管理
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
recyclerView.setLayoutManager(layoutManager);
//创建适配器
adapter = new MultipleItemAdapter(data);
//给RecyclerView设置适配器
recyclerView.setAdapter(adapter);
}
}
这样就实现了根据从网络获取到的实体数据去动态的判断需要使用哪种布局去展示,我们也明白其实是对type的字段进行了动态判断,运行的效果和第一种情况运行的效果是一致的,因为在设置type的值时,我们就是以第一种方式实现的效果来设置的position和type取值之间的关系,当然为了验证你也可以改变该规则。
方式二:为 BaseQuickAdapter 设置代理的方式
从第一种实现方式中我们可以知道,其实多布局的本质还是要用某一个变量来区分,上述的方法是使用专门提供得MultiItemEntity接口,让我们实现,从而进行区分,既然是通过某一个变量来区分,那我们能不能不实现MultiItemEntity接口,直接在适配器里进行区分呢?
答案是可以的,官方还给出了一种方式,就是我们接下来要说的,给BaseQuickAdapter 设置代理的方式:
1,定义实体类还是通过type字段判断,只不过此时不用实现MultiItemEntity接口,也就不用重写getItemType方法,之前getItemType方法中通过type判断布局样式的逻辑代码就放在adapter中完成了,具体看下面adapter中代码:
public class Model2 {
public static final int FIRST_TYPE = 1;
public static final int SECOND_TYPE = 2;
public static final int NORMAL_TYPE = 3;
private String title;
private String content;
private String imgUrl;
private int type;
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getImgUrl() {
return imgUrl;
}
public void setImgUrl(String imgUrl) {
this.imgUrl = imgUrl;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
}
2,Adapter中代码,我们可以很明显发现之前写在实体类Model2中的判断逻辑代码此时全部写在了Adapter中:
这种方式实现adapter分三步,在下面也标出来了。
public class MultipleItemAdapter extends BaseQuickAdapter<Model2, BaseViewHolder> {
public MultipleItemAdapter(@Nullable List<Model2> data) {
super(data);
// 第一步:动态判断
setMultiTypeDelegate(new MultiTypeDelegate<Model2>() {
@Override
protected int getItemType(Model2 entity) {
//根据你的实体类来判断布局类型
int type = entity.getType();
if(type == 1){
return FIRST_TYPE; // 蓝色
}else if(type == 2){
return SECOND_TYPE; // 绿色
}else{
return NORMAL_TYPE; // 红色
}
}
});
// 第二步:设置type和layout的对应关系
getMultiTypeDelegate()
.registerItemType(FIRST_TYPE, R.layout.first_type_layout)
.registerItemType(SECOND_TYPE, R.layout.second_type_layout)
.registerItemType(NORMAL_TYPE, R.layout.item_rv);
}
@Override
protected void convert(BaseViewHolder helper, Model2 item) {
// 第三步:设置不同布局下的组件数据
switch (helper.getItemViewType()) {
case FIRST_TYPE:
helper.setImageResource(R.id.iv_img, R.mipmap.ic_launcher)
.setText(R.id.tv_title, item.getTitle())
.setText(R.id.tv_content, item.getContent());
break;
case SECOND_TYPE:
helper.setImageResource(R.id.iv_img, R.mipmap.ic_launcher)
.setText(R.id.tv_title, item.getTitle())
.setText(R.id.tv_content, item.getContent());
break;
case NORMAL_TYPE:
helper.setImageResource(R.id.iv_img, R.mipmap.ic_launcher)
.setText(R.id.tv_title, item.getTitle())
.setText(R.id.tv_content, item.getContent());
break;
}
}
}
3,Activity中代码和第一种方式实际项目例子中那个保持不变:
public class GroupActivity extends AppCompatActivity {
private RecyclerView recyclerView;
private List<Model2> data;
private MultipleItemAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//初始化RecyclerView
recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
//模拟的假数据(实际开发中当然是从网络获取数据)
data = new ArrayList<>();
Model2 model2;
for (int i = 0; i < 30; i++) {
model2 = new Model2();
model2.setTitle("我是第" + i + "条标题");
model2.setContent("第" + i + "条内容");
// 根据不同position设置不同的type值去加载不同的视图,模拟的是网络数据指定属性的值动态变化的场景
if (i % 3 == 0) {
// 蓝色:0,3,6,9,12,15,18....
model2.setType(1);
} else if (i % 4 == 0) {
// 绿色: 4,8,12,16,20,24....
model2.setType(2);
} else {
// 红色: 其余的全是红色
model2.setType(3);
}
data.add(model2);
}
//创建布局管理
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
recyclerView.addItemDecoration(new DividerItemDecoration2(this,DividerItemDecoration2.VERTICAL_LIST));
recyclerView.setLayoutManager(layoutManager);
//创建适配器
adapter = new MultipleItemAdapter(data);
//给RecyclerView设置适配器
recyclerView.setAdapter(adapter);
}
}
总结:可以发现两种实现方式最主要的区别就是将判断type的逻辑代码一个放在了实体类中,一个放在了Adapter中,第二种方式最终运行的效果和前面也是一致的。
至此两种方式设置多布局的内容就讲解完了。
没有数据时就默认显示该布局:
// 没有数据的时候默认显示该布局
adapter.setEmptyView(View.inflate(this,R.layout.load_empty,null));
拖拽和滑动删除的回调方法:
// 拖拽
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 MyAdapter extends BaseItemDraggableAdapter<Model, BaseViewHolder> {
public MyAdapter(int layoutResId, List<Model> data) {
super(layoutResId, data);
}
@Override
protected void convert(BaseViewHolder helper, Model item) {
//可链式调用赋值
helper.setText(R.id.tv_title, item.getTitle())
.setText(R.id.tv_content, item.getContent())
.addOnClickListener(R.id.iv_img) //给图标添加点击事件
.addOnLongClickListener(R.id.tv_title)//给标题添加长按事件
.addOnClickListener(R.id.tv_content); //给内容也添加点击事件
// 设置图片
Glide.with(mContext).load(item.getImgUrl()).into((ImageView) helper.getView(R.id.iv_img));
}
}
adapter跟之前的adapter的区别就是继承的接口类名不一样,类体中代码不需要更改,包括构造方法也不需要修改。
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即可。
完整activity代码如下:
public class MainActivity extends AppCompatActivity {
private RecyclerView recyclerView;
private List<Model> datas;
private List<Model> datas2;
private MyAdapter adapter;
private int TOTAL_COUNTER = 30; // 总的数据
private int mCurrentCounter = 15; // 每页显示的条数
private boolean isErr = true;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//初始化RecyclerView
recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
//模拟的数据(实际开发中一般是从网络获取的)
datas = new ArrayList<>();
datas2 = new ArrayList<>();
Model model;
for (int i = 0; i < 15; i++) {
model = new Model();
model.setImgUrl("https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=2516392654,826153290&fm=26&gp=0.jpg");
model.setTitle("我是第" + i + "条标题");
model.setContent("第" + i + "条内容");
datas.add(model);
}
Model model2;
for (int i = 15; i < 30; i++) {
model2 = new Model();
model2.setImgUrl("https://ss0.bdstatic.com/70cFuHSh_Q1YnxGkpoWK1HF6hhy/it/u=2516392654,826153290&fm=26&gp=0.jpg");
model2.setTitle("我是第" + i + "条标题");
model2.setContent("第" + i + "条内容");
datas2.add(model2);
}
//创建布局管理
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
recyclerView.addItemDecoration(new DividerItemDecoration2(this,DividerItemDecoration2.VERTICAL_LIST));
recyclerView.setLayoutManager(layoutManager);
//创建适配器,参数1为条目布局
adapter = new MyAdapter(R.layout.item,datas);
ItemDragAndSwipeCallback itemDragAndSwipeCallback = new ItemDragAndSwipeCallback(adapter);
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(itemDragAndSwipeCallback);
itemTouchHelper.attachToRecyclerView(recyclerView);
adapter.setLoadMoreView(new CustomLoadMoreView());
// 拖拽
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) {
}
@Override
public void onItemSwipeMoving(Canvas canvas, RecyclerView.ViewHolder viewHolder, float dX, float dY, boolean isCurrentlyActive) {
}
};
// 开启拖拽
adapter.enableDragItem(itemTouchHelper, R.id.tv_title, true);
adapter.setOnItemDragListener(onItemDragListener);
// 开启滑动删除
adapter.enableSwipeItem();
adapter.setOnItemSwipeListener(onItemSwipeListener);
//给RecyclerView设置适配器
recyclerView.setAdapter(adapter);
//上拉加载(设置这个监听就表示有上拉加载功能了)
adapter.setOnLoadMoreListener(new BaseQuickAdapter.RequestLoadMoreListener() {
@Override public void onLoadMoreRequested() {
recyclerView.postDelayed(new Runnable() {
@Override
public void run() {
if (mCurrentCounter >= TOTAL_COUNTER) {
//数据全部加载完毕
adapter.loadMoreEnd();
} else {
if (isErr) {
//成功获取更多数据(可以直接往适配器添加数据)
adapter.addData(datas2);
mCurrentCounter = adapter.getData().size();
//主动调用加载完成,停止加载
adapter.loadMoreComplete();
} else {
//获取更多数据失败
isErr = true;
Toast.makeText(MainActivity.this, "加载失败!", Toast.LENGTH_LONG).show();
//同理,加载失败也要主动调用加载失败来停止加载(而且该方法会提示加载失败)
adapter.loadMoreFail();
}
}
}
}, 5000);
}
}, recyclerView);
}
}
注意:上面开启拖拽时:
adapter.enableDragItem(itemTouchHelper, R.id.tv_title, true);
参数2为一个int类型的值,此处我设置的是每一个条目中的标题组件的id,在测试时发现只有长按每个item的标题才能实现拖拽,长按其他位置,如图片,内容,空白处都不可以,所以一般将该参数值设置为条目item的根布局id,我们需要去给item布局文件中的根布局设置一个id,然后此处设置为该id,经测试,此时长按条目空白处可以实现拖拽,但是长按标题,内容,图片等都没有反应,所以该参数的值就是指定长按那个组件来触发拖拽。
列表和网格布局的切换
private boolean flag;
public void change(View view){
if(!flag){
// 网格
setGvAdapter();
flag = true;
}else{
setLvAdapter();
flag = false;
}
// 定位到上次切换的位置
// recyclerView.scrollToPosition(5);
}
private void setLvAdapter() {
adapter = new MyAdapter(R.layout.item,datas);
adapter.setLoadMoreView(new CustomLoadMoreView());
adapter.setOnLoadMoreListener(new BaseQuickAdapter.RequestLoadMoreListener() {
@Override
public void onLoadMoreRequested() {
recyclerView.postDelayed(new Runnable() {
@Override
public void run() {
if (mCurrentCounter >= TOTAL_COUNTER) {
//数据全部加载完毕
adapter.loadMoreEnd();
} else {
if (isErr) {
//成功获取更多数据(可以直接往适配器添加数据)
adapter.addData(datas2);
mCurrentCounter = adapter.getData().size();
//主动调用加载完成,停止加载
adapter.loadMoreComplete();
} else {
//获取更多数据失败
isErr = true;
Toast.makeText(MainActivity.this, "加载失败!", Toast.LENGTH_LONG).show();
//同理,加载失败也要主动调用加载失败来停止加载(而且该方法会提示加载失败)
adapter.loadMoreFail();
}
}
}
}, 5000);
}
}, recyclerView);
recyclerView.setAdapter(adapter);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
}
private void setGvAdapter() {
adapter2 = new MyAdapter(R.layout.item22,datas);
// 经测试切换之后adapter1和adapter2的数量保持同步
Toast.makeText(this, "数量"+adapter2.getData().size(), Toast.LENGTH_SHORT).show();
adapter2.setLoadMoreView(new CustomLoadMoreView());
adapter2.setOnLoadMoreListener(new BaseQuickAdapter.RequestLoadMoreListener() {
@Override
public void onLoadMoreRequested() {
recyclerView.postDelayed(new Runnable() {
@Override
public void run() {
if (mCurrentCounter >= TOTAL_COUNTER) {
//数据全部加载完毕
adapter2.loadMoreEnd();
} else {
if (isErr) {
//成功获取更多数据(可以直接往适配器添加数据)
adapter2.addData(datas2);
mCurrentCounter = adapter2.getData().size();
//主动调用加载完成,停止加载
adapter2.loadMoreComplete();
} else {
//获取更多数据失败
isErr = true;
Toast.makeText(MainActivity.this, "加载失败!", Toast.LENGTH_LONG).show();
//同理,加载失败也要主动调用加载失败来停止加载(而且该方法会提示加载失败)
adapter2.loadMoreFail();
}
}
}
}, 5000);
}
}, recyclerView);
recyclerView.setAdapter(adapter2);
recyclerView.setLayoutManager(new GridLayoutManager(this,2));
}