RecyclerView使用進階
目前的項目中,基本已經使用 RecyclerView 全面替換了ListView,GridView. 使用RecyclerView確實更加靈活,功能也更加強大. RecyclerView的基本套路應該都很熟悉了,這裏整理一下一些相對進階一點的知識點,方便隨時復習.
分割線
雖然和ListView比較, RecyclerView 設置分割線麻煩了很多, 不過也更自由了,可以實現更多的效果.
RecyclerView 默認是沒有分割線的,需要通過下面這個方法添加
public void addItemDecoration(ItemDecoration decor) { addItemDecoration(decor, -1); }
那麽 ItemDecoration 又是什麽東西? ItemDecoration是 RecyclerView 的一個內部抽象類,很明顯,這個東西是給我們實現的. 當我們實現 ItemDecoration 的時候,只需要關註 3 個方法,說起來麻煩,直接看代碼和註釋.
public class ItemDivider extends RecyclerView.ItemDecoration { // 構造方法,可以在這裏做一些初始化,比如指定畫筆顏色什麽的 public ItemDivider() { } /** * 指定item之間的間距(就是指定分割線的寬度) 回調順序 1 * @param outRect Rect to receive the output. * @param view The child view to decorate * @param parent RecyclerView this ItemDecoration is decorating * @param state The current state of RecyclerView. */ @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { super.getItemOffsets(outRect, view, parent, state); } /** * 在item 繪制之前調用(就是繪制在 item 的底層) 回調順序 2 * 一般分割線在這裏繪制 * 看到canvas,對自定義控件有一定了解的話,就能想到為什麽說給RecyclerView設置分割線更靈活了 * @param c Canvas to draw into * @param parent RecyclerView this ItemDecoration is drawing into * @param state The current state of RecyclerView */ @Override public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { super.onDraw(c, parent, state); } /** * 在item 繪制之後調用(就是繪制在 item 的上層) 回調順序 3 * 也可以在這裏繪制分割線,和上面的方法 二選一 * @param c Canvas to draw into * @param parent RecyclerView this ItemDecoration is drawing into * @param state The current state of RecyclerView */ @Override public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) { super.onDrawOver(c, parent, state); } }
- getItemOffsets 指定item 之間的間距(默認為0),將來就是在這個間距內繪制分割線
- onDraw 在繪制 item之前執行,也就是說,在這裏繪制的圖形可能會被item遮蓋(所以需要指定item之間的間距)
- onDrawOver 在繪制item之後執行,在這裏繪制的圖形,可能會遮住item(說以如果要在這裏繪制分割線的話,也要找準位置)
PS:在 RecyclerView 25.0.0中,終於有了官方實現的分割線-DividerItemDecoration,可惜只支持 LinearLayoutManager ,感興趣的可以試試.
下面是我自己的實現,適配 LinearLayoutManager 和 GridLayoutManager
public class ItemDivider extends RecyclerView.ItemDecoration {
private int dividerWith = 1;
private Paint paint;
private RecyclerView.LayoutManager layoutManager;
// 構造方法,可以在這裏做一些初始化,比如指定畫筆顏色什麽的
public ItemDivider() {
initPaint();
paint.setColor(0xffff0000);
}
private void initPaint() {
if (paint == null) {
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setStyle(Paint.Style.FILL);
}
}
public ItemDivider setDividerWith(int dividerWith) {
this.dividerWith = dividerWith;
return this;
}
public ItemDivider setDividerColor(int color) {
initPaint();
paint.setColor(color);
return this;
}
/**
* 指定item之間的間距(就是指定分割線的寬度) 回調順序 1
* @param outRect Rect to receive the output.
* @param view The child view to decorate
* @param parent RecyclerView this ItemDecoration is decorating
* @param state The current state of RecyclerView.
*/
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
if (layoutManager == null) {
layoutManager = parent.getLayoutManager();
}
// 適用 LinearLayoutManager 和 GridLayoutManager
if (layoutManager instanceof LinearLayoutManager) {
int orientation = ((LinearLayoutManager) layoutManager).getOrientation();
if (orientation == LinearLayoutManager.VERTICAL) {
// 水平分割線將繪制在item底部
outRect.bottom = dividerWith;
} else if (orientation == LinearLayoutManager.HORIZONTAL) {
// 垂直分割線將繪制在item右側
outRect.right = dividerWith;
}
if (layoutManager instanceof GridLayoutManager) {
GridLayoutManager.LayoutParams lp = (GridLayoutManager.LayoutParams) view.getLayoutParams();
// 如果是 GridLayoutManager 則需要繪制另一個方向上的分割線
if (orientation == LinearLayoutManager.VERTICAL && lp != null && lp.getSpanIndex() > 0) {
// 如果列表是垂直方向,則最左邊的一列略過
outRect.left = dividerWith;
} else if (orientation == LinearLayoutManager.HORIZONTAL && lp != null && lp.getSpanIndex() > 0) {
// 如果列表是水平方向,則最上邊的一列略過
outRect.top = dividerWith;
}
}
}
}
/**
* 在item 繪制之前調用(就是繪制在 item 的底層) 回調順序 2
* 一般分割線在這裏繪制
* 看到canvas,對自定義控件有一定了解的話,就能想到為什麽說給RecyclerView設置分割線更靈活了
* @param c Canvas to draw into
* @param parent RecyclerView this ItemDecoration is drawing into
* @param state The current state of RecyclerView
*/
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDraw(c, parent, state);
// 這個值是為了補償橫豎方向上分割線交叉處間隙
int offSet = (int) Math.ceil(dividerWith * 1f / 2);
for (int i = 0; i < parent.getChildCount(); i++) {
View child = parent.getChildAt(i);
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
int left1 = child.getRight() + params.rightMargin;
int right1 = left1 + dividerWith;
int top1 = child.getTop() - offSet - params.topMargin;
int bottom1 = child.getBottom() + offSet + params.bottomMargin;
//繪制分割線(矩形)
c.drawRect(left1, top1, right1, bottom1, paint);
int left2 = child.getLeft() - offSet - params.leftMargin;
int right2 = child.getRight() + offSet + params.rightMargin;
int top2 = child.getBottom() + params.bottomMargin;
int bottom2 = top2 + dividerWith;
//繪制分割線(矩形)
c.drawRect(left2, top2, right2, bottom2, paint);
}
}
/**
* 在item 繪制之後調用(就是繪制在 item 的上層) 回調順序 3
* 也可以在這裏繪制分割線,和上面的方法 二選一
* @param c Canvas to draw into
* @param parent RecyclerView this ItemDecoration is drawing into
* @param state The current state of RecyclerView
*/
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDrawOver(c, parent, state);
}
}
使用方式
recyclerView.addItemDecoration(new ItemDivider().setDividerWith(2).setDividerColor(Color.BLUE));
看看效果
LinearLayoutManager
GridLayoutManager
掌握了分割線的原理,還可以做很多有意思的事.比如像列表分欄,在IOS中很容易做到讓當前欄目懸停的效果. 而Android中的常規做法,就是布局嵌套,在屏幕上面單獨方一個文本,然後監聽列表的滾動.....太麻煩了. 其實借助分割線的原理,可以更簡單實現這個效果.
基於組件化的思想,可以將這個功能封裝為一個單獨的控件
public class StickyRecyclerView extends RecyclerView {
private int lineHeight,titleHeight;
private int lineColor,titleColor,titleTextColor;
public StickyRecyclerView(Context context) {
this(context,null);
}
public StickyRecyclerView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs,0);
}
public StickyRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.StickyRecyclerView);
// 分割線的高度
lineHeight = array.getDimensionPixelOffset(R.styleable.StickyRecyclerView_dividerHeight,1);
// 分欄的高度
titleHeight = array.getDimensionPixelOffset(R.styleable.StickyRecyclerView_titleHeight,dip2px(context,35));
// 分割線顏色
lineColor = array.getColor(R.styleable.StickyRecyclerView_dividerColor,Color.LTGRAY);
// 分欄背景色
titleColor = array.getColor(R.styleable.StickyRecyclerView_titleColor,Color.LTGRAY);
// 分欄文字顏色
titleTextColor = array.getColor(R.styleable.StickyRecyclerView_titleTextColor,Color.BLUE);
array.recycle();
// 不用說,肯定是線性布局了,默認就實現
setLayoutManager(new LinearLayoutManager(context));
}
@Deprecated
@Override
public void setAdapter(Adapter adapter) {
super.setAdapter(adapter);
}
// 讓 adapter 必須繼承 StickyAdapter
public void setAdapter(@NonNull StickyAdapter stickyAdapter){
addItemDecoration(new StickyDivider(stickyAdapter));
super.setAdapter(stickyAdapter);
}
/**
* 自定義分割線,通過分割線繪制title
*/
private class StickyDivider extends ItemDecoration{
private StickyAdapter adapter;
private Paint paint;
StickyDivider(@NonNull StickyAdapter adapter) {
super();
this.adapter = adapter;
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setStyle(Paint.Style.FILL);
paint.setTextSize(titleHeight * 0.5f);
}
/**
* 計算 item間間隙(是普通分割線 ,還是title)
*/
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state) {
super.getItemOffsets(outRect, view, parent, state);
if(!adapter.needTitle(((LayoutParams) view.getLayoutParams()).getViewLayoutPosition())){
outRect.top = lineHeight;
}else {
outRect.top = titleHeight;
}
}
/**
* 底層繪制,繪制分欄title
*/
@Override
public void onDraw(Canvas c, RecyclerView parent, State state) {
super.onDraw(c, parent, state);
int left = parent.getPaddingLeft();
int right = parent.getMeasuredWidth() - parent.getPaddingRight();
final int childCount = parent.getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = parent.getChildAt(i);
int position = ((LayoutParams) child.getLayoutParams()).getViewLayoutPosition();
int bottom = child.getTop() - ((LayoutParams) child.getLayoutParams()).topMargin;
if(!adapter.needTitle(position)){
// 畫分割線
int top = bottom - lineHeight;
paint.setColor(lineColor);
c.drawRect(left, top, right, bottom, paint);
}else {
//畫TITLE
int top = bottom - titleHeight;
paint.setColor(titleColor);
c.drawRect(left, top, right, bottom, paint);
drawText(c,adapter.getItemViewTitle(position),left + titleHeight * 0.25f,bottom - titleHeight * 0.25f);
}
}
}
/**
* 上層繪制,繪制頂部懸停title
*/
@Override
public void onDrawOver(Canvas c, RecyclerView parent, State state) {
super.onDrawOver(c, parent, state);
// 懸停title
int left = parent.getPaddingLeft();
int right = parent.getMeasuredWidth() - parent.getPaddingRight();
int top = parent.getPaddingTop();
int bottom = top + titleHeight;
paint.setColor(titleColor);
c.drawRect(left,top,right,bottom,paint);
int pos = ((LinearLayoutManager)(parent.getLayoutManager())).findFirstVisibleItemPosition();
drawText(c,adapter.getItemViewTitle(pos),left + titleHeight * 0.25f,bottom - titleHeight * 0.25f);
}
void drawText(Canvas c, String itemViewTitle, float x, float y){
if(!TextUtils.isEmpty(itemViewTitle)){
paint.setColor(titleTextColor);
//paint.getTextBounds(itemViewTitle, 0, itemViewTitle.length(), mBounds);
c.drawText(itemViewTitle, x,y, paint);
}
}
}
public static abstract class StickyAdapter extends Adapter{
// 獲取當前 item 的標題
public abstract String getItemViewTitle(int position);
// 如果標題和前面的item的標題一樣,就不需要繪制
boolean needTitle(int position){
return position > -1 && (position == 0 || !getItemViewTitle(position).equals(getItemViewTitle(position - 1)));
}
}
public int dip2px(Context context, float dpValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
}
大致流程就是通過底層分割線繪制各個分欄,通過頂層分割線繪制頂部懸停的那一欄,具體可以看下註釋.
使用方式和普通RecyclerView 差不多:
stickyRecyclerView.setAdapter(myAdapter);
//關鍵一:繼承關系
private class MyAdapter extends StickyRecyclerView.StickyAdapter {
.....
//關鍵二:重寫該方法,返回當前item的標題
@Override
public String getItemViewTitle(int position) {
return String.valueOf(datas.get(position).shuruma.charAt(0));
}
}
其中,分欄背景色,高度,文字顏色,以及分割線顏色和高度都是可以通過自定義屬性設置的.
不規則布局
網格布局很常見,但是不規則的網格布局也不少見.比如要實現下面這個效果
上面是網格,下面又變成列表,在以前的做法可能是給ListView添加一個頭部,頭部裏面放GridView,甚至是ScrollView嵌套等等.做過的同學肯定知道有多少坑在裏面. 而使用 RecyclerView ,可以做很大程度的簡化,並且很容易就能實現更復雜的布局.
RecyclerView 可以通過 GridLayoutManager 實現網格布局.而要實現上面的效果,關鍵就在 GridLayoutManager上, GridLayoutManager 可以設置網格的列數,而通過下面的方法,可以指定每一個item占據的列數.
gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
// 這裏的返回值,表示下標為position的item 占據多少列
return 1;
}
});
通過下面這個例子看起來更加直觀:
gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
//這裏只是一個例子,實際中要根據需求來設置
if(position % 5 == 0){
return 4;
}else if(position % 5 == 1){
return 3;
}else if(position % 5 == 2){
return 1;
}else{
return 2;
}
}
});
不規則布局
關於不規則布局的內容不多,這裏再補充一個例子. RecyclerView分頁加載, Google官方以及一些第三方的下拉刷新控件都不支持分頁功能,因為分頁功能應該讓列表自己去實現. 而目前的列表基本都可以使用RecyclerView完成,所以如果能做個統一封裝就方便多了(這裏就和 SwipeRefreshLayout封裝在一起了,順便解決 SwipeRefreshLayout 的坑).
public class SuperRefreshLayout extends SwipeRefreshLayout {
private static OnRefreshHandler onRefreshHandler;
private static boolean isRefresh = false;
private Adapter adapter;
private int mTouchSlop;
private float mPrevX;
public SuperRefreshLayout(Context context) {
this(context, null);
}
public SuperRefreshLayout(Context context, AttributeSet attrs) {
super(context, attrs);
setColorSchemeColors(0xff3b93eb);
setProgressBackgroundColorSchemeColor(0xffffffff);
float scale = context.getResources().getDisplayMetrics().density;
setProgressViewEndTarget(true, (int) (64 * scale + 0.5f));
//refreshLayout.setProgressViewOffset(false,dip2px(this,-40),dip2px(this,64));
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
}
/**
* 監聽器
*/
public void setOnRefreshHandler(OnRefreshHandler handler) {
onRefreshHandler = handler;
super.setOnRefreshListener(new OnRefreshCallBack());
}
/**
* 自動刷新,原生不支持,通過反射修改字段屬性
*/
public void autoRefresh() {
try {
setRefreshing(true);
Field field = SwipeRefreshLayout.class.getDeclaredField("mNotify");
field.setAccessible(true);
field.set(this, true);
} catch (Exception e) {
if(onRefreshHandler != null){
onRefreshHandler.refresh();
}
}
}
@Override
public void setRefreshing(boolean refreshing) {
super.setRefreshing(refreshing);
isRefresh = isRefreshing();
}
/**
* 加載完畢
* @param hasMore 是否還有下一頁
*/
public void loadComplete(boolean hasMore){
if(adapter == null){
throw new RuntimeException("must call method setAdapter to bind data");
}
adapter.setState(hasMore ? Adapter.STATE_MORE : Adapter.STATE_END);
}
/**
* 加載出錯
*/
public void loadError(){
if(adapter == null){
throw new RuntimeException("must call method setAdapter to bind data");
}
adapter.setState(Adapter.STATE_ERROR);
}
/**
* 只支持 RecyclerView 加載更多,且需要通過此方法設置適配器
*/
public void setAdapter(@NonNull RecyclerView recyclerView,@NonNull SuperRefreshLayout.Adapter mAdapter) {
adapter = mAdapter;
recyclerView.setAdapter(adapter);
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (onRefreshHandler != null
&& !isRefreshing()
&& (adapter.getState() == Adapter.STATE_MORE || adapter.getState() == Adapter.STATE_ERROR)
&& newState == RecyclerView.SCROLL_STATE_IDLE
&& !ViewCompat.canScrollVertically(recyclerView, 1)
) {
adapter.setState(Adapter.STATE_LOAIND);
onRefreshHandler.loadMore();
}
}
});
}
/**
* 如果滑動控件嵌套過深,可通過該方法控制是否可以下拉
*/
public void setRefreshEnable(boolean enable){
// boolean e = !ViewCompat.canScrollVertically(scrollView,-1);
if(isEnabled() && !enable){
setEnabled(false);
}else if(!isEnabled() && enable){
setEnabled(true);
}
}
/**
* 解決水平滑動沖突
*/
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mPrevX = MotionEvent.obtain(event).getX();
break;
case MotionEvent.ACTION_MOVE:
final float eventX = event.getX();
float xDiff = Math.abs(eventX - mPrevX);
if (xDiff > mTouchSlop) {
return false;
}
}
return super.onInterceptTouchEvent(event);
}
private class OnRefreshCallBack implements OnRefreshListener {
@Override
public void onRefresh() {
if(adapter != null && adapter.getState() != Adapter.STATE_MORE){
adapter.setState(Adapter.STATE_MORE);
}
if(onRefreshHandler != null){
onRefreshHandler.refresh();
}
}
}
public static abstract class OnRefreshHandler{
public abstract void refresh();
public void loadMore() {
}
}
/**
* 支持加載更多的適配器
*/
public static abstract class Adapter extends RecyclerView.Adapter {
static final int STATE_MORE = 0, STATE_LOAIND = 1, STATE_END = 2, STATE_ERROR = 3;
int state = STATE_MORE;
public void setState(int state) {
if (this.state != state) {
this.state = state;
notifyItemChanged(getItemCount() - 1);
}
}
public int getState() {
return state;
}
@Override
public int getItemViewType(int position) {
if (position == getItemCount() - 1) {
return -99;
}
return getItemType(position);
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == -99) {
return new RecyclerView.ViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.loadmore_default_footer, parent, false)) {};
} else {
return onCreateItemHolder(parent, viewType);
}
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (getItemViewType(position) == -99) {
ProgressBar progressBar = (ProgressBar) holder.itemView.findViewById(R.id.loadmore_default_footer_progressbar);
TextView textView = (TextView) holder.itemView.findViewById(R.id.loadmore_default_footer_tv);
if (state == STATE_END) {
progressBar.setVisibility(View.GONE);
textView.setText("沒有更多了");
} else if (state == STATE_MORE) {
progressBar.setVisibility(View.GONE);
textView.setText("點擊加載");
} else if (state == STATE_LOAIND) {
progressBar.setVisibility(View.VISIBLE);
textView.setText("加載中...");
} else if (state == STATE_ERROR) {
progressBar.setVisibility(View.GONE);
textView.setText("加載失敗,點擊重新加載");
}
holder.itemView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
if (onRefreshHandler != null && !isRefresh && (state == STATE_MORE || state == STATE_ERROR)) {
setState(STATE_LOAIND);
onRefreshHandler.loadMore();
}
}
});
} else {
onBindItemHolder(holder,position);
}
}
@Override
public int getItemCount() {
return getCount() == 0 ? 0 : getCount() + 1;
}
public int getItemType(int position){
return super.getItemViewType(position);
}
public abstract RecyclerView.ViewHolder onCreateItemHolder(ViewGroup parent, int viewType);
public abstract void onBindItemHolder(RecyclerView.ViewHolder holder, int position);
public abstract int getCount();
@Override
public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) {
// 處理瀑布流模式 最後的 item 占整行
if (holder.getLayoutPosition() == getItemCount() - 1) {
LayoutParams lp = holder.itemView.getLayoutParams();
if (lp != null && lp instanceof StaggeredGridLayoutManager.LayoutParams) {
StaggeredGridLayoutManager.LayoutParams p = (StaggeredGridLayoutManager.LayoutParams) lp;
p.setFullSpan(true);
}
}
}
@Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
// 處理網格布局模式 最後的 item 占整行
final RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
if (layoutManager instanceof GridLayoutManager) {
GridLayoutManager gridManager = ((GridLayoutManager) layoutManager);
final GridLayoutManager.SpanSizeLookup spanSizeLookup = gridManager.getSpanSizeLookup();
final int lastSpanCount = gridManager.getSpanCount();
gridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
return position == getItemCount() - 1 ? lastSpanCount :
(spanSizeLookup == null ? 1 : spanSizeLookup.getSpanSize(position));
}
});
}
}
}
}
整體思路就是給RecyclerView在末尾添加了一個item,並且必要保證這個item占據整行. 所以需要處理兩種情況:
-
StaggeredGridLayoutManager
StaggeredGridLayoutManager.LayoutParams p = (StaggeredGridLayoutManager.LayoutParams) lp; //設置為占滿整行 p.setFullSpan(true);
-
GridLayoutManager
gridManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { @Override public int getSpanSize(int position) { return position == getItemCount() - 1 ? lastSpanCount : (spanSizeLookup == null ? 1 : spanSizeLookup.getSpanSize(position)); } });
所以利用不規則布局就可以讓RecyclerView支持分頁功能了.
拖動排序和滑動刪除
RecyclerView的拖動拍和滑動刪除需要靠 ItemTouchHelper 這個類來支持, ItemTouchHelper 有個內部抽象類 Callback ,實現這個類可以讓我們定義相關規則,以及處理回調事件.直接看代碼,每個方法都有註釋:
public class MyItemTouchHandler extends ItemTouchHelper.Callback {
ItemTouchAdapterImpl adapter;
public MyItemTouchHandler(@NonNull ItemTouchAdapterImpl adapter) {
this.adapter = adapter;
}
/**
* 設置 允許拖拽和滑動刪除的方向
*/
@Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
// 指定可 拖拽方向 和 滑動消失的方向
int dragFlags,swipeFlags;
RecyclerView.LayoutManager manager = recyclerView.getLayoutManager();
if (manager instanceof GridLayoutManager || manager instanceof StaggeredGridLayoutManager) {
// 上下左右都可以拖動
dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
} else {
// 可以上下拖動
dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
}
// 可以左右方向滑動消失
swipeFlags = ItemTouchHelper.START | ItemTouchHelper.END;
// 如果某個值傳 0 , 表示不支持該功能
return makeMovementFlags(dragFlags, swipeFlags);
}
/**
* 拖拽後回調,一般通過接口暴露給adapter, 讓adapter去處理數據的交換
*/
@Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
// 相同 viewType 之間才能拖動交換
if (viewHolder.getItemViewType() == target.getItemViewType()) {
int fromPosition = viewHolder.getAdapterPosition();
int toPosition = target.getAdapterPosition();
if (fromPosition < toPosition) {
//途中所有的item位置都要移動
for (int i = fromPosition; i < toPosition; i++) {
adapter.onItemMove(i, i + 1);
}
} else {
for (int i = fromPosition; i > toPosition; i--) {
adapter.onItemMove(i, i - 1);
}
}
adapter.notifyItemMoved(fromPosition, toPosition);
return true;
}
return false;
}
/**
* 滑動刪除後回調,一般通過接口暴露給adapter, 讓adapter去刪除該條數據
*/
@Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
// 刪除數據
adapter.onItemRemove(viewHolder.getAdapterPosition());
// adapter 刷新
adapter.notifyItemRemoved(viewHolder.getAdapterPosition());
}
@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);
if(actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
//滑動時改變Item的透明度
final float alpha = 1 - Math.abs(dX) / (float)viewHolder.itemView.getWidth();
viewHolder.itemView.setAlpha(alpha);
viewHolder.itemView.setTranslationX(dX);
}
}
/**
* item被選中(長按)
* 這裏改變了 item的背景色, 也可以通過接口暴露, 讓adapter去處理邏輯
*/
@Override
public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
if (actionState == ItemTouchHelper.ACTION_STATE_DRAG) {
// 拖拽狀態
viewHolder.itemView.setBackgroundColor(Color.BLUE);
}else if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
// 滑動刪除狀態
viewHolder.itemView.setBackgroundColor(Color.RED);
}
super.onSelectedChanged(viewHolder, actionState);
}
/**
* item取消選中(取消長按)
* 這裏改變了 item的背景色, 也可以通過接口暴露, 讓adapter去處理邏輯
*/
@Override
public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
viewHolder.itemView.setBackgroundColor(Color.TRANSPARENT);
super.clearView(recyclerView, viewHolder);
}
/**
* 是否支持長按開始拖拽,默認開啟 * 可以不開啟,然後在長按 item 的時候,手動 調用 mItemTouchHelper.startDrag(myHolder) 開啟,更加靈活
*/
@Override
public boolean isLongPressDragEnabled() {
return adapter.autoOpenDrag();
}
/**
* 是否支持滑動刪除,默認開啟 * 可以不開啟,然後在長按 item 的時候,手動 調用 mItemTouchHelper.startSwipe(myHolder) 開啟,更加靈活
*/
@Override
public boolean isItemViewSwipeEnabled() {
return adapter.autoOpenSwipe();
}
// 建議讓 adapter 實現該接口
public static abstract class ItemTouchAdapterImpl extends RecyclerView.Adapter{
public abstract void onItemMove(int fromPosition, int toPosition);
public abstract void onItemRemove(int position);
// 是否自動開啟拖拽
protected boolean autoOpenDrag(){
return true;
}
// 是否自動開啟滑動刪除
protected boolean autoOpenSwipe(){
return true;
}
}
}
使用方式
new ItemTouchHelper(new MyItemTouchHandler(myAdapter)).attachToRecyclerView(recyclerView);
...
private class MyAdapter extends MyItemTouchHandler.ItemTouchAdapterImpl{
...
@Override
public void onItemMove(int fromPosition, int toPosition) {
// 拖動排序的回調,這裏交換集合中數據的位置
Collections.swap(str, fromPosition, toPosition);
}
@Override
public void onItemRemove(int position) {
// 滑動刪除的回調,這裏刪除指定的數據
}
}
拖動排序
滑動刪除
本文Demo
另外還有個使用RecyclerView模仿ViewPager的例子,在這裏
RecyclerView使用進階