1.几个重要的类
1.1 简述:首先说明他的几个重要的类
1.1.2 RecyclerView.Adapter
抽象类,为RecyclerView提供数据,一般根据不同的业务需求来编写具体的实现类。
1.1.3 RecyclerView.LayoutManager
:抽象类,主要用于测量RecyclerView的子Item,以及根据不同的布局方式来实现Item的布局效果,v 7包自带的实现类有:LinearLayoutManager、StaggeredGridLayoutManager、GridLayoutManager。
1.1.4 RecyclerView.ItemDecoration
抽象类,这个主要用于不同的Item之间添加分割线(可选)。官方没有实现类,所以如果要添加分割线,我们需要手动实现这个抽象类
.1.1.5 RecyclerView.ItemAnimator
抽象类,这个主要用于当一个item添加或者删除的时候出现的动画效果,官方提供一个默认的实现类。如果想要使我们的RecyclerView在添加、删除数据的时候有炫酷的动画,可以实现这个抽象类。
2.创建Adapter适配器
继承该类的时候,必须重写这三个方法,我们分别解释一下这三个方法是什么作用:
- onCreateViewHolder:创建ViewHolder,该方法会在RecyclerView需要展示一个item的时候回调。重写该方法时,应该使ViewHolder加载item view的布局。这个能发避免了不必要的findViewById操作,提高了性能。如果熟悉ListView的ViewHolder操作那么也能很容易理解这个。
- onBindeViewHolder:该方法在RecyclerView在特定位置展示数据时候回调,顾名思义,把数据绑定、填充到相应的item view中。
- getItemCount:返回数据的数量。我们注意到,继承该类的时候需要声明它的泛型类型VH,VH继承自RecyclerView.ViewHolder,同时第2个方法的返回值也是VH,由于ViewHolder是根据业务需求而变化的,不同的业务需求而需要的ViewHolder不尽相同,所以没必要单独写一个ViewHolder.java文件,因此我们可以在Adapter子类内部实现一个内部类ViewHolder,这样也符合单一职责原则。
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
private List<String> mDataSet;
//构造器,接受数据集
public MyAdapter(List<String> data){
mDataSet = data;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
//加载布局文件
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.itemlayout,parent,false);
ViewHolder vh = new ViewHolder(v);
return vh;
}
@Override
public void onBindViewHolder(final ViewHolder holder, int position) {
//将数据填充到具体的view中
holder.mTextView.setText(mDataSet.get(position));
}
@Override
public int getItemCount() {
return mDataSet.size();
}
class ViewHolder extends RecyclerView.ViewHolder{
public TextView mTextView;
public ViewHolder(View itemView) {
super(itemView);
//由于itemView是item的布局文件,我们需要的是里面的textView,因此利用itemView.findViewById获
//取里面的textView实例,后面通过onBindViewHolder方法能直接填充数据到每一个textView了
mTextView = (TextView) itemView.findViewById(R.id.num);
}
}
}
3. 更改布局管理器
3.1 GridLayoutManager
要改变布局管理器很简单,我们只需要实例化一个表格布局管理器即可,我们先看看它的构造函数:
/**
* @param context Current context, will be used to access resources.
* @param spanCount The number of columns or rows in the grid
* @param orientation Layout orientation. Should be {@link #HORIZONTAL} or {@link
* #VERTICAL}.
* @param reverseLayout When set to true, layouts from end to start.
*/
public GridLayoutManager(Context context, int spanCount, int orientation,
boolean reverseLayout) {
super(context, orientation, reverseLayout);
setSpanCount(spanCount);
}
其中第二个参数spanCount表示表格的行数或者列数;第三个参数表示是水平滑动或者是竖直方向滑动;最后一个参数表示是否从数据的尾部开始显示。
我们在MainActivity中作出如下修改:
mRecyclerView.setLayoutManager(new GridLayoutManager(this,4,VERTICAL,false));
3.2 StaggeredGridLayoutManager
瀑布流效果现在经常会见到,如果只有ListView或者GridView实现的话,有一定难度,但是如果使用RecyclerView则会简单很多,只需要使用StaggeredGridLayoutManager即可实现。
我们在MainActivity中,更改使用的布局管理器如下:
//构造器中,第一个参数表示列数或者行数,第二个参数表示滑动方向
mRecyclerView.setLayoutManager(new StaggeredGridLayoutManager(4,StaggeredGridLayoutManager.VERTICAL));
4. 处理RecyclerView的点击事件
4.1 利用View.onClickListener及onLongClickListener
利用了java的回调机制,这里我们依赖于子Item View的onClickListener及onLongClickListener。
- 首先新建两个接口:
public interface OnItemClickListener{
void onItemClick(View view,int position);
}
public interface OnItemLongClickListener{
void onItemLongClick(View view,int position);
}
- 新建两个私有变量用于保存用户设置的监听器以及其set方法。
public interface OnItemClickListener{
void onItemClick(View view,int position);
}
public interface OnItemLongClickListener{
void onItemLongClick(View view,int position);
}
- 在onBindViewHolder方法内,实现回调:
@Override
public void onBindViewHolder(final ViewHolder holder, int position) {
holder.mTextView.setText(mDataSet.get(position));
//判断是否设置了监听器
if(mOnItemClickListener != null){
//为ItemView设置监听器
holder.itemView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
int position = holder.getLayoutPosition(); // 1
mOnItemClickListener.onItemClick(holder.itemView,position); // 2
}
});
}
if(mOnItemLongClickListener != null){
holder.itemView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
int position = holder.getLayoutPosition();
mOnItemLongClickListener.onItemLongClick(holder.itemView,position);
//返回true 表示消耗了事件 事件不会继续传递
return true;
}
});
}
}
4.2 利用RecyclerView.OnItemTouchListener
官方虽然没有提供现成的监听器,但是提供了一个内部接口:OnItemTouchListener,我们看看官方文档对它的描述:AnOnItemTouchListener allows the application to intercept touch events in progress at the view hierarchy level of the RecyclerView before those touch events are considered for RecyclerView’s own scrolling behavior。大概意思是说该接口允许我们对RecyclerView的触摸事件进行拦截,我们看看它的几个接口方法:
@Override
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
return false;
}
@Override
public void onTouchEvent(RecyclerView rv, MotionEvent e) {
}
@Override
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
}
/**
* @author linghailong
* @date on 2018/10/28
* @email 105354999@qq.com
* @describe : 对Adapter中使用到的点击事件进行优化、解耦。在实现方法二的RecyclerViewClickListener的时候,
* 在内部对事件的实现了单击、长按的判断,但是这个长按事件不是标准的,只有松开手指的时候才会触发长按事件,
* 这也算是一点瑕疵,同时如果要增加别的事件,比如说双击事件,则需要增加相应的逻辑,如果需要判断的事件种类变多则会给我们的代码编写带来困难,
* 那么有没有更加简便的方法呢?其实安卓SDK为我们提供了一个手势检测类:GestureDetector来处理各种不同的手势,
* 那么我们完全可以利用GestureDetector来对方法二进行改进。
*/
public class RecyclerViewClickListener2 implements RecyclerView.OnItemTouchListener{
private GestureDetector mGestureDetector;
private OnItemClickListener mListener;
private Context mContext;
public interface OnItemClickListener
{
void onItemClick(View view ,int position);
void onItemLongClick(View view,int position);
}
public RecyclerViewClickListener2(Context context, final RecyclerView recyclerView, final OnItemClickListener listener) {
this.mGestureDetector = mGestureDetector;
this.mListener =listener;
this.mContext = context;
mGestureDetector=new GestureDetector(context,new GestureDetector.SimpleOnGestureListener(){
/**
* 单击事件
* @param e
* @return
*/
@Override
public boolean onSingleTapUp(MotionEvent e) {
/** 通过手指当前的点击坐标获取当前的childView*/
View childView = recyclerView.findChildViewUnder(e.getX(),e.getY());
if (childView!=null&& mListener!=null){
mListener.onItemClick(childView,recyclerView.getChildLayoutPosition(childView));
return true;
}
return false;
}
/**
* 长按事件
* @param e
*/
@Override
public void onLongPress(MotionEvent e) {
View childView=recyclerView.findChildViewUnder(e.getX(),e.getY());
if(childView!=null&&mListener!=null){
mListener.onItemLongClick(childView,recyclerView.getChildLayoutPosition(childView));
}
super.onLongPress(e);
}
});
}
@Override
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
// 把事件交给GestureDetector处理
if(mGestureDetector.onTouchEvent(e)){
return true;
}else
return false;
}
@Override
public void onTouchEvent(RecyclerView rv, MotionEvent e) {
}
@Override
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
}
}
MainActivity中的代码如下:
mRecyclerView.addOnItemTouchListener(new RecyclerViewClickListener2(this, mRecyclerView,
new RecyclerViewClickListener2.OnItemClickListener() {
@Override
public void onItemClick(View view, int position) {
Toast.makeText(MainActivity.this,"Click "+mData.get(position),Toast.LENGTH_SHORT).show();
}
@Override
public void onItemLongClick(View view, int position) {
Toast.makeText(MainActivity.this,"Long Click "+mData.get(position),Toast.LENGTH_SHORT).show();
}
}));
5.操作数据以及分割线
5.1 操作数据
//该方法用于当增加一个数据的时候,position表示新增数据显示的位置
final void notifyItemInserted(int position)
//该方法用于删除一个数据的时候,position表示数据删除的位置
final void notifyItemRemoved(int position)
//该方法表示所在position对应的item位置不会改变,但是该item内容发生变化
final void notifyItemChanged(int position)
//该方法一般用于:适配器之前装载的数据大部分已经过时了,需要重新更新数据
//调用该方法的时候,recyclerView会重新计算子item及所有子item重新布局
//出于效率考虑,官方建议用更加精确的方法(比如上面三个方法)来取代这个方法
final void notifyDataSetChanged()
为了直观地看到各个方法的用法,我们对原有代码进行修改,看看具体的运行结果如何。首先对MyAdapter.java修改,新增三个方法:
//移除数据
public void removeData(int position) {
mDataSet.remove(position);
notifyItemRemoved(position);
}
//新增数据
public void addData(int position){
mDataSet.add(position,"Add One");
notifyItemInserted(position);
}
//更改某个位置的数据
public void changeData(int position){
mDataSet.set(position,"Item has changed"+ count++);
notifyItemChanged(position);
}
5.1 添加分割线
public class DividerItemDecoration 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;
private int mOrientation;
public DividerItemDecoration(Context context, int orientation) {
final TypedArray a = context.obtainStyledAttributes(ATTRS);
mDivider = a.getDrawable(0);
a.recycle();
setOrientation(orientation);
}
public void setOrientation(int orientation) {
if (orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST) {
throw new IllegalArgumentException("invalid orientation");
}
mOrientation = orientation;
}
@Override
public void onDraw(Canvas c, RecyclerView parent) {
if (mOrientation == VERTICAL_LIST) {
drawVertical(c, parent);
} else {
drawHorizontal(c, parent);
}
}
public void drawVertical(Canvas c, RecyclerView parent) {
final int left = parent.getPaddingLeft();
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);
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
.getLayoutParams();
final int top = child.getBottom() + params.bottomMargin;
final int bottom = top + mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
public 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;
final int right = left + mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
@Override
public void getItemOffsets(Rect outRect, int itemPosition, RecyclerView parent) {
if (mOrientation == VERTICAL_LIST) {
outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
} else {
outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
}
}
}
6. 滑动监听
ItemTouchHelper是一个处理RecyclerView的滑动删除和拖拽的辅助类,RecyclerView 的item拖拽移动和滑动删除就靠它来实现。
ItemTouchHelper 是系统为我们提供的一个用于滑动和删除 RecyclerView 条目的工具类,用起来也是非常简单的,大致两步:
- 创建 ItemTouchHelper 实例,同时实现 ItemTouchHelper.SimpleCallback 中的抽象方法,用于初始化 ItemTouchHelper
- 调用 ItemTouchHelper 的 attachToRecyclerView 方法关联上 RecyclerView 即可
ItemTouchHelper的源码如下:
itemTouchHelper=new ItemTouchHelper(new ItemTouchHelper.Callback() {
//用于设置拖拽和滑动的方向
@Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
int dragFlags=0,swipeFlags=0;
if(recyclerView.getLayoutManager() instanceof StaggeredGridLayoutManager||recyclerView.getLayoutManager() instanceof GridLayoutManager){
//网格式布局有4个方向
dragFlags=ItemTouchHelper.UP|ItemTouchHelper.DOWN|ItemTouchHelper.LEFT|ItemTouchHelper.RIGHT;
}else if(recyclerView.getLayoutManager() instanceof LinearLayoutManager){
//线性式布局有2个方向
dragFlags=ItemTouchHelper.UP|ItemTouchHelper.DOWN;
swipeFlags = ItemTouchHelper.START|ItemTouchHelper.END; //设置侧滑方向为从两个方向都可以
}
return makeMovementFlags(dragFlags,swipeFlags);//swipeFlags 为0的话item不滑动
}
//长摁item拖拽时会回调这个方法
@Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
int from=viewHolder.getAdapterPosition();
int to=target.getAdapterPosition();
Meizi moveItem=meizis.get(from);
meizis.remove(from);
meizis.add(to,moveItem);//交换数据链表中数据的位置
mAdapter.notifyItemMoved(from,to);//更新适配器中item的位置
return true;
}
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
//这里处理滑动删除
}
@Override
public boolean isLongPressDragEnabled() {
return false;//返回true则为所有item都设置可以拖拽
}
});
itemTouchHelper需要与recyclerView绑定才有效果,在recyclerView初始化的时候调用
itemTouchHelper.attachToRecyclerView(recyclerview);
如果你想为item设置拖拽和滑动时的响应动画效果,可以利用ItemTouchHelper的下面三个方法。用线性布局示例:
//当item拖拽开始时调用
@Override
public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
super.onSelectedChanged(viewHolder, actionState);
if(actionState==ItemTouchHelper.ACTION_STATE_DRAG){
viewHolder.itemView.setBackgroundColor(Color.LTGRAY);//拖拽时设置背景色为灰色
}
}
//当item拖拽完成时调用
@Override
public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
super.clearView(recyclerView, viewHolder);
viewHolder.itemView.setBackgroundColor(Color.WHITE);//拖拽停止时设置背景色为白色
}
//当item视图变化时调用
@Override
public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
//根据item滑动偏移的值修改item透明度。screenwidth是我提前获得的屏幕宽度
viewHolder.itemView.setAlpha(1-Math.abs(dX)/screenwidth);
}
参考文献
来源:CSDN
作者:linghaoDo
链接:https://blog.csdn.net/linghaoDo/article/details/104016700