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);
}
}
});