本文是MultiItem系列的擴充套件文章,跨RecyclerView的Item拖動,並支援縮放的功能,主要防辦公軟體的皮膚,本功能的實現大量參考了ItemTouchHelper
的原始碼。
MutliItem主要解決多型別RecyclerView Adapter問題,在正常使用中做到了Adapter零編碼,解放了複雜的Adapter類,提高擴充套件性。
原始碼地址
Github地址:github.com/free46000/M…,請大家多多關注,更多更新會首先在GitHub上體現,也會在第一時間在本平臺釋出。
系列文章
效果截圖
用法
拖動功能
拖動功能使用比較簡單,拖動功能相關流程和控制都會回撥到ItemDragListener
監聽中。
例項化拖動輔助類,設定監聽,並在dispatchTouchEvent
中呼叫dragHelper.onTouch(ev)
方法
@Override
protected void onCreate(Bundle savedInstanceState) {
...
//ItemDragHelper,需要傳入外層的橫向滾動的RecyclerView
dragHelper = new ItemDragHelper(horizontalRecycler);
//為dragHelper設定拖動監聽,基本都有預設實現,可根據具體業務繼承重寫方法
dragHelper.setOnItemDragListener(new OnBaseDragListener());
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
//需要保證在Activity或者外層的ViewGroup中重寫此方法
//呼叫dragHelper.onTouch():true消耗掉事件;false則執行super
return dragHelper.onTouch(ev) || super.dispatchTouchEvent(ev);
}複製程式碼
為垂直列表註冊資料來源,必須實現ItemData
介面
//註冊的資料來源(TextDragBean)類,必須實現`ItemData`介面
baseItemAdapter.register(TextDragBean.class, new TextViewDragManager());複製程式碼
開啟拖動功能
dragHelper.startDrag(viewHolder);複製程式碼
拖動監聽,此處只複寫了拖動結束回撥的介面
class OnBaseDragListener extends OnItemDragListener {
@Override
public void onDragFinish(RecyclerView recyclerView, int itemRecyclerPos, int itemPos) {
super.onDragFinish(recyclerView, itemRecyclerPos, itemPos);
String text = String.format("拖動起始第%s個列表的第%s項 結束第%s個列表的第%s項
拖動資料:%s", originalRecyclerPosition,
originalItemPosition, itemRecyclerPos, itemPos, dragItemData);
Toast.makeText(PanelActivity.this, text, Toast.LENGTH_SHORT).show();
}
}複製程式碼
通用Item拖動控制功能
主要包括對Item的拖動、移動、切換的控制功能,資料來源需要實現ItemDrag
介面,這樣在拖動過程中OnItemDragListener
會根據不同的狀態進行對應的控制。
當然你也可以通過OnBaseDragListener
的不同回撥方法,根據業務實現定製化的控制,這裡我們看下ItemDrag
介面:
/**
* 資料來源Item拖動介面,實現一些move change等定製化的通用控制
* Created by free46000 on 2017/4/3.
*/
public interface ItemDrag {
/**
* 是否可以在自己的Recycler中move
*
* @return boolean
*/
boolean isCanMove();
/**
* 是否可以切換到其他Recycler
*
* @return boolean
*/
boolean isCanChangeRecycler();
/**
* 是否可以開啟拖動
*
* @return boolean
*/
boolean isCanDrag();
}複製程式碼
雙擊縮小功能
例項化輔助類併為輔助類設定需要的檢視物件
//例項化縮放功能輔助類
scaleHelper = new ViewScaleHelper();
//設定最外層的Content檢視
scaleHelper.setContentView(contentView);
//設定橫向的Recycler列表檢視
scaleHelper.setHorizontalView(horizontalRecycler);複製程式碼
新增外層的垂直檢視到輔助類,進行縮放統一管理
scaleHelper.addVerticalView(verticalView);複製程式碼
在OnItemDragListener
的回撥中返回當前縮放級別,配合完成縮放後的拖動功能
class OnBaseDragListener extends OnItemDragListener {
@Override
public float getScale() {
return scaleHelper.isInScaleMode() ? scaleHelper.getScale() : super.getScale();
}
}複製程式碼
開啟或關閉縮放功能
//開啟或關閉縮放模式
scaleHelper.toggleScaleModel();
//開啟縮放模式
scaleHelper.startScaleModel();
//關閉縮放模式
scaleHelper.stopScaleModel();複製程式碼
其他定製化用法
定製化業務主要通過複寫ItemDragListener
的相關方法實現,下面簡單列舉一些可定製的功能:
getScale()
縮放比例詳見ViewScaleHelper#getScale()
- Item和列表選中移動等流程控制相關方法(包含移動前的確認回撥),詳見原始碼及註釋
getHorizontalScrollMaxSpeed
getVerticalScrollMaxSpeed
最大水平垂直滾動速度getHorizontalLimit
getVerticalLimit
計算水平垂直滾動距離calcHorizontalScrollDistance
calcVerticalScrollDistance
最大水平垂直滾動速度onDrawFloatView(View floatView)
浮動檢視動畫處理getMoveLimit()
兩個Item是否進行move邊界值
上面所介紹定製化用法並不是全部,如有需要更深層次定製可以提交issues或留言
主要流程解析
備註:以下所說觸控位置都為相對螢幕位置,這樣方便後續計算
生成浮動檢視
被拖動Item檢視設為不可見狀態,浮動檢視採用WindowManager + ImageView
展示被拖動Item檢視的Bitmap
計算橫向和豎向的RecyclerView滾動
- 大量參考了
ItemTouchHelper
的原始碼 - 根據使用者觸控位置計算是否需要滾動,和滾動的方向與距離 詳見
ItemDragListener
的calcXXXScrollDistance()
calcScrollXXXDirect()
- 採用定時Runnable形式,保證持續的滾動
- 滾動時呼叫Item位置計算方法,使得在滾動過程中也可以更換Item位置
Item位置更換計算
- 根據觸控位置
horizontalRecycler.findChildViewUnder(x, y)
找到垂直recyclerView
的位置,若找到位置繼續 - 根據上一次垂直
recyclerView
所在的位置,判斷是否為第一次選中或者是切換recyclerView
的操作,此處可通過itemDragListener
回撥攔截此次操作的結果 - 如果需要切換
recyclerView
的位置,此時需要對被拖動的Item進行remove
,並在新的recyclerView
中add
進去 - 根據觸控位置
recyclerView.findChildViewUnder(itemX, itemY)
找到itemView
的位置 - 根據上一次
itemView
所在的位置,判斷是否需要移動itemView
位置的操作,此處可通過itemDragListener
回撥攔截此次操作的結果 - 如果需要移動動
itemView
位置則需要把recyclerView
滾動到合適的位置,防recyclerView
亂跳
檢視縮放
檢視縮放採用的是把外層的檢視還有橫向列表檢視擴大,並對橫向列表檢視進行setScaleX setScaleY
的操作,然後對縮放後的檢視寬度進行賦值,防止一些充滿螢幕的佈局,影響縮放效果,等停止縮放的時候還原即可。
總結
這個功能實現起來比較倉促,並沒有過多的考慮,算是一個beta版本。
以上整理了用法,並對主要流程做了簡單的解析,如果大家有興趣可以結合原始碼理解原理,如有不明白,或者實現不夠優雅的地方,歡迎大家指出。