文章目录
一、导包
implementation 'androidx.recyclerview:recyclerview:1.1.0'
二、基本使用
2.1、首先是两个布局文件
Activity 的布局文件
<?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:orientation="vertical">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
item 的布局文件
<?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">
<TextView
android:id="@+id/tvItem"
android:layout_width="match_parent"
android:layout_height="50dp"
android:gravity="center" />
</LinearLayout>
2.2、Adapter 代码
public class HomeAdapter extends RecyclerView.Adapter<HomeAdapter.MyViewHolder> {
private List<String> mList;
private Context mContext;
public HomeAdapter(Context mContext, List<String> mList) {
this.mContext = mContext;
this.mList = mList;
}
public void removeData(int position) {
mList.remove(position);
notifyDataSetChanged();
}
@NonNull
@Override
public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(mContext).inflate(R.layout.item_recycler, parent, false);
MyViewHolder holder = new MyViewHolder(itemView);
return holder;
}
@Override
public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
holder.tv.setText(mList.get(position));
}
@Override
public int getItemCount() {
return mList.size();
}
class MyViewHolder extends RecyclerView.ViewHolder {
private TextView tv;
public MyViewHolder(@NonNull View itemView) {
super(itemView);
tv = itemView.findViewById(R.id.tvItem);
}
}
}
2.3、Activity 中的代码
public class MainActivity extends AppCompatActivity {
private RecyclerView mRecyclerView;
private HomeAdapter mHomeAdapter;
private List<String> mList;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mRecyclerView = this.findViewById(R.id.recyclerView);
// 设置布局管理器
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
linearLayoutManager.setOrientation(RecyclerView.VERTICAL);
mRecyclerView.setLayoutManager(linearLayoutManager);
// 设置 item 增加和删除时的动画
mRecyclerView.setItemAnimator(new DefaultItemAnimator());
mList = getList();
mHomeAdapter = new HomeAdapter(this, mList);
mRecyclerView.setAdapter(mHomeAdapter);
}
private List<String> getList() {
List<String> list = new ArrayList<>();
for (int i = 0; i < 100; i++) {
list.add(i + "");
}
return list;
}
}
2.4、排列方向
垂直排列(默认): linearLayoutManager.setOrientation(RecyclerView.VERTICAL);
水平排列: linearLayoutManager.setOrientation(RecyclerView.HORIZONTAL);
运行效果
三、设置分割线
参考博客:
RecyclerView之ItemDecoration由浅入深
RecyclerView 之 ItemDecoration 讲解及高级特性实践
正确使用RecyclerView分割线
3.1、默认分割线。
mRecyclerView.addItemDecoration(new DividerItemDecoration(this, RecyclerView.VERTICAL));
效果图:
3.2、复制 DividerItemDecoration 的源码进行修改。参考 5.2 中示例
我们可以根据默认分割线的源码,继承 RecyclerView.ItemDecoration 来自定义分割线。里面核心的方法就是 onDraw 方法,它根据传进来的 orientation 来判断是绘制横向 item 的分割线还是纵向 item 的分割线。getItemOffsets 方法则用于设置 item 的 padding 属性。
四、自定义点击事件
1、自定义接口并提供回调方法
private OnItemClickListener mOnItemClickListener;
public interface OnItemClickListener {
void onItemClick(View view, int position);
void onItemLongClick(View view, int position);
}
public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
this.mOnItemClickListener = onItemClickListener;
}
2、Adapter 继承 View.OnClickListener 和 View.OnLongClickListener 并实现其中的方法。
3、OnBindViewHolder 方法中给 item 设置 tag。
@Override
public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
holder.itemView.setTag(position);
holder.tv.setText(mList.get(position));
}
4、监听 item 的点击事件并回调给我们自定义的监听。
@Override
public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(mContext).inflate(R.layout.item_recycler, parent, false);
MyViewHolder holder = new MyViewHolder(itemView);
itemView.setOnClickListener(this);
itemView.setOnLongClickListener(this);
return holder;
}
@Override
public void onClick(View v) {
if (mOnItemClickListener != null) {
mOnItemClickListener.onItemClick(v, (int) v.getTag());
}
}
@Override
public boolean onLongClick(View v) {
if (mOnItemClickListener != null) {
mOnItemClickListener.onItemLongClick(v, (int) v.getTag());
}
return false;
}
5、最后在 Activity 中进行监听
mHomeAdapter.setOnItemClickListener(new HomeAdapter.OnItemClickListener() {
@Override
public void onItemClick(View view, int position) {
Toast.makeText(MainActivity.this, "点击了第" + (position + 1) + "条", Toast.LENGTH_SHORT).show();
}
@Override
public void onItemLongClick(View view, final int position) {
new AlertDialog.Builder(MainActivity.this)
.setTitle("确认删除吗?")
.setNegativeButton("取消", null)
.setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
mHomeAdapter.removeData(position);
}
}).show();
}
});
长按时会弹出对话框,效果如下。
五、实现 GridView
5.1、布局管理器
这里设置 4 列
GridLayoutManager gridLayoutManager = new GridLayoutManager(this,4);
mRecyclerView.setLayoutManager(gridLayoutManager);
或者用瀑布流布局管理器
StaggeredGridLayoutManager staggeredGridLayoutManager = new StaggeredGridLayoutManager(4, StaggeredGridLayoutManager.HORIZONTAL);
mRecyclerView.setLayoutManager(staggeredGridLayoutManager);
注意:StaggeredGridLayoutManager.VERTICAL 的情况下,4 是列数,表示只有 4 列。改为 StaggeredGridLayoutManager.HORIZONTAL 方向时,4 是行数,表示只有 4 行。
5.2、分割线
没有默认的网格布局分割线,这里我们通过修改 DividerItemDecoration 的源码自定义一个分割线。代码如下:
public class DividerGridItemDecoration extends RecyclerView.ItemDecoration {
public static final int HORIZONTAL = LinearLayout.HORIZONTAL;
public static final int VERTICAL = LinearLayout.VERTICAL;
private static final String TAG = "DividerItem";
private static final int[] ATTRS = new int[]{android.R.attr.listDivider};
private Drawable mDivider;
private int mOrientation;
private final Rect mBounds = new Rect();
public DividerGridItemDecoration(Context context) {
final TypedArray a = context.obtainStyledAttributes(ATTRS);
mDivider = a.getDrawable(0);
if (mDivider == null) {
Log.w(TAG, "@android:attr/listDivider was not set in the theme used for this "
+ "DividerItemDecoration. Please set that attribute all call setDrawable()");
}
a.recycle();
}
public void setDrawable(@NonNull Drawable drawable) {
if (drawable == null) {
throw new IllegalArgumentException("Drawable cannot be null.");
}
mDivider = drawable;
}
@Nullable
public Drawable getDrawable() {
return mDivider;
}
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
if (parent.getLayoutManager() == null || mDivider == null) {
return;
}
drawVertical(c, parent);
drawHorizontal(c, parent);
}
private void drawVertical(Canvas canvas, RecyclerView parent) {
canvas.save();
final int left;
final int right;
//noinspection AndroidLintNewApi - NewApi lint fails to handle overrides.
if (parent.getClipToPadding()) {
left = parent.getPaddingLeft();
right = parent.getWidth() - parent.getPaddingRight();
canvas.clipRect(left, parent.getPaddingTop(), right,
parent.getHeight() - parent.getPaddingBottom());
} else {
left = 0;
right = parent.getWidth();
}
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
parent.getDecoratedBoundsWithMargins(child, mBounds);
final int bottom = mBounds.bottom + Math.round(child.getTranslationY());
final int top = bottom - mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(canvas);
}
canvas.restore();
}
private void drawHorizontal(Canvas canvas, RecyclerView parent) {
canvas.save();
final int top;
final int bottom;
//noinspection AndroidLintNewApi - NewApi lint fails to handle overrides.
if (parent.getClipToPadding()) {
top = parent.getPaddingTop();
bottom = parent.getHeight() - parent.getPaddingBottom();
canvas.clipRect(parent.getPaddingLeft(), top,
parent.getWidth() - parent.getPaddingRight(), bottom);
} else {
top = 0;
bottom = parent.getHeight();
}
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
parent.getLayoutManager().getDecoratedBoundsWithMargins(child, mBounds);
final int right = mBounds.right + Math.round(child.getTranslationX());
final int left = right - mDivider.getIntrinsicWidth();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(canvas);
}
canvas.restore();
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
RecyclerView.State state) {
if (mDivider == null) {
outRect.set(0, 0, 0, 0);
return;
}
if (mOrientation == VERTICAL) {
outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
} else {
outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
}
}
}
添加分割线
DividerGridItemDecoration gridItemDecoration=new DividerGridItemDecoration(this);
mRecyclerView.addItemDecoration(gridItemDecoration);
运行效果如下
5.3、每个item占用的格数
使用setSpanSizeLookup函数,其中传入一个GridLayoutManager.SpanSizeLookup对象,其内部有一个抽象函数getSpanSize(),你可以设置返回的数值,让当前的item占据几个位置,当然返回的int型数值只能小于等于GridLayoutManager设置span的个数,比如每行item的个数为4个,然后你设置返回5,就会报错。
首先我们不能再以上面的方式添加分割线了,而是通过设置item的样式来设置分割线,为了更加直观添加了一个颜色。
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<stroke
android:width="1dp"
android:color="#000000" />
<solid android:color="@color/colorAccent" />
</shape>
设置每个数据占用的格数。
GridLayoutManager gridLayoutManager = new GridLayoutManager(this, 4);
gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
if (position == 0) {
return 4;
} else if (position == 2) {
return 3;
} else {
return 1;
}
}
});
这里设置第1个数据占用4格,第3个数据占用3格,其余的都只占用一格。效果图如下。
六、实现瀑布流
6.1、修改 item 布局
为了实现方便,瀑布流不用分割线,我们定义 item 的分割距离为 2dp,为了更加直观,我们还给 item 添加了一个颜色。
<?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:layout_margin="2dp"
android:background="@color/colorPrimary"
android:orientation="vertical">
<TextView
android:id="@+id/tvItem"
android:layout_width="match_parent"
android:layout_height="50dp"
android:gravity="center" />
</LinearLayout>
6.2、每个 item 的高度
通常这个高度是由服务器返回的数据高度来控制的,在这里我们写一个随机高度来控制 item。
mHeights = new ArrayList<>();
for (int i = 0; i < mList.size(); i++) {
mHeights.add((int) (100 + Math.random() * 300));
}
设置 item 的高度
@Override
public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
holder.itemView.setTag(position);
holder.tv.setText(mList.get(position));
ViewGroup.LayoutParams lp = holder.tv.getLayoutParams();
lp.height = mHeights.get(position);
holder.tv.setLayoutParams(lp);
}
运行效果如下
七、更多效果
八、RecyclerView 常见问题
- 给 RecyclerView 设置 padding 时,滚动内容时与屏幕边缘存在一个间隙
给 RecyclerView 设置布局属性:android:clipToPadding=“false”
九、RecyclerView 和 ScrollView 嵌套的问题
- 滑动不流畅
代码中:RecyclerView 使用 setNestedScrollingEnabled(false) 方法禁止嵌套滑动。
或者布局中:android:nestedScrollingEnabled=“false” - 首次进入会占用焦点,导致 ScrollView 不能显示最上方
RecyclerView 设置 setFocusable(false) 。 - 显示不全
外层嵌套布局并使用 android:descendantFocusability=“blocksDescendants” 属性。
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:descendantFocusability="blocksDescendants">
<android.support.v7.widget.RecyclerView
android:id="@+id/recyclerview"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</RelativeLayout>