Note that there are some explanatory texts on larger screens.

plurals
  1. PORetrieving precise drop position of Android View
    primarykey
    data
    text
    <p>The target application allows the user to drag/drop views to move them in a FrameLayout or store them in another LinearLayout.</p> <p>The overall mechanism works but I can't get the precision I need on the position when the view is dropped, it "jumps" a little further. How could I retrieve the position of the drag shadow to put the dropped view exactly where the shadow was, in order to avoid any "jump" artefact?</p> <p>For the sake of the example, the layout is pretty simple, here's an overview with two TextView objects that can be moved around (I removed all the details):</p> <pre class="lang-xml prettyprint-override"><code>&lt;LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"&gt; &lt;LinearLayout android:id="@+id/repository"/&gt; &lt;FrameLayout android:id="@+id/frame"&gt; &lt;TextView android:id="@+id/view1"/&gt; &lt;TextView android:id="@+id/view2"/&gt; &lt;/FrameLayout&gt; &lt;/LinearLayout&gt; </code></pre> <p>First, to improve the start position of the drag shadow itself, I'm overriding <code>onProvideShadowMetrics</code>. Thanks to that, the view doesn't jump when the user starts dragging it:</p> <pre class="lang-java prettyprint-override"><code>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); } } </code></pre> <p>Then an implementation of the <code>OnTouchListener</code> is used to start the drag operation:</p> <pre class="lang-java prettyprint-override"><code>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; } } </code></pre> <p>The drop operation is where I would like to retrieve the initial x, y position of the "finger" in the view, to position the view exactly as the shadow.</p> <pre class="lang-java prettyprint-override"><code>private class DragListener implements View.OnDragListener { private final Class&lt;?&gt; FRAMELAYOUT_CLASS = FrameLayout.class; private final Class&lt;?&gt; 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; } } </code></pre> <p>All that is set in the <code>OnCreate</code> method of the activity:</p> <pre class="lang-java prettyprint-override"><code>@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()); } </code></pre> <p>Thanks in advance for any tip on how I should get this information!</p> <hr> <p><strong>EDIT</strong>: Obviously I could pass the data in the ClipData but that would be awkward (especially if I need to pass larger data sets in the future, serialization is an overkill here):</p> <pre><code>ClipData data = ClipData.newPlainText( "pos", String.format("%d %d", (int)event.getX(), (int)event.getY())); v.startDrag(data, shadowBuilder, v, 0); </code></pre> <p>...</p> <pre><code>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); </code></pre>
    singulars
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload