startDragAndDrop

最近一直在研讨拖拽功用, 想要完成分屏状态下,左右使用的拖拽切换。

android 供给了两种用于完成view拖拽的API。
  1. ViewDragHelper (需求自定义ViewGroup)
  2. startDrag / startDragAndDrop (合作 setOnDragListener / onDragEvent)
运用场景:
  1. ViewDragHelper 适用于 “View自身的拖拽”。
  2. startDrag / startDragAndDrop 适用于 “View带着的数据的拖拽”。
不同点:
  1. startDrag / startDragAndDrop 产生的拖拽作用,是拖拽一个半透明的view。该view的绘制内容能够自定义绘制。一般默许和被拖拽view相同。该view位于最顶层。
  2. startDrag / startDragAndDrop 能够在拖拽时带着数据。该数据能够跨进程传输。
  3. 运用startDrag / startDragAndDrop时,要响应(监听)该view拖拽事情的view都要设置setOnDragListener。
  4. ViewDragHelper 拖拽的是ViewGroup的内容,针对直接子View。
  5. ViewDragHelper 拖拽不带着数据。

由于我这儿需求完成带着数据的拖拽,所以需求运用 View#startDragAndDrop

// 首要看看注释怎么说:
1. 拖放操作,调用此办法时,会传递一个{@link android.view.View.DragShadowBuilder} 方针给体系,体系会去调用 {@link DragShadowBuilder#onProvideShadowMetrics(Point, Point)} 来获取拖动阴影的度量,然后再调用 {@link DragShadowBuilder#onDrawShadow(Canvas)}来绘制拖动阴影自身。
​
2. 一旦下体系有了拖动阴影,会开端拖动操作经过 发送 拖拽事情给你的使用程序中目前可见的所有 View 方针。
​
3. 这儿经过调用 View 的 {@link android.view.View.OnDragListener#onDrag(View,DragEvent) onDrag()} 或许调用 {@link android.view.View#onDragEvent(DragEvent) onDragEvent()} 办法。都是经过 {@link android.view.DragEvent} 方针的{@link android.view.DragEvent#getAction()} {@link android.view.DragEvent#ACTION_DRAG_STARTED},能够运用在任何的附加的视图方针上。

这儿看看参数:

startDragAndDrop(ClipData data, DragShadowBuilder shadowBuilder, Object myLocalState, int flags)
// 1. ClipData  将该数据方针转换为拖动操作
// 2. DragShadowBuilder 结构 拖动阴影
// 3. myLocalState  包括本地的拖动操作数据,当传递拖拽事情给相同 activity 中的 views , 该方针能够经过 @link android.view.DragEvent#getLocalState() 运用。其他activity的 view 不能拜访这个数据。是一种轻量型的机制, 从拖动view 和 target view 之间发送信息。 经过 flag 来区分 是仿制操作还是移动操作。
// 4. flags  控制拖拽操作类型的标志位 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. 脱离使用程序,准备工作
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 是体系级的事情分配服务
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 为负数或许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.完成

Android 拖拽功能研究 —— startDragAndDrop

运用这个办法,最关键的是 ClipData(剪贴板) 的结构

2.1 ClipData

Clip Object 的三种方式:

  1. Text
  2. URI 解析数据资源
  3. Intent 支撑仿制使用快捷方式

注意: 剪贴板每次只是支撑一个 clip 方针。

3. setOnDragListener()

接收拖放事情的View咱们暂且称之为方针View,方针View调用setOnDragListener(),并完成其中的办法onDrag()后能够接收拖放事情的回调。

这儿设置监听:

Android 拖拽功能研究 —— startDragAndDrop

4.View.DragShadowBuilder

在拖放操作进行的时分,需求显示正在拖动的图片,View.DragShadowBuilder类供给了能够传入View的结构办法,这个View是被拖放的View,咱们将经过DragShadowBuilder创立的拖动图片称为拖动阴影,这个将作为参数传入startDragAndDrop()或startDrag()办法中,如若有需求的话,还能够承继View.DragShadowBuilder类去完成自定义的作用。

参考文献:

  1. Android 拖拽理解: www.jianshu.com/p/dc90a8543…
  2. Android为View添加拖放作用:zhuanlan.zhihu.com/p/468692495
  3. clipdata : blog.csdn.net/jjj11223344…