ターゲット アプリケーションでは、ユーザーがビューをドラッグ アンド ドロップして FrameLayout に移動したり、別の LinearLayout に格納したりできます。
全体的なメカニズムは機能しますが、ビューがドロップされたときに必要な精度が得られず、さらに「ジャンプ」します。「ジャンプ」アーティファクトを回避するために、ドラッグ シャドウの位置を取得して、ドロップされたビューをシャドウがあった場所に正確に配置するにはどうすればよいですか?
例のために、レイアウトは非常に単純です。ここでは、移動可能な 2 つの TextView オブジェクトの概要を示します (詳細はすべて削除しました)。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android">
<LinearLayout android:id="@+id/repository"/>
<FrameLayout android:id="@+id/frame">
<TextView android:id="@+id/view1"/>
<TextView android:id="@+id/view2"/>
</FrameLayout>
</LinearLayout>
まず、ドラッグ シャドウ自体の開始位置を改善するために、オーバーライドしていますonProvideShadowMetrics
。そのおかげで、ユーザーがドラッグを開始してもビューはジャンプしません。
private class MyDragShadowBuilder extends View.DragShadowBuilder {
private float x, y;
public MyDragShadowBuilder(View view, float x, float y) {
super(view);
this.x = x;
this.y = y;
}
@Override
public void onProvideShadowMetrics(Point size, Point touchPoint) {
super.onProvideShadowMetrics(size, touchPoint);
touchPoint.set((int) x, (int) y);
}
}
次に、 の実装をOnTouchListener
使用してドラッグ操作を開始します。
private class LongClickListener implements View.OnTouchListener {
@Override
public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
TextView tv = (TextView) v;
if (tv != null) {
ClipData data = ClipData.newPlainText("", "");
MyDragShadowBuilder shadowBuilder = new MyDragShadowBuilder(v, event.getX(), event.getY());
v.startDrag(data, shadowBuilder, v, 0);
v.setVisibility(View.INVISIBLE);
return true;
}
}
return false;
}
}
ドロップ操作は、ビュー内の「指」の最初の x、y 位置を取得して、ビューを正確に影として配置する場所です。
private class DragListener implements View.OnDragListener {
private final Class<?> FRAMELAYOUT_CLASS = FrameLayout.class;
private final Class<?> LINEARLAYOUT_CLASS = LinearLayout.class;
private Drawable enterShape = getResources().getDrawable(R.drawable.shape_droptarget);
private Drawable normalShape = getResources().getDrawable(R.drawable.shape);
@Override
public boolean onDrag(View v, DragEvent event) {
int action = event.getAction();
switch (action) {
case DragEvent.ACTION_DRAG_STARTED:
break;
case DragEvent.ACTION_DRAG_ENTERED:
v.setBackground(enterShape);
break;
case DragEvent.ACTION_DRAG_EXITED:
v.setBackground(normalShape);
break;
case DragEvent.ACTION_DROP:
View view = (View) event.getLocalState();
ViewGroup owner = (ViewGroup) view.getParent();
ViewGroup container = (ViewGroup) v;
if (container.getClass() == FRAMELAYOUT_CLASS) {
// If I could just get shadowBuilder here, I would be
// able to retrieve its initial (x,y) offset
// or its final (x,y) position, both work...
view.setX(event.getX());
view.setY(event.getY());
} else if (container.getClass() == LINEARLAYOUT_CLASS) {
// In a LinearLayout we remove any offset:
view.setX(0);
view.setY(0);
} else
return false;
owner.removeView(view);
container.addView(view);
view.setVisibility(View.VISIBLE);
break;
case DragEvent.ACTION_DRAG_ENDED:
v.setBackground(normalShape);
default:
break;
}
return true;
}
}
OnCreate
アクティビティのメソッドで設定されるすべて:
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
View v = findViewById(R.id.view1);
v.setOnTouchListener(new LongClickListener());
v = findViewById(R.id.view2);
v.setOnTouchListener(new LongClickListener());
v = findViewById(R.id.frame);
v.setOnDragListener(new DragListener());
v = findViewById(R.id.repository);
v.setOnDragListener(new DragListener());
}
この情報を取得する方法についてのヒントを事前にありがとう!
編集:明らかに、ClipData でデータを渡すことができますが、それは厄介です (特に、将来、より大きなデータセットを渡す必要がある場合、シリアル化はここではやり過ぎです):
ClipData data = ClipData.newPlainText(
"pos",
String.format("%d %d", (int)event.getX(), (int)event.getY()));
v.startDrag(data, shadowBuilder, v, 0);
...
if (container.getClass() == FRAMELAYOUT_CLASS) {
ClipData data = event.getClipData();
String[] pos = ((String)data.getItemAt(0).getText()).split(" ");
int x = Integer.valueOf(pos[0]);
int y = Integer.valueOf(pos[1]);
view.setX(event.getX() - x);
view.setY(event.getY() - y);