前言
RecyclerView依靠ItemTouchHelper,實現item的拖動與滑動功能。
了解重寫方法
ItemTouchHelper提供了大量的重寫方法,讓你自己實現需要的組合。需要一一了解。
是否開啟長按拖動
@Override public boolean isLongPressDragEnabled() { return false; }
是否開啟ItemView的滑動
@Override public boolean isItemViewSwipeEnabled() { return true; }
觸發拖動的百分比
// 針對drag狀態,當滑動超過多少就可以觸發onMove()方法(這里指onMove()方法的調用,並不是隨手指移動的View) @Override public float getMoveThreshold(@NonNull RecyclerView.ViewHolder viewHolder) { return super.getMoveThreshold(viewHolder); }
觸發滑動的百分比
// 側滑事件的滑動距離觸發值 @Override public float getSwipeThreshold(@NonNull RecyclerView.ViewHolder viewHolder) { return super.getSwipeThreshold(viewHolder); }
觸發滑動的滑動速度
// 側滑事件的速度觸發值 @Override public float getSwipeEscapeVelocity(float defaultValue) { return super.getSwipeEscapeVelocity(defaultValue); }
設置支持的拖動或者滑動的方向
這是一個重要的方法,設置此方法可以實現只能上拖還是下拖 或者 都可以。 也可以設置又支持拖動又支持滑動
@Override public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) { int dragFlags = ItemTouchHelper.ACTION_STATE_IDLE;//設置拖動為空閑 int swipeFlags = ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;//設置左右都能滑動 return makeMovementFlags(dragFlags, swipeFlags); }
正在拖動的回調方法
這是重要的方法
/** * onMove */ @Override public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) { return false; }
正在滑動的方法
/** * 在刷動 */ @Override public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) { }
正在ItemView的動畫繪制方法
/** * 針對swipe和drag狀態,整個過程中一直會調用這個函數,隨手指移動的view就是在super里面做到的(和ItemDecoration里面的onDraw()函數對應) */ @Override public void onChildDraw(@NonNull Canvas c, @NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) { super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive); }
動畫的持續時間
/** * 針對swipe和drag狀態,當手指離開之后,view回到指定位置動畫的持續時間(swipe可能是回到原位,也有可能是swipe掉) */ @Override public long getAnimationDuration(@NonNull RecyclerView recyclerView, int animationType, float animateDx, float animateDy) { return super.getAnimationDuration(recyclerView, animationType, animateDx, animateDy); }
執行完拖動或者滑動后,對應清理View的方法
/** * 針對swipe和drag狀態,當一個item view在swipe、drag狀態結束的時候調用 * drag狀態:當手指釋放的時候會調用 * swipe狀態:當item從RecyclerView中刪除的時候調用,一般我們會在onSwiped()函數里面刪除掉指定的item view */ @Override public void clearView(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) { super.clearView(recyclerView, viewHolder); }
一些不常用的方法
/** * 針對drag狀態,當前target對應的item是否允許移動 * 我們一般用drag來做一些換位置的操作,就是當前對應的target對應的Item可以移動 */ @Override public boolean canDropOver(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder current, @NonNull RecyclerView.ViewHolder target) { return super.canDropOver(recyclerView, current, target); } /** * 針對drag狀態,當itemView滑動到RecyclerView邊界的時候(比如下面邊界的時候),RecyclerView會scroll, * 同時會調用該函數去獲取scroller距離(不用我們處理 直接super) */ @Override public int interpolateOutOfBoundsScroll(@NonNull RecyclerView recyclerView, int viewSize, int viewSizeOutOfBounds, int totalSize, long msSinceStartScroll) { return super.interpolateOutOfBoundsScroll(recyclerView, viewSize, viewSizeOutOfBounds, totalSize, msSinceStartScroll); } /** * 針對swipe和drag狀態,當swipe或者drag對應的ViewHolder改變的時候調用 * 我們可以通過重寫這個函數獲取到swipe、drag開始和結束時機,viewHolder 不為空的時候是開始,空的時候是結束 */ @Override public void onSelectedChanged(@Nullable RecyclerView.ViewHolder viewHolder, int actionState) { super.onSelectedChanged(viewHolder, actionState); }
使用ItemTouchHelper實現上下拖動的例子
首先我們需要繼承重寫 ItemTouchHelper.Callback
public class QuickReplyItemTouchCallback extends ItemTouchHelper.Callback { private QuickReplyAdapter mAdapter; private boolean mIsLongPressDragEnabled = true; public QuickReplyItemTouchCallback(QuickReplyAdapter adapter) { //傳入適配器 mAdapter = adapter; } public void setLongPressDragEnabled(boolean isLongPressDragEnabled) { mIsLongPressDragEnabled = isLongPressDragEnabled; } @Override public boolean isItemViewSwipeEnabled() { //是否啟用左右滑動 return false; } @Override public boolean isLongPressDragEnabled() { //返回設置是否長按拖動Item上下移動 return mIsLongPressDragEnabled; } @Override public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) { //在這個回調方法里我們返回我們需要的使用的動作功能 int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN; //拖動 這里設置的UP 與 DOWN 表示允許上下拖動 int swipeFlags = ItemTouchHelper.ACTION_STATE_IDLE; //滑動 這里設置的ACTION_STATE_IDLE 表示我們將滑動動作設置為空閑 return makeMovementFlags(dragFlags, swipeFlags); } @Override public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) { //用於上下移動Item的回調方法,在這個方法里我們要主動將Adapter里的數據互相替換位置 mAdapter.onItemMove(viewHolder.getAdapterPosition(), target.getAdapterPosition()); //返回 true表示我們已經將Adapter里的數據互相替換位置 return true; } @Override public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) { //因為我們不需要處理滑動,所以此處不寫邏輯 } }
Adapter更換位置的實現
/** * 提供給QuickReplyItemTouchCallback類使用的移動Item位置的方法 * @param fromPosition * @param toPosition */ public void onItemMove(int fromPosition, int toPosition) { Collections.swap(mList, fromPosition, toPosition);//更換我們數據List的位置 notifyItemMoved(fromPosition, toPosition); //更換Adapter Item的視圖位置 if (mOnChangeDataPositionListener != null){ mOnChangeDataPositionListener.onChange(mList); } }
在Activity里給RecyclerView添加ItemTouchHelper.Callback
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getContext(), LinearLayoutManager.VERTICAL, false); mQuickReplyRecyclerView.setLayoutManager(linearLayoutManager); mQuickReplyAdapter = new QuickReplyAdapter(); mQuickReplyRecyclerView.setAdapter(mQuickReplyAdapter); mCallback = new QuickReplyItemTouchCallback(mQuickReplyAdapter); ItemTouchHelper itemTouchHelper = new ItemTouchHelper(mCallback); itemTouchHelper.attachToRecyclerView(mQuickReplyRecyclerView);
使用ItemTouchHelper實現左右滑動的例子
下面的這個例子是改變需要出現的View的寬度
滾動整個View來實現,這個效果的關鍵點是那個需要隱藏或者顯示的View 需要在父類布局的外面
布局例子:
關鍵點 app:layout_constraintLeft_toRightOf="parent"
<ImageButton android:id="@+id/delete_btn" android:layout_width="80dp" android:layout_height="0dp" android:background="@color/color_text_yellow" android:src="@drawable/ic_delete_2" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toRightOf="parent" app:layout_constraintHorizontal_bias="0.5" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout>
代碼
@Override public void onChildDraw(@NonNull Canvas c, @NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) { if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) { Context context = recyclerView.getContext(); if (mWidth == 0) { mWidth = UnitConversionUtil.dip2px(context, 80); } if (dX == 0){ //有時候點擊也會被觸發成swipe,這里判斷不發生偏移量就跳過 return; } boolean isLeft = dX < 0; SwitchListAdapter.ViewHolder itemViewHolder = (SwitchListAdapter.ViewHolder) viewHolder; if (isLeft) { itemViewHolder.rootView.setScrollX(Math.min((int) Math.abs(dX), mWidth)); } else { itemViewHolder.rootView.setScrollX(Math.max((int) (mWidth - dX), 0)); } } else { super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive); } }
End
// 側滑事件的滑動距離觸發值
@Override
public float getSwipeThreshold(@NonNull RecyclerView.ViewHolder viewHolder) {
return super.getSwipeThreshold(viewHolder);
}