1.为RecyclerView添加分割线
RecyclerView添加分割线可以在item的布局中设置,这里通过实现ItemDecoration来实现分割线效果。
思路:
a.创建一个类继承RecyclerView.ItemDecoration,主要重写两个方法getItemOffsets(..)和onDraw(…),
b.getItemOffsets(…)确定的是item四边的偏移量,onDraw(…)就是绘制分割线
/**
* RecyclerView的LinearLayoutManager分割线
*/
public class RecyclerViewLinearDivider extends RecyclerView.ItemDecoration {
private Drawable mDivider;
private int mDividerHeight = 2;//分割线高度,默认是2px
private int mOrientation;//列表方向:LinearLayoutManager.VERTICAL 或LinearLayoutManager.Horienzontal
private int mDividerHeaderMargin;
private int mDividerFooterMargin;
/**
* 自定义分割线
*
* @param context
* @param orientation 列表方向
* @param drawableId 分割线drawable资源
* @param headerMargin 分割线距离头的距离 单位:像素
* @param footerMargin 分割线距离尾的距离 单位:像素
*/
public RecyclerViewLinearDivider(Context context, int orientation, @DrawableRes int drawableId, int headerMargin, int footerMargin) {
if (orientation != LinearLayoutManager.HORIZONTAL && orientation != LinearLayoutManager.VERTICAL) {
throw new IllegalArgumentException("orientation must be LinearLayoutManager.HORIZONTAL or LinearLayoutManager.VERTICAL");
}
mOrientation = orientation;
mDivider = ContextCompat.getDrawable(context, drawableId);
mDividerHeight = Math.abs(mDivider.getIntrinsicHeight());//返回drawable的内在高度。
mDividerHeaderMargin = headerMargin;
mDividerFooterMargin = footerMargin;
}
//设置四个方向的偏移量,根据位置索引可以指定view的偏移量。这里是当时画横线时,bottom的偏移量就是分割线的高度,也就是上下item的间隔,当画纵线时,right的偏移量就是分割线的厚度,也就是左右item的间隔
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView recyclerView, RecyclerView.State state) {
int itemPosition = ((RecyclerView.LayoutParams) view.getLayoutParams()).getViewLayoutPosition();
if (mOrientation == LinearLayoutManager.VERTICAL) {
outRect.set(0, 0, mDividerHeight, 0);
} else {
outRect.set(0, 0, 0, mDividerHeight);
}
}
//绘制分割线
@Override
public void onDraw(Canvas c, RecyclerView recyclerView, RecyclerView.State state) {
if (mOrientation == LinearLayoutManager.VERTICAL) {
drawVertical(c, recyclerView);
} else {
drawHorizontal(c, recyclerView);
}
}
//绘制横向item分割线
private void drawHorizontal(Canvas c, RecyclerView recyclerView) {
int left = recyclerView.getPaddingLeft();
int right = recyclerView.getMeasuredWidth() - recyclerView.getPaddingRight();
int childSize = recyclerView.getChildCount();
for (int i = 0; i < childSize; i++) {
View child = recyclerView.getChildAt(i);
RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) child.getLayoutParams();
int top = child.getBottom() + layoutParams.bottomMargin;
int bottom = top + mDividerHeight;
if (mDivider != null) {
mDivider.setBounds(left + mDividerHeaderMargin, top, right - mDividerFooterMargin, bottom);
mDivider.draw(c);
}
}
}
//绘制纵向item分割线
private void drawVertical(Canvas canvas, RecyclerView recyclerView) {
int top = recyclerView.getPaddingTop();
int bottom = recyclerView.getMeasuredHeight() - recyclerView.getPaddingBottom();
int childSize = recyclerView.getChildCount();
for (int i = 0; i < childSize; i++) {
View child = recyclerView.getChildAt(i);
RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) child.getLayoutParams();
int left = child.getRight() + layoutParams.rightMargin;
int right = left + mDividerHeight;
if (mDivider != null) {
mDivider.setBounds(left, top + mDividerHeaderMargin, right, bottom - mDividerFooterMargin);
mDivider.draw(canvas);
}
}
}
}
/**
* RecyclerView的GridLayoutManager的分割线
*/
public class RecyclerViewGridDivider extends RecyclerView.ItemDecoration {
private static final String TAG = RecyclerViewGridDivider.class.getName();
private Drawable mDivider;
private int mDividerHeight = 2;//分割线高度,默认是2px
public RecyclerViewGridDivider(Context context, @DrawableRes int drawableId) {
mDivider = ContextCompat.getDrawable(context, drawableId);
mDividerHeight = Math.abs(mDivider.getIntrinsicHeight());//返回drawable的内在高度。
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
if (((GridLayoutManager) parent.getLayoutManager()).getOrientation() != GridLayoutManager.VERTICAL) {
throw new RuntimeException("GridLayoutManager's oritention must be GridLayoutManager.VERTICAL");
}
int itemPosition = ((RecyclerView.LayoutParams) view.getLayoutParams()).getViewLayoutPosition();
int spanCount = getSpanCount(parent);//列数
int childCount = parent.getAdapter().getItemCount();
int left = 0;
@SuppressWarnings("SuspiciousNameCombination")
int right = mDividerHeight;
int top ;
int bottom = mDividerHeight;
if (isfirstRow(parent, itemPosition, spanCount, childCount)) {//如果是第一行,这里做的是第一行距离顶端是分割线的高度
top = mDividerHeight;
} else {
top = 0;
}
if (isLastRow(parent, itemPosition, spanCount, childCount)) {//如果是最后一行,item底端的偏移量置为0,不再绘制分割线
bottom = 0;
}
if (isRightMost(parent, itemPosition, spanCount, childCount)) {//如果是最右边的一列,item的右边偏移量置为0,不再绘制分割线
right = 0;
}
outRect.set(left, top, right, bottom);
}
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDraw(c, parent, state);
draw(c, parent);
}
//绘制横向 item 分割线
private void draw(Canvas canvas, RecyclerView parent) {
int childSize = parent.getChildCount();
for (int i = 0; i < childSize; i++) {
View child = parent.getChildAt(i);
RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) child.getLayoutParams();
int left;
int right;
int top;
int bottom;
//画水平分隔线
left = child.getLeft();
right = child.getRight();
top = child.getBottom() + layoutParams.bottomMargin;
bottom = top + mDividerHeight;
if (mDivider != null) {
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(canvas);
}
//画垂直分割线
top = child.getTop();
bottom = child.getBottom() + mDividerHeight;
left = child.getRight() + layoutParams.rightMargin;
right = left + mDividerHeight;
if (mDivider != null) {
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(canvas);
}
}
}
/**
* 判断是否是最后一行
*
* @param parent
* @param pos
* @param spanCount
* @param childCount
* @return
*/
private boolean isLastRow(RecyclerView parent, int pos, int spanCount, int childCount) {
RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
if (layoutManager instanceof GridLayoutManager) {
int lines = childCount % spanCount == 0 ? childCount / spanCount : childCount / spanCount + 1;
return lines == pos / spanCount + 1;
} else {
throw new RuntimeException("LayoutManager must be GridLayoutManager");
}
}
/**
* 判断是否是第一行
*
* @param parent
* @param pos
* @param spanCount
* @param childCount
* @return
*/
private boolean isfirstRow(RecyclerView parent, int pos, int spanCount, int childCount) {
RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
if (layoutManager instanceof GridLayoutManager) {
int lines = childCount % spanCount == 0 ? childCount / spanCount : childCount / spanCount + 1;//得到有多少行
if ((pos / spanCount) == 0) {//只有第一行的position<spanCount,其比值的int值一定是0
return true;
} else {
return false;
}
} else {
throw new RuntimeException("LayoutManager must be GridLayoutManager");
}
}
/**
* 是否是最右边的一列
*
* @param parent
* @param pos
* @param spanCount
* @param childCount
* @return
*/
private boolean isRightMost(RecyclerView parent, int pos, int spanCount, int childCount) {
RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
if (layoutManager instanceof GridLayoutManager) {
if ((pos % spanCount) == (spanCount - 1)) {
return true;
} else {
return false;
}
} else {
throw new RuntimeException("LayoutManager must be GridLayoutManager");
}
}
/**
* 获取列数
*
* @param parent
* @return
*/
private int getSpanCount(RecyclerView parent) {
int spanCount = -1;
RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
if (layoutManager instanceof GridLayoutManager) {
spanCount = ((GridLayoutManager) layoutManager).getSpanCount();
} else {
throw new RuntimeException("LayoutManager must be GridLayoutManager");
}
return spanCount;
}
}
2.RecyclerView条目点击事件的优化
RecyclerView点击事件很多的做法就是对item每一个都设置点击监听,明显是很消耗性能的方式,这里通过触摸监听来实现点击事件的监听,进行优化,adapter只负责数据的绑定
思路:
1.为RecyclerView添加手势触摸的监听
2.重写点击事件的处理
3.OnRecyclerItemClickListener将别点击的View的id和ViewHolder对象传递给RecyclerView,然后根据业务做处理
OnGestureListener主要回调各种单击事件,必须实现所有方法
OnDoubleTapListener主要回调各种双击事件,必须实现所有方法
SimpeOnGestureListener实现了上面两个接口,都是空方法
OnGestureListener:
//用户按下屏幕就会触发
public boolean onDown(MotionEvent e);
//如果是按下的时间超过瞬间,而且在按下的时候没有松开或者是拖动的,那么onShowPress就会执行
public void onShowPress(MotionEvent e);
//一次单独的轻击抬起操作,也就是轻击一下屏幕,就是普通点击事件
public boolean onSingleTapUp(MotionEvent e);
//在屏幕上拖动事件
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY);
//长按触摸屏,超过一定时长,就会触发这个事件
public void onLongPress(MotionEvent e);
//滑屏,用户按下触摸屏、快速移动后松开
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY);
OnDoubleTapListener:
//单击事件。用来判定该次点击是SingleTap而不是DoubleTap,
//如果连续点击两次就是DoubleTap手势,如果只点击一次,
//系统等待一段时间后没有收到第二次点击则判定该次点击为SingleTap而不是DoubleTap,
//然后触发SingleTapConfirmed事件
public boolean onSingleTapConfirmed(MotionEvent e);
//双击事件
public boolean onDoubleTap(MotionEvent e);
//双击间隔中发生的动作。指触发onDoubleTap以后,在双击之间发生的其它动作
public boolean onDoubleTapEvent(MotionEvent e);
/**
* 实例化手势探测器,需要一个手势监听器:OnGestureListener,
* OnGestureListener主要回调各种单击事件,必须实现所有方法
* OnDoubleTapListener主要回调各种双击事件,必须实现所有方法
* SimpeOnGestureListener实现了上面两个接口,都是空方法
*/
public abstract class OnRecyclerItemClickListener implements RecyclerView.OnItemTouchListener {
private static final String TAG = OnRecyclerItemClickListener.class.getName();
private GestureDetectorCompat mGestureDetector;
private RecyclerView recyclerView;
public OnRecyclerItemClickListener(RecyclerView recyclerView) {
this.recyclerView = recyclerView;
mGestureDetector = new GestureDetectorCompat(recyclerView.getContext(), new ItemTouchHelperGestureListener());
}
public abstract void onItemClick(int viewId, RecyclerView.ViewHolder viewHolder);
public abstract void onItemLongClick(RecyclerView.ViewHolder vh);
@Override
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
mGestureDetector.onTouchEvent(e);//解析坐标点和触摸规律来识别触摸手势
return false;
}
@Override
public void onTouchEvent(RecyclerView rv, MotionEvent e) {
mGestureDetector.onTouchEvent(e);//解析坐标点和触摸规律来识别触摸手势
}
@Override//用来处理触摸冲突
public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
}
private class ItemTouchHelperGestureListener extends GestureDetector.SimpleOnGestureListener {
@Override//点击事件处理
public boolean onSingleTapUp(MotionEvent e) {
final View itemRecycler = recyclerView.findChildViewUnder(e.getX(), e.getY());//获取被点击item的view
if (itemRecycler instanceof ViewGroup) {
ViewGroup itemView = (ViewGroup) itemRecycler;
for (int i = 0; i < itemView.getChildCount(); i++) {
itemView.getChildAt(i).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
onItemClick(v.getId(), recyclerView.getChildViewHolder(itemRecycler));
}
});
}
} else {
itemRecycler.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (itemRecycler != null) {
onItemClick(itemRecycler.getId(), recyclerView.getChildViewHolder(itemRecycler));
}
}
});
}
return true;
}
@Override
public void onLongPress(MotionEvent e) {
View child = recyclerView.findChildViewUnder(e.getX(), e.getY());
if (child != null) {
RecyclerView.ViewHolder vh = recyclerView.getChildViewHolder(child);
onItemLongClick(vh);
}
}
}
}
mRecycler.addOnItemTouchListener(new OnRecyclerItemClickListener(mRecycler) {
@Override
public void onItemClick(int viewId, RecyclerView.ViewHolder viewHolder) {
int position = viewHolder.getAdapterPosition();
MyAdapter.ViewHolder myViewHolder = (MyAdapter.ViewHolder) viewHolder;
switch (viewId){
case R.id.tv:
Toast.makeText(MainActivity.this,"第"+position+"个条目的TextView被点击"+myViewHolder.tv.getText(),Toast.LENGTH_SHORT).show();
break;
case R.id.img:
Toast.makeText(MainActivity.this,"第"+position+"个条目的ImageView被点击",Toast.LENGTH_SHORT).show();
break;
}
}
@Override
public void onItemLongClick(RecyclerView.ViewHolder vh) {
}
});
3.RecyclerView实现拖拽效果
/**
* RecyclerView实现拖拽的帮助类,(swipe方式侧滑删除与拖拽同时存在时,会有冲突,这里只有拖拽)
*/
public class RecyclerViewDragHelper extends ItemTouchHelper.Callback {
private ItemTouchAdapter mItemTouchAdapter;
private OnDragListener mOnDragListener;
public RecyclerViewDragHelper(ItemTouchAdapter itemTouchAdapter) {
this.mItemTouchAdapter = itemTouchAdapter;
}
@Override//设置recyclerview是否支持长按拖拽,默认是true,如果这里返回true,所有的Item都支持拖拽,如果有特定的item不支持拖拽,这里就要返回false,在长按的状态下进行处理
public boolean isLongPressDragEnabled() {
return false;
}
@Override//设置item是否可以侧滑删除
public boolean isItemViewSwipeEnabled() {
return true;
}
@Override//设置是否处理拖拽事件和滑动事件以及拖拽和滑动操作的方向
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
if (layoutManager instanceof GridLayoutManager){
int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;//拖拽标志
int swipeFlags = 0;//滑动标志,设置为0,不处理滑动操作
return makeMovementFlags(dragFlags,swipeFlags);
}else if (layoutManager instanceof LinearLayoutManager){
int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
int swipeFlags = 0 ;//列表可以实现侧滑删除,这里用0 处理,侧滑删除和拖拽都需要长时间按住,所以手势识别有冲突
return makeMovementFlags(dragFlags,swipeFlags);
}else {
throw new IllegalArgumentException("LayoutManager must be GridLayoutManager or LinearLayoutManager");
}
}
@Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
int fromPosition = viewHolder.getAdapterPosition();//获取拖动viewholder的position
int toPosition = target.getAdapterPosition();//获取目标viewholder的position
mItemTouchAdapter.onMove(fromPosition,toPosition);
return true;
}
@Override//侧滑删除时调用,这里没有使用
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
int position = viewHolder.getAdapterPosition();
mItemTouchAdapter.onSwiped(position);
}
@Override
public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) {
if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE){
float alpha = 1 - Math.abs(dX)/(float)viewHolder.itemView.getWidth();
viewHolder.itemView.setAlpha(alpha);
viewHolder.itemView.setTranslationX(dX);
}else {
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
}
}
@Override//当长按选中item的时候调用(拖拽的时候)
public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
if (actionState == ItemTouchHelper.ACTION_STATE_DRAG ){
viewHolder.itemView.setBackgroundColor(viewHolder.itemView.getContext().getResources().getColor(R.color.md_red_A400));
}
}
@Override//当手指松开时调用
public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
super.clearView(recyclerView, viewHolder);
viewHolder.itemView.setBackgroundColor(viewHolder.itemView.getContext().getResources().getColor(R.color.md_red_A200));
if (mOnDragListener != null) mOnDragListener.onFinishDrag();
}
public RecyclerViewDragHelper setOnDragListener(OnDragListener onDragListener){
this.mOnDragListener = onDragListener;
return this;
}
public interface OnDragListener{
void onFinishDrag();
}
public interface ItemTouchAdapter{
void onMove(int fromPosition,int toPosition);//拖拽
void onSwiped(int position);//侧滑删除
}
}
//Adapter要实现拖拽帮助类的接口,拖拽的接口,
private class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> implements RecyclerViewDragHelper.ItemTouchAdapter{
...
@Override
public void onMove(int fromPosition, int toPosition) {
if (fromPosition == mList.size() -1 || toPosition == mList.size() -1){
return;
}
if (fromPosition < toPosition){
for (int i = fromPosition; i < toPosition; i++) {
Collections.swap(mList,i,i + 1);
}
}else {
for (int i = fromPosition; i > toPosition ; i--) {
Collections.swap(mList,i,i -1);
}
}
notifyItemMoved(fromPosition,toPosition);
}
...
}
//RecyclerView的监听处理
mRecycler.addOnItemTouchListener(new OnRecyclerItemClickListener(mRecycler) {
@Override
public void onItemClick(RecyclerView.ViewHolder vh) {
int position = vh.getLayoutPosition();
Toast.makeText(MainActivity.this,"第"+position+"被点击了",Toast.LENGTH_SHORT).show();
}
@Override
public void onItemLongClick(RecyclerView.ViewHolder vh) {
int childCount = ((RecyclerView)vh.itemView.getParent()).getAdapter().getItemCount();
if (vh.getLayoutPosition() != childCount -1){//最后一个不能拖动
mDragHelper.startDrag(vh);//可以拖动
VibratorUtil.vibrate(MainActivity.this,70);
}
}
});