startDragAndDrop
最近一直在研究拖拽功能, 想要實現(xiàn)分屏狀態(tài)下验毡,左右應(yīng)用的拖拽切換。
android 提供了兩種用于實現(xiàn)view拖拽的API帝嗡。
- ViewDragHelper (需要自定義ViewGroup)
- startDrag / startDragAndDrop (配合 setOnDragListener / onDragEvent)
使用場景:
- ViewDragHelper 適用于 “View本身的拖拽”晶通。
- startDrag / startDragAndDrop 適用于 “View攜帶的數(shù)據(jù)的拖拽”。
不同點:
- startDrag / startDragAndDrop 產(chǎn)生的拖拽效果哟玷,是拖拽一個半透明的view狮辽。該view的繪制內(nèi)容可以自定義繪制。一般默認(rèn)和被拖拽view相同碗降。該view位于最頂層隘竭。
- startDrag / startDragAndDrop 可以在拖拽時攜帶數(shù)據(jù)。該數(shù)據(jù)可以跨進(jìn)程傳輸讼渊。
- 使用startDrag / startDragAndDrop時,要響應(yīng)(監(jiān)聽)該view拖拽事件的view都要設(shè)置setOnDragListener尊剔。
- ViewDragHelper 拖拽的是ViewGroup的內(nèi)容爪幻,針對直接子View。
- ViewDragHelper 拖拽不攜帶數(shù)據(jù)须误。
因為我這里需要實現(xiàn)攜帶數(shù)據(jù)的拖拽挨稿,所以需要使用 View#startDragAndDrop
// 首先看看注釋怎么說:
1. 拖放操作,調(diào)用此方法時京痢,會傳遞一個{@link android.view.View.DragShadowBuilder} 對象給系統(tǒng)奶甘,系統(tǒng)會去調(diào)用 {@link DragShadowBuilder#onProvideShadowMetrics(Point, Point)} 來獲取拖動陰影的度量,然后再調(diào)用 {@link DragShadowBuilder#onDrawShadow(Canvas)}來繪制拖動陰影本身祭椰。
2. 一旦下系統(tǒng)有了拖動陰影臭家,會開始拖動操作通過 發(fā)送 拖拽事件給你的應(yīng)用程序中目前可見的所有 View 對象。
3. 這里通過調(diào)用 View 的 {@link android.view.View.OnDragListener#onDrag(View,DragEvent) onDrag()} 或者調(diào)用 {@link android.view.View#onDragEvent(DragEvent) onDragEvent()} 方法方淤。都是通過 {@link android.view.DragEvent} 對象的{@link android.view.DragEvent#getAction()} {@link android.view.DragEvent#ACTION_DRAG_STARTED}钉赁,可以使用在任何的附加的視圖對象上。
這里看看參數(shù):
startDragAndDrop(ClipData data, DragShadowBuilder shadowBuilder, Object myLocalState, int flags)
// 1\. ClipData 將該數(shù)據(jù)對象轉(zhuǎn)換為拖動操作
// 2\. DragShadowBuilder 構(gòu)造 拖動陰影
// 3\. myLocalState 包含本地的拖動操作數(shù)據(jù)携茂,當(dāng)傳遞拖拽事件給相同 activity 中的 views , 該對象可以通過 @link android.view.DragEvent#getLocalState() 使用你踩。其他activity的 view 不能訪問這個數(shù)據(jù)。是一種輕量型的機(jī)制, 從拖動view 和 target view 之間發(fā)送信息带膜。 通過 flag 來區(qū)分 是拷貝操作還是移動操作吩谦。
// 4\. flags 控制拖拽操作類型的標(biāo)志位 DRAG_FLAG_GLOBAL DRAG_FLAG_GLOBAL_PERSISTABLE_URI_PERMISSION ...
public final boolean startDragAndDrop(ClipData data, DragShadowBuilder shadowBuilder,
27338 Object myLocalState, int flags) {
27339 if (ViewDebug.DEBUG_DRAG) {
27340 Log.d(VIEW_LOG_TAG, "startDragAndDrop: data=" + data + " flags=" + flags);
27341 }
27342 if (mAttachInfo == null) {
27343 Log.w(VIEW_LOG_TAG, "startDragAndDrop called on a detached view.");
27344 return false;
27345 }
27346 if (!mAttachInfo.mViewRootImpl.mSurface.isValid()) {
27347 Log.w(VIEW_LOG_TAG, "startDragAndDrop called with an invalid surface.");
27348 return false;
27349 }
27350
27351 if (data != null) {
// 1\. 離開應(yīng)用程序,準(zhǔn)備工作
27352 data.prepareToLeaveProcess((flags & View.DRAG_FLAG_GLOBAL) != 0);
27353 }
27354
27355 Rect bounds = new Rect();
// 2\. 獲取屏幕上的邊界大小
27356 getBoundsOnScreen(bounds, true);
27357
27358 Point lastTouchPoint = new Point();
27359 mAttachInfo.mViewRootImpl.getLastTouchPoint(lastTouchPoint); // 最后的觸摸點
// 3\. 獲取 ViewRoot
27360 final ViewRootImpl root = mAttachInfo.mViewRootImpl;
27361
27362 // Skip surface logic since shadows and animation are not required during the a11y drag
// 4\. AccessibilityManager 是系統(tǒng)級的事件分派服務(wù)
27363 final boolean a11yEnabled = AccessibilityManager.getInstance(mContext).isEnabled();
27364 if (a11yEnabled && (flags & View.DRAG_FLAG_ACCESSIBILITY_ACTION) != 0) {
27365 try {
27366 IBinder token = mAttachInfo.mSession.performDrag(
27367 mAttachInfo.mWindow, flags, null,
27368 mAttachInfo.mViewRootImpl.getLastTouchSource(),
27369 0f, 0f, 0f, 0f, data);
27370 if (ViewDebug.DEBUG_DRAG) {
27371 Log.d(VIEW_LOG_TAG, "startDragAndDrop via a11y action returned " + token);
27372 }
27373 if (token != null) {
27374 root.setLocalDragState(myLocalState);
27375 mAttachInfo.mDragToken = token;
27376 mAttachInfo.mViewRootImpl.setDragStartedViewForAccessibility(this);
27377 setAccessibilityDragStarted(true); // 開始拖拽
27378 }
27379 return token != null;
27380 } catch (Exception e) {
27381 Log.e(VIEW_LOG_TAG, "Unable to initiate a11y drag", e);
27382 return false;
27383 }
27384 }
27385
27386 Point shadowSize = new Point();
27387 Point shadowTouchPoint = new Point();
27388 shadowBuilder.onProvideShadowMetrics(shadowSize, shadowTouchPoint);
27389 // 5\. 這里處理一些 shadowSize 為負(fù)數(shù)或者0 的情況
27390 if ((shadowSize.x < 0) || (shadowSize.y < 0)
27391 || (shadowTouchPoint.x < 0) || (shadowTouchPoint.y < 0)) {
27392 throw new IllegalStateException("Drag shadow dimensions must not be negative");
27393 }
27394
27395 // Create 1x1 surface when zero surface size is specified because SurfaceControl.Builder
27396 // does not accept zero size surface.
27397 if (shadowSize.x == 0 || shadowSize.y == 0) {
27398 if (!sAcceptZeroSizeDragShadow) {
27399 throw new IllegalStateException("Drag shadow dimensions must be positive");
27400 }
// 為 0 時就置為 1 * 1
27401 shadowSize.x = 1;
27402 shadowSize.y = 1;
27403 }
27404
27405 if (ViewDebug.DEBUG_DRAG) {
27406 Log.d(VIEW_LOG_TAG, "drag shadow: width=" + shadowSize.x + " height=" + shadowSize.y
27407 + " shadowX=" + shadowTouchPoint.x + " shadowY=" + shadowTouchPoint.y);
27408 }
27409 // 6\. 建立 surfaceSession
27410 final SurfaceSession session = new SurfaceSession();
27411 final SurfaceControl surfaceControl = new SurfaceControl.Builder(session)
27412 .setName("drag surface")
27413 .setParent(root.getSurfaceControl())
27414 .setBufferSize(shadowSize.x, shadowSize.y)
27415 .setFormat(PixelFormat.TRANSLUCENT)
27416 .setCallsite("View.startDragAndDrop")
27417 .build();
27418 final Surface surface = new Surface();
27419 surface.copyFrom(surfaceControl);
27420 IBinder token = null;
27421 try {
//7\. 真正的拖拽操作
27422 final Canvas canvas = isHardwareAccelerated() // 是否硬件加速膝藕?
27423 ? surface.lockHardwareCanvas()
27424 : surface.lockCanvas(null);
27425 try {
27426 canvas.drawColor(0, PorterDuff.Mode.CLEAR);
27427 shadowBuilder.onDrawShadow(canvas);
27428 } finally {
27429 surface.unlockCanvasAndPost(canvas);
27430 }
27431
27432 token = mAttachInfo.mSession.performDrag(mAttachInfo.mWindow, flags, surfaceControl,
27433 root.getLastTouchSource(), lastTouchPoint.x, lastTouchPoint.y,
27434 shadowTouchPoint.x, shadowTouchPoint.y, data);
27435 if (ViewDebug.DEBUG_DRAG) {
27436 Log.d(VIEW_LOG_TAG, "performDrag returned " + token);
27437 }
27438 if (token != null) {
27439 if (mAttachInfo.mDragSurface != null) {
27440 mAttachInfo.mDragSurface.release();
27441 }
27442 mAttachInfo.mDragSurface = surface;
27443 mAttachInfo.mDragToken = token;
27444 // Cache the local state object for delivery with DragEvents
27445 root.setLocalDragState(myLocalState);
27446 if (a11yEnabled) {
27447 // Set for AccessibilityEvents
27448 mAttachInfo.mViewRootImpl.setDragStartedViewForAccessibility(this);
27449 }
27450 }
27451 return token != null;
27452 } catch (Exception e) {
27453 Log.e(VIEW_LOG_TAG, "Unable to initiate drag", e);
27454 return false;
27455 } finally {
27456 if (token == null) {
27457 surface.destroy();
27458 }
27459 session.kill();
27460 }
27461 }
2.實現(xiàn)
使用這個方法式廷,最關(guān)鍵的是 ClipData(剪貼板) 的構(gòu)造
2.1 ClipData
Clip Object 的三種形式:
- Text
- URI 解析數(shù)據(jù)資源
- Intent 支持復(fù)制應(yīng)用快捷方式
注意: 剪貼板每次僅僅支持一個 clip 對象。
3. setOnDragListener()
接收拖放事件的View我們暫且稱之為目標(biāo)View束莫,目標(biāo)View調(diào)用setOnDragListener()懒棉,并實現(xiàn)其中的方法onDrag()后可以接收拖放事件的回調(diào)。
這里設(shè)置監(jiān)聽:
4.View.DragShadowBuilder
在拖放操作進(jìn)行的時候览绿,需要顯示正在拖動的圖片策严,View.DragShadowBuilder類提供了可以傳入View的構(gòu)造方法,這個View是被拖放的View饿敲,我們將通過DragShadowBuilder創(chuàng)建的拖動圖片稱為拖動陰影妻导,這個將作為參數(shù)傳入startDragAndDrop()或startDrag()方法中,如若有需要的話怀各,還可以繼承View.DragShadowBuilder類去實現(xiàn)自定義的效果倔韭。
參考文獻(xiàn):
- Android 拖拽理解: www.reibang.com/p/dc90a8543…
- Android為View添加拖放效果:zhuanlan.zhihu.com/p/468692495
- clipdata : blog.csdn.net/jjj11223344…