Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I restrict drag and drop along the y axis only in android?

I'm trying to restrict drag and drop motion to only the Y-axis so that a user can only take a view and drag it up or down--not left or right.

I've got two views (ids of textView and dropZone) right now. One (textView) has a touch listener set to it and another (dropZone) has a drag listener set on it.

Here's the layout xml (activity_main.xml):

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context=".MainActivity" >

<TextView
    android:id="@+id/textView"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:layout_centerHorizontal="true"
    android:layout_marginTop="50dp"
    android:background="#FFFFFF00"
    android:text="Text Drag" />

<TextView
    android:id="@+id/dropZone"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:layout_centerHorizontal="true"
    android:layout_marginTop="167dp"
    android:background="#FFFF0000"
    android:text="Drop Zone" />

</RelativeLayout>

The following is the activity code:

package com.example.dragexperiment;

import android.app.Activity;
import android.content.ClipData;
import android.graphics.Color;
import android.os.Bundle;
import android.util.Log;
import android.view.DragEvent;
import android.view.Menu;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.DragShadowBuilder;
import android.view.View.OnDragListener;
import android.view.View.OnGenericMotionListener;
import android.view.View.OnTouchListener;
import android.widget.RelativeLayout;
import android.widget.TextView;

public class MainActivity extends Activity {
TextView tv, dz, sv;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        tv = (TextView) findViewById(R.id.textView);
        dz = (TextView) findViewById(R.id.dropZone);
        tv.setOnTouchListener(new MyTouchListener());
        dz.setOnDragListener(new MyDragListener());
    }

    private final class MyTouchListener implements OnTouchListener {
        int viewX0, viewY0,
            cY0, cY1,
            deltaCursorY;

        @Override
        public boolean onTouch(final View v,final MotionEvent event) {
              switch(event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    viewX0 = (int) v.getX();
                    viewY0 = (int) v.getY();
                    cY0 = (int) event.getRawY();
                    ClipData data = ClipData.newPlainText("", "");
                    DragShadowBuilder shadow = new View.DragShadowBuilder(v);
                    v.startDrag(data, shadow, v, 0);
                    return true;
                case MotionEvent.ACTION_MOVE:
                //  cY1 = (int) event.getRawY();
                //  deltaCursorY = cY1 - cY0;
                //  v.setX(viewX0);
                //  v.setY(viewY0 + deltaCursorY);
                    return true;
              }
              return false;
        }
    }

    class MyDragListener implements OnDragListener {

        public boolean onDrag(View v, DragEvent event) {
            switch (event.getAction()) {
            case DragEvent.ACTION_DRAG_STARTED:
                break;
            case DragEvent.ACTION_DRAG_ENTERED:
                v.setBackgroundColor(Color.BLUE);
                break;
            case DragEvent.ACTION_DRAG_EXITED:
                break;
            case DragEvent.ACTION_DROP:
                v.setBackgroundColor(Color.BLUE);
                break;
            case DragEvent.ACTION_DRAG_ENDED:
                break;
            default:
                break;
            }
            return true;
        }
    }


    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }

}

As you can see from the code above, when a user drags textView over dropZone, I try to turn the dropZone view background color to blue. This works fine if I use create a DragShadowBuilder in the ACTION_DOWN motion event:

    ClipData data = ClipData.newPlainText("", "");
    DragShadowBuilder shadow = new View.DragShadowBuilder(v);
    v.startDrag(data, shadow, v, 0);

The problem is that I can't control the shadow so that it only moves along the Y-axis (vertical) axis.

If I take out the DragShadowBuilder code (the three lines above), and add in code to move the dragged view in the ACTION_MOVE motion event (it's commented out above):

    cY1 = (int) event.getRawY();
    deltaCursorY = cY1 - cY0;
    v.setX(viewX0);
    v.setY(viewY0 + deltaCursorY);

Then I can control the textView to move only along the Y-axis. Unfortunately, without a DragShadowBuilder, I can't get the ACTION_DRAG_ENTERED DragEvent to trigger to turn the dropZone view blue.

If I leave both the DragShadowBuilder code and the four lines of code above, the ACTION_MOVE motion event is only triggered once for a drag; it doesn't continue to follow the drag around.

Anybody have any ideas as to what I can do? I've been tooling around with this for a while, but with no luck. I even tried at one point to create a custom view that extends the Android View class, but I can't override the startDrag method because it's declared as final. I wish I were better at this. :(

like image 205
Dog Tamer Avatar asked Dec 01 '13 00:12

Dog Tamer


1 Answers

Ah ha! I wasn't able to find this answer anywhere (and a lot of people were asking), so here's what I did.

The basic idea was to make the DragShadow invisible and pass the Y axis movement to the view you want to "drag". So in reality, you're dragging the DragShadow, but it will appear to the user that the draggable view is moving.

draggableItem.setOnTouchListener(new View.OnTouchListener() {
        @Override
        public boolean onTouch(final View view, MotionEvent motionEvent) {
            if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
                ClipData data = ClipData.newPlainText("", "");
                View.DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(new View(getApplicationContext()));
                view.startDrag(data, shadowBuilder, view, 0);
                return true;
            } else {
                return false;
            }
        }
    });

mDropZone.setOnDragListener(new View.OnDragListener() {
        @Override
        public boolean onDrag(View v, DragEvent event) {
                switch (event.getAction()) {
                case DragEvent.ACTION_DRAG_STARTED:
                    ViewGroup.MarginLayoutParams lParams = (ViewGroup.MarginLayoutParams) arcMenu.getLayoutParams();
                    _yDelta = (int) event.getY() - lParams.topMargin;
                    break;
                case DragEvent.ACTION_DRAG_LOCATION:
                    ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) arcMenu.getLayoutParams();
                    layoutParams.topMargin = (int) event.getY() - _yDelta;
                    arcMenu.setLayoutParams(layoutParams);
                    break;
                case DragEvent.ACTION_DROP:
                    // Dropped, reassign View to ViewGroup
                    View view = (View) event.getLocalState();

                    // Do your drop actions here

                    view.setVisibility(View.VISIBLE);
                    break;
                default:
                    break;
            }
            return true;
        }
    });

You'll also need to declare the _yDelta variable in your class.

Hope this works for all you cool cats out there!

like image 148
Ben Chen Avatar answered Sep 20 '22 03:09

Ben Chen