Android 拖拽功能研究 —— startDragAndDrop

startDragAndDrop

最近一直在研究拖拽功能, 想要實現(xiàn)分屏狀態(tài)下验毡,左右應(yīng)用的拖拽切換。

android 提供了兩種用于實現(xiàn)view拖拽的API帝嗡。
  1. ViewDragHelper (需要自定義ViewGroup)
  2. startDrag / startDragAndDrop (配合 setOnDragListener / onDragEvent)
使用場景:
  1. ViewDragHelper 適用于 “View本身的拖拽”晶通。
  2. startDrag / startDragAndDrop 適用于 “View攜帶的數(shù)據(jù)的拖拽”。
不同點:
  1. startDrag / startDragAndDrop 產(chǎn)生的拖拽效果哟玷,是拖拽一個半透明的view狮辽。該view的繪制內(nèi)容可以自定義繪制。一般默認(rèn)和被拖拽view相同碗降。該view位于最頂層隘竭。
  2. startDrag / startDragAndDrop 可以在拖拽時攜帶數(shù)據(jù)。該數(shù)據(jù)可以跨進(jìn)程傳輸讼渊。
  3. 使用startDrag / startDragAndDrop時,要響應(yīng)(監(jiān)聽)該view拖拽事件的view都要設(shè)置setOnDragListener尊剔。
  4. ViewDragHelper 拖拽的是ViewGroup的內(nèi)容爪幻,針對直接子View。
  5. 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 的三種形式:

  1. Text
  2. URI 解析數(shù)據(jù)資源
  3. 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):

  1. Android 拖拽理解: www.reibang.com/p/dc90a8543…
  2. Android為View添加拖放效果:zhuanlan.zhihu.com/p/468692495
  3. clipdata : blog.csdn.net/jjj11223344…

作者:飯盒君
鏈接:https://juejin.cn/post/7176131749020188731

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市瓢对,隨后出現(xiàn)的幾起案子寿酌,更是在濱河造成了極大的恐慌,老刑警劉巖硕蛹,帶你破解...
    沈念sama閱讀 217,734評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件醇疼,死亡現(xiàn)場離奇詭異,居然都是意外死亡法焰,警方通過查閱死者的電腦和手機(jī)秧荆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來埃仪,“玉大人乙濒,你說我怎么就攤上這事÷羊龋” “怎么了颁股?”我有些...
    開封第一講書人閱讀 164,133評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長毙玻。 經(jīng)常有香客問我豌蟋,道長,這世上最難降的妖魔是什么桑滩? 我笑而不...
    開封第一講書人閱讀 58,532評論 1 293
  • 正文 為了忘掉前任梧疲,我火速辦了婚禮允睹,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘幌氮。我一直安慰自己缭受,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,585評論 6 392
  • 文/花漫 我一把揭開白布该互。 她就那樣靜靜地躺著米者,像睡著了一般。 火紅的嫁衣襯著肌膚如雪宇智。 梳的紋絲不亂的頭發(fā)上蔓搞,一...
    開封第一講書人閱讀 51,462評論 1 302
  • 那天,我揣著相機(jī)與錄音随橘,去河邊找鬼喂分。 笑死,一個胖子當(dāng)著我的面吹牛机蔗,可吹牛的內(nèi)容都是我干的蒲祈。 我是一名探鬼主播,決...
    沈念sama閱讀 40,262評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼萝嘁,長吁一口氣:“原來是場噩夢啊……” “哼梆掸!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起牙言,我...
    開封第一講書人閱讀 39,153評論 0 276
  • 序言:老撾萬榮一對情侶失蹤酸钦,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后咱枉,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體钝鸽,經(jīng)...
    沈念sama閱讀 45,587評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,792評論 3 336
  • 正文 我和宋清朗相戀三年庞钢,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片因谎。...
    茶點故事閱讀 39,919評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡基括,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出财岔,到底是詐尸還是另有隱情风皿,我是刑警寧澤,帶...
    沈念sama閱讀 35,635評論 5 345
  • 正文 年R本政府宣布匠璧,位于F島的核電站桐款,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏夷恍。R本人自食惡果不足惜魔眨,卻給世界環(huán)境...
    茶點故事閱讀 41,237評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧遏暴,春花似錦侄刽、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至杂彭,卻和暖如春墓毒,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背亲怠。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評論 1 269
  • 我被黑心中介騙來泰國打工所计, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人赁炎。 一個月前我還...
    沈念sama閱讀 48,048評論 3 370
  • 正文 我出身青樓醉箕,卻偏偏與公主長得像,于是被迫代替她去往敵國和親徙垫。 傳聞我的和親對象是個殘疾皇子讥裤,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,864評論 2 354

推薦閱讀更多精彩內(nèi)容