Androidでドラッグ・アンド・ドロップ

前置き

Android SDKのDev Guideにドラッグ・アンド・ドロップ(D&D)の情報が追加されたので読んでみました。要点をまとめます。サンプルコードは載せないので、本家のドキュメントをご参照下さい。

読んだのは、SDK Tools revision 10(APIレベルで言えば11、要するにAndroid 3.0/HoneycombのSDK)のDev Guideです。

本文

登場人物・用語

D&D対象
つかまれて移動されるviewオブジェクト。
drag shadow
ドラッグ中のD&D対象。
ドロップ先候補
ドロップ先となりうるview。
D&Dイベント
D&Dの過程で発生し、ドロップ先候補のlistenerへ通知されるイベント。

流れ

  1. D&D開始
  2. ドラッグ
  3. ドロップ
  4. D&D終了

D&Dイベントを受け取る準備

ドロップ先候補はD&Dイベントを受け取りたいはずですが、その方法が2つあります。

  • そのviewにlistenerを登録する → View#setOnDragListener()
  • View#onDragEvent()をオーバーライドする

どちらでも構いませんが、クラスライブラリのView系クラスを使っているときに、わざわざD&Dのためだけに派生クラスを作るのは面倒なので、listenerを使う方が多いでしょう。

listenerが登録してあれば、まずlistenerのメソッドView.OnDragListener#onDrag()が呼ばれます。onDrag()がfalseを返した場合のみ、View#onDragEvent()が呼ばれます。

以下、listenerを使うものとして書きます。

D&Dイベント

D&Dイベントの内容はDragEventオブジェクトにカプセル化されています。DragEvent#getAction()でアクション種別を取得することができます。アクション種別は以下の6種類です。

ACTION_DRAG_STARTED
D&Dが始まった。
ACTION_DRAG_ENTERED
drag shadowが当該viewの外接矩形内に入った。
ACTION_DRAG_LOCATION
drag shadowが当該viewの外接矩形内で動いた。
ACTION_DRAG_EXITED
drag shadowが当該viewの外接矩形から出た。
ACTION_DRAG_DROP
ドロップした。
ACTION_DRAG_ENDED
D&Dが終わった。

D&D開始

何をトリガにしてD&Dを始めるかについてはアプリが決めます。一般的には、アイコンの長タッチがトリガになるでしょう。

D&Dを始めるには、View#startDrag()を呼んでシステムに伝えます。このときのviewオブジェクトは、D&D対象でもドロップ先候補でも、(レイアウト内のviewオブジェクトなら)何でも良いようです。一旦D&Dを始めてしまえば、あとはタッチイベントを気にする必要はありません。D&Dイベントの処理に集中します。

startDrag()の引数には、View.DragShadowBuilderオブジェクトを渡します。これは、システムがD&D中にdrag shadowを描画するために利用されます。デフォルトのタッチポイントはdrag shadowの中心です。つまり、drag shadowの中心をつかんでいるように描画されます。View.DragShadowBuilderクラスを継承してonProvideShadowMetrics()をオーバーライドすればタッチポイントの位置を変えることができます。

startDrag()の引数には、D&D対象に関する情報(メタデータ)も渡すことができます。このメタデータは、D&Dイベントを経由してドロップ先候補のlistenerへ伝えられます。

D&Dが始まると、すぐに、全てのドロップ先候補のlistenerへACTION_DRAG_STARTEDが通知されます。ここでlistenerがfalseを返せば、そのあとのD&Dイベントは通知されません。よって、当該viewにとってD&D対象がドロップ受け入れ可能かどうかに応じてtrueかfalseを返せばよいでしょう。

ドラッグ

ドラッグ中にdrag shadowがドロップ先候補の上空を通過するとき、当該viewへD&Dイベントが通知されます。アクション種別はACTION_DRAG_ENTERED、ACTION_DRAG_LOCATION、ACTION_DRAG_EXITEDのどれかです。ENTEREDが来てからEXITEDが来るまでの間に、少なくとも1回はLOCATIONが来るようです。

ここでは、drag shadowがviewと重なったことがユーザに分かるように、ドロップ先候補の見た目を変化させるなどの処理を行えば良いでしょう。たぶんtrueを返した方が良さそうです(これに関して、Dev Guideの説明には矛盾があります)。

ドロップ

ユーザが指を離すと、その時点でdrag shadow下にあったドロップ先候補へACTION_DRAG_DROPが通知されます。drag shadow下にドロップ先候補が無かった場合は、誰にもACTION_DRAG_DROPは通知されません。

通知されたlistenerは、ドロップ処理を行い、その成否を返します。

D&D終了

最後にシステムは、全てのドロップ先候補のlistenerへACTION_DRAG_ENDEDを通知します。ただし、ACTION_DRAG_STARTEDに対してfalseを返したlistenerには通知しません(たぶん)。

ここでは、DragEvent#getResult()を使って、ドロップ処理の成否を調べることができます。これはすなはち、ACTION_DRAG_DROPを受け取ったlistenerのreturn値です。そもそもACTION_DRAG_DROPイベントが発生しなかった場合はfalseになります。

ACTION_DRAG_ENDEDを受け取ったlistenerは、trueを返すべきです(Dev Guideには、その理由は書いてませんでした)。

補足

DragEventクラスの一部のメソッドは、使えるタイミングが限られています。

  • DragEvent#getX()とgetY()は、アクション種別がENTERED/LOCATION/DROPのときのみ使用可能
  • DragEvent#getClipData()は、DROPのみ
  • DragEvent#getResult()は、ENDEDのみ
Last modified:2011/05/10 20:38:06
Keyword(s):
References:[Androidアプリ開発]
This page is frozen.