Android 5.0以后提供了一个更强大滚动控件——RecyclerView,可以说是一个增强版的ListView,不仅可以轻松实现ListView 同样的效果,还优化了ListView 中存在的不足之处。RecyclerView 架构提供了一种插拔式的体验,它具有高度的解藕、异常的灵活性和更高的效率,通过设置他提供的不同的LayoutManager、ItemDecoration、ItemAnimator 可实现更加丰富多样的效果。但是不得不说的是RecyclerView 也有缺点和让人头疼的地方:设置列表的分割线需要自定义,另外列表的点击事件也需要自己去实现。OK,下面开始讲解RecyclerView 的基本用法。
1、想要使用RecyclerView这个控件,首先要在项目的build.gradle 中添加相应的依赖库才行。打开app/build.gradle 文件,在dependencies 闭包中添加如下内容: implementation 'com.android.support:recyclerview-v7:26.1.0'
添加成功后修改activity_main.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:orientation="vertical">
<android.support.v7.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent">
</android.support.v7.widget.RecyclerView>
</LinearLayout>
还要创建一个适配器,新建MyAdapter类,让这个适配器继承RecyclerView.Adapter,并将泛型指定为MyAdapter.ViewHolder。
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {
private List<String> mList;
private Context mContext;
/**
* 自定义适配器
*
* @param context
* @param list
*/
public MyAdapter(Context context, List<String> list) {
this.mContext = context;
this.mList = list;
}
/**
* 创建自定义的ViewHolder
*
* @param parent
* @param viewType
* @return
*/
@Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
MyViewHolder holder = new MyViewHolder(LayoutInflater.from(mContext).
inflate(R.layout.item_recycler, parent, false));
return holder;
}
/**
* 绑定ViewHolder
*
* @param holder
* @param position
*/
@Override
public void onBindViewHolder(final MyViewHolder holder, final int position) {
holder.tvItem.setText(mList.get(position));
}
@Override
public int getItemCount() {
return mList.size();
}
/**
* 自定义ViewHolder
*/
class MyViewHolder extends RecyclerView.ViewHolder {
TextView tvItem;
public MyViewHolder(View itemView) {
super(itemView);
tvItem = (TextView) itemView.findViewById(R.id.tv_item);
}
}
}
其中的item_recycler.xml 是一个只包含TextView的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/tv_item"
android:layout_width="match_parent"
android:layout_height="50dp"
android:gravity="center"
android:text="111"
android:textSize="18sp" />
</LinearLayout>
在修改下MainActivity 中的代码,就可以初步实现一个简单的RecyclerView 了。
for (int i = 0; i < 20; i++) {
lists.add(i + "");
}
mRecyclerView = (RecyclerView) findViewById(R.id.recyclerView);
// 设置布局管理器 默认是垂直布局
mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
// 设置item 增加和删除时候的动画
mRecyclerView.setItemAnimator(new DefaultItemAnimator());
myAdapter = new MyAdapter(this, lists);
mRecyclerView.setAdapter(myAdapter);
而想要设置水平布局,代码如下设置
// 设置水平布局
LinearLayoutManager manager=new LinearLayoutManager(this);
manager.setOrientation(LinearLayoutManager.HORIZONTAL);
mRecyclerView.setLayoutManager(manager);
还有网格布局,这个布局管理器的第一个参数是一行显示多少个,第二个参数是以什么形式显示
// 设置网格布局
StaggeredGridLayoutManager layoutManager = new StaggeredGridLayoutManager(4,
LinearLayoutManager.VERTICAL);
mRecyclerView.setLayoutManager(layoutManager);
代码跑起来后,很明显的发现,并没有像ListView那样,有默认的分割线,这就需要我们自己继承RecyclerView.ItemDecoration 来自定义分割。我自己写了一个MyDividerItemDecoration类,代码如下:
/**
* author: smile .
* date: On 2018/5/19
*/
public class MyDividerItemDecoration extends RecyclerView.ItemDecoration {
private static final int[] ATTRS = new int[]{
android.R.attr.listDivider
};
// 分割线的方向参数
public static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL;
public static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL;
private Drawable mDivider;
// 分割线的高度 默认是mDivider.getIntrinsicHeight()=1
private int mDividerHeight = 2;
// 分割线的方向
private int mOrientation;
// 自定义画笔工具
private Paint mPaint;
/**
* 构造函数 自定义分割线
*
* @param context 环境
* @param orientation 布局方向
*/
public MyDividerItemDecoration(Context context, int orientation) {
final TypedArray array = context.obtainStyledAttributes(ATTRS);
mDivider = array.getDrawable(0);
array.recycle();
setOrientation(orientation);
}
/**
* 构造函数 自定义分割线
*
* @param context 环境
* @param orientation 布局方向
* @param drawableId 分割线图片
*/
public MyDividerItemDecoration(Context context, int orientation, int drawableId) {
mDivider = ContextCompat.getDrawable(context, drawableId);
mDividerHeight = mDivider.getIntrinsicHeight();
setOrientation(orientation);
}
/**
* 构造函数 自定义分割线
*
* @param context 环境
* @param orientation 布局方向
* @param dividerHight 分割线高度
* @param dividerHight 分割线颜色
*/
public MyDividerItemDecoration(Context context, int orientation, int dividerHight, int dividerColor) {
mDividerHeight = dividerHight;
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setColor(dividerColor);
mPaint.setStyle(Paint.Style.FILL);
setOrientation(orientation);
}
/**
* 判断绘制横向的分割线还是纵向的分割线
*
* @param orientation 布局方向
*/
public void setOrientation(int orientation) {
if (orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST) {
// 跑出非法参数异常
throw new IllegalArgumentException("invalid orientation");
}
mOrientation = orientation;
}
/**
* 进行绘制
*
* @param c 画布
* @param parent 父控件
* @param state
*/
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDraw(c, parent, state);
// 判断选择的方向是什么 来确定绘制什么方向的分割线
if (mOrientation == VERTICAL_LIST) {
drawVertical(c, parent);
} else {
drawHorizontal(c, parent);
}
}
/**
* 绘制垂直方向的分割线
*
* @param c
* @param parent
*/
private void drawVertical(Canvas c, RecyclerView parent) {
// 获取左边的X值
final int left = parent.getPaddingLeft();
// 获取右边的X值
final int right = parent.getWidth() - parent.getPaddingRight();
// 获取子控件的数量
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
// 获取子控件
final View child = parent.getChildAt(i);
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
// 获取子空间的底部 即分割线的顶部 Y值
final int top = child.getBottom() + params.bottomMargin;
// 获取分割线的底部 顶部+分割线的高度
final int bottom = top + mDividerHeight;
Log.e("MyDivider", "drawVertical: " + left + "-------" + top + "-------"
+ right + "------" + bottom);
if (mDivider != null) {
// 设置分割线的范围
mDivider.setBounds(left, top, right, bottom);
// 绘制分割线
mDivider.draw(c);
}
if (mPaint != null) {
c.drawRect(left, top, right, bottom, mPaint);
}
}
}
/**
* 绘制水平方向的分割线
*
* @param c
* @param parent
*/
private void drawHorizontal(Canvas c, RecyclerView parent) {
// 获取分割线的顶部Y 值
final int top = parent.getPaddingTop();
// 获取分割线的底部Y 值
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);
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
// 获取子空间的右边 即分割线的左边X值
final int left = child.getRight() + params.rightMargin;
// 获取分割线的右边 左边+分割线的高度
final int right = left + mDividerHeight;
if (mDivider != null) {
// 设置分割线的范围
mDivider.setBounds(left, top, right, bottom);
// 绘制分割线
mDivider.draw(c);
}
if (mPaint != null) {
c.drawRect(left, top, right, bottom, mPaint);
}
}
}
/**
* 设置item 四个方向的值
*
* @param outRect
* @param view
* @param parent
* @param state
*/
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
// 为outRect 设置的四个方向的值 最终也会计入RecyclerView
if (mOrientation == VERTICAL_LIST) {
outRect.set(0, 0, 0, mDividerHeight);
} else {
outRect.set(0, 0, mDividerHeight, 0);
}
}
}
这个MyDividerItem类中,提供了三个构造函数:第一个构造函数式只设置布局方向,分割线的样式和高度跟随系统默认。第二个构造函数是设置布局方向以及分割线的图片,分割线的样式由图片决定。第三个构造函数是设置布局方向、分割线的高度和颜色,采用画笔的方式将分割线实现。 设置完自定义的分割线后,我们只要在setAdapter 之前加入代码便可以加入分割线。
// 设置分割线
mRecyclerView.addItemDecoration(new MyDividerItemDecoration(this,
MyDividerItemDecoration.VERTICAL_LIST, 3, Color.BLACK));
接下来我们来自定义点击事件,自定义点击事件其实并不是很难,先自定义一个 OnItemClickListener 接口并提供回调,用来监听点击和长按事件,代码如下:
/**
* 自定义回调接口
*/
public interface OnItemClickListener {
void onItemClick(View view, int position);
void onItemLongClick(View view, int position);
}
在MyAdpater适配器中设置接口方法回调给对象
/**
* 设置接口回调对象
*
* @param onItemClickListener
*/
public void setOnItemClickListener(OnItemClickListener onItemClickListener) {
this.mOnItemClickListener = onItemClickListener;
}
接下来对item中的控件进行点击事件的监听并回调给我们的自定义的监听,代码如下:
/**
* 绑定ViewHolder
*
* @param holder
* @param position
*/
@Override
public void onBindViewHolder(final MyViewHolder holder, final int position) {
holder.tvItem.setText(mList.get(position));
if (mOnItemClickListener != null) {
holder.tvItem.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int pos = holder.getLayoutPosition();
mOnItemClickListener.onItemClick(v, pos);
}
});
holder.tvItem.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
int pos = holder.getLayoutPosition();
mOnItemClickListener.onItemLongClick(v, pos);
return false;
}
});
}
}
最后在Activity中对适配器设置监听:
// 设置点击事件的回调
myAdapter.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(View view, int position) {
Toast.makeText(MainActivity.this, "这是点击的第" + position + "条",
Toast.LENGTH_SHORT).show();
}
@Override
public void onItemLongClick(View view, final int position) {
Toast.makeText(MainActivity.this, "这是长按的第" + position + "条",
Toast.LENGTH_SHORT).show();
}
});
最后我还想添加一个长按当前Item 删除的功能。首先在MyAdapter 中添加一个removeData的方法
/**
* 删除数据并更新界面
*
* @param position
*/
public void removeData(int position) {
mList.remove(position);
notifyItemRemoved(position);
}
然后更改长按监听的事件逻辑处理就可以了。
// 设置点击事件的回调
myAdapter.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(View view, int position) {
Toast.makeText(MainActivity.this, "这是点击的第" + position + "条",
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) {
myAdapter.removeData(position);
}
}).show();
}
});
这时候长按会弹出对话框,删除时会有消失的动画效果。
今天520,前几天重新回到了伟大的单身狗阶级,所以今天有时间写博客。我想对她说,我以后可能再也不能陪你走下去了,不要太要强,好好照顾自己,一定要幸福!!!