測試demo代碼
RecyclerView recyclerView = new RecyclerView(context);
recyclerView.setLayoutManager(new LinearLayoutManager(context));
final TouchAdapter adapter = new TouchAdapter();
recyclerView.setAdapter(adapter);
ItemTouchHelper helper = new ItemTouchHelper(new ItemTouchHelper.Callback() {
@Override
public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
return makeMovementFlags(ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT | ItemTouchHelper.UP | ItemTouchHelper.DOWN, 0);
}
@Override
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder viewHolder1) {
// adapter.swap(viewHolder,viewHolder1);
return true;
}
@Override
public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int i) {
}
});
helper.attachToRecyclerView(recyclerView);
這樣當recyclerview長按就可以拖動子item上下左右擺動了任岸,但是并不會交換順序,所以默認ItemTouchHelper實現(xiàn)的就是上下左右拖拽item但是不會自動交換,只是會回調(diào)交換的函數(shù)颓帝。下面一點點分析函數(shù)調(diào)用流程
注冊監(jiān)聽器
helper.attachToRecyclerView(recyclerView);
內(nèi)部調(diào)用設(shè)置很多監(jiān)聽器和初始化
private void setupCallbacks() {
ViewConfiguration vc = ViewConfiguration.get(this.mRecyclerView.getContext());
this.mSlop = vc.getScaledTouchSlop();
this.mRecyclerView.addItemDecoration(this);
//添加這個裝飾器购城,下文介紹,其實就是用來繪制的
this.mRecyclerView.addOnItemTouchListener(this.mOnItemTouchListener);
//監(jiān)聽Item觸摸事件吴趴,由RecyclerView事件分發(fā)而來
this.mRecyclerView.addOnChildAttachStateChangeListener(this);
this.startGestureDetection();
//手勢監(jiān)聽侮攀,比如長按判斷
}
事件傳遞流程
Down:
- RecyclerView.onInterceptTouchEvent
- RecyclerView.listener.onInterceptTouchEvent
- RecyclerView.OnItemTouchListener.onInterceptTouchEvent( 見demo:this.mRecyclerView.addOnItemTouchListener(this.mOnItemTouchListener);
) - ItemTouchHelper.this.mGestureDetector.onTouchEvent(event);
其實就是ItemTouchHelper注冊了RecyclerView事件監(jiān)聽兰英,然后就傳遞給mGestureDetector處理,mGestureDetector檢測到長按事件通知出來陨闹,
這時候ItemTouchHelper也就知道長按了趋厉,然后
調(diào)用
View child = ItemTouchHelper.this.findChildView(e);
if (child != null) {
ViewHolder vh = ItemTouchHelper.this.mRecyclerView.getChildViewHolder(child);
if (vh != null) {
if (!ItemTouchHelper.this.mCallback.hasDragFlag(ItemTouchHelper.this.mRecyclerView, vh)) {
return;
}
根據(jù)手勢找到屬于哪一個View和ViewHolder胶坠,如果設(shè)置flag拖拽支持沈善,則繼續(xù)帖蔓;
ItemTouchHelper.this.select(vh, 2);
主要是初始化this.mSelected = selected;
并且設(shè)置回味動畫對象ItemTouchHelper.RecoverAnimation
這樣ItemTouchHelper就知道當前有一個選中的ViewHolder
Move:
- RecyclerView.onTouchEvent(MotionEvent e)
- RecyclerView.dispatchOnItemTouch(e)
- RecyclerView.OnItemTouchListener.onTouchEvent()
- ViewHolder viewHolder = ItemTouchHelper.this.mSelected;
- switch(action) {
case 2:recyclerview.invalidate();
這幾步驟分析Move的時候,其實也是從RecyclerView傳遞出來事件然后最終導(dǎo)致RecyclerView繪制
RecyclerView繪制看的會有點暈瞳脓,其實再看
public void onDraw(Canvas c) {
super.onDraw(c);
int count = this.mItemDecorations.size();
for(int i = 0; i < count; ++i) {
((RecyclerView.ItemDecoration)this.mItemDecorations.get(i)).onDraw(c, this, this.mState);
}
}
RecyclerView繪制會觸發(fā)ItemDecoration重新繪制的
Up:
case MotionEvent.ACTION_UP:
select(null, ACTION_STATE_IDLE);
標記當前選中為null
同時也會觸發(fā)this.mRecyclerView.invalidate();
分析ItemDecoration
public class ItemTouchHelper extends ItemDecoration
看onDraw方法會觸發(fā)ItemTouchUIUtilImpl.java的onChildDraw和onChildDrawOver
onChildDrawOver是空方法
onChildDraw是核心是view.setTranslationX(dX);
view.setTranslationY(dY);
其實就是x,y偏移塑娇,說來說去就是按照手移動設(shè)置x,y的偏移
偏移量計算規(guī)則
private void getSelectedDxDy(float[] outPosition) {
if ((mSelectedFlags & (LEFT | RIGHT)) != 0) {
outPosition[0] = mSelectedStartX + mDx - mSelected.itemView.getLeft();
} else {
outPosition[0] = mSelected.itemView.getTranslationX();
}
if ((mSelectedFlags & (UP | DOWN)) != 0) {
outPosition[1] = mSelectedStartY + mDy - mSelected.itemView.getTop();
} else {
outPosition[1] = mSelected.itemView.getTranslationY();
}
}
參數(shù)mDx是當前手和之前落下的距離差,見
void updateDxDy(MotionEvent ev, int directionFlags, int pointerIndex) {
float x = ev.getX(pointerIndex);
float y = ev.getY(pointerIndex);
this.mDx = x - this.mInitialTouchX;
this.mDy = y - this.mInitialTouchY;
參數(shù)mSelectedStartX 是選中初始位置劫侧,見
this.mSelectedStartX = (float)selected.itemView.getLeft();
select方法里面設(shè)置的埋酬,注意mInitialTouchY是onLongPress設(shè)置手勢落下點
,倆個參數(shù)不一樣
通知交換ViewHolder回調(diào)
在ItemTouchHelper監(jiān)聽滑動時候發(fā)現(xiàn)當前選中不為空切拖拽模式
ItemTouchHelper.this.updateDxDy(event, ItemTouchHelper.this.mSelectedFlags, activePointerIndex);
ItemTouchHelper.this.moveIfNecessary(viewHolder);
ItemTouchHelper.this.mRecyclerView.removeCallbacks(ItemTouchHelper.this.mScrollRunnable);
ItemTouchHelper.this.mScrollRunnable.run();
ItemTouchHelper.this.mRecyclerView.invalidate();
其中ItemTouchHelper.this.moveIfNecessary(viewHolder);就是觸發(fā)回調(diào)交換順序
分析浮在RecyclerView最上面實現(xiàn)原理
Build.VERSION.SDK_INT < 21
改寫拖拽的view位置getChildDrawingOrder
mChildDrawingOrderCallback = new RecyclerView.ChildDrawingOrderCallback() {
@Override
public int onGetChildDrawingOrder(int childCount, int i) {
if (mOverdrawChild == null) {
return i;
}
int childPosition = mOverdrawChildPosition;
if (childPosition == -1) {
childPosition = mRecyclerView.indexOfChild(mOverdrawChild);
mOverdrawChildPosition = childPosition;
}
if (i == childCount - 1) {
return childPosition;
}
return i < childPosition ? i : i + 1;
}
Build.VERSION.SDK_INT >= 21見ItemTouchUIUtilImpl
if (Build.VERSION.SDK_INT >= 21) {
if (isCurrentlyActive) {
Object originalElevation = view.getTag(R.id.item_touch_helper_previous_elevation);
if (originalElevation == null) {
originalElevation = ViewCompat.getElevation(view);
float newElevation = 1f + findMaxElevation(recyclerView, view);
ViewCompat.setElevation(view, newElevation);
view.setTag(R.id.item_touch_helper_previous_elevation, originalElevation);
}
}
}
分析總結(jié)
默認長按的情況烧栋,進入拖拽模式写妥,隨著手移動,計算x,y偏移量审姓,然后
手勢觸發(fā)繪制不是直接讓view調(diào)用setTranlateX方法,而是recyclerview.invalidate,然后觸發(fā)ItemDecoration繪制調(diào)用onDraw方法魔吐,
然后設(shè)置拖拽的view設(shè)置setTranlateX以及setTranlateY