MultiItem擴充套件 仿任務皮膚 跨多個RecyclerView的Item拖動 支援縮小後拖動

free46000發表於2019-02-28

本文是MultiItem系列的擴充套件文章,跨RecyclerView的Item拖動,並支援縮放的功能,主要防辦公軟體的皮膚,本功能的實現大量參考了ItemTouchHelper的原始碼。
MutliItem主要解決多型別RecyclerView Adapter問題,在正常使用中做到了Adapter零編碼,解放了複雜的Adapter類,提高擴充套件性。

原始碼地址

Github地址:github.com/free46000/M…,請大家多多關注,更多更新會首先在GitHub上體現,也會在第一時間在本平臺釋出。

系列文章

效果截圖

MultiItem擴充套件 仿任務皮膚 跨多個RecyclerView的Item拖動 支援縮小後拖動
跨Recycler拖動

MultiItem擴充套件 仿任務皮膚 跨多個RecyclerView的Item拖動 支援縮小後拖動
縮放後跨Recycler拖動

用法

拖動功能

拖動功能使用比較簡單,拖動功能相關流程和控制都會回撥到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的原始碼
  • 根據使用者觸控位置計算是否需要滾動,和滾動的方向與距離 詳見ItemDragListenercalcXXXScrollDistance() calcScrollXXXDirect()
  • 採用定時Runnable形式,保證持續的滾動
  • 滾動時呼叫Item位置計算方法,使得在滾動過程中也可以更換Item位置

Item位置更換計算

  • 根據觸控位置horizontalRecycler.findChildViewUnder(x, y)找到垂直recyclerView的位置,若找到位置繼續
  • 根據上一次垂直recyclerView所在的位置,判斷是否為第一次選中或者是切換recyclerView的操作,此處可通過itemDragListener回撥攔截此次操作的結果
  • 如果需要切換recyclerView的位置,此時需要對被拖動的Item進行remove,並在新的recyclerViewadd進去
  • 根據觸控位置recyclerView.findChildViewUnder(itemX, itemY)找到itemView的位置
  • 根據上一次itemView所在的位置,判斷是否需要移動itemView位置的操作,此處可通過itemDragListener回撥攔截此次操作的結果
  • 如果需要移動動itemView位置則需要把recyclerView滾動到合適的位置,防recyclerView亂跳

檢視縮放

檢視縮放採用的是把外層的檢視還有橫向列表檢視擴大,並對橫向列表檢視進行setScaleX setScaleY的操作,然後對縮放後的檢視寬度進行賦值,防止一些充滿螢幕的佈局,影響縮放效果,等停止縮放的時候還原即可。

總結

這個功能實現起來比較倉促,並沒有過多的考慮,算是一個beta版本。
以上整理了用法,並對主要流程做了簡單的解析,如果大家有興趣可以結合原始碼理解原理,如有不明白,或者實現不夠優雅的地方,歡迎大家指出。

相關文章