Drag and Drop and OnClick TextView

Viewed 7899

I'm creating drag and drop text view and I need this text view also can be clicked. My code as per below :

     <?xml version="1.0" encoding="utf-8"?>
    <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/root"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/bg_grey"
        android:orientation="vertical" >

        <GridView
            android:id="@+id/gridview"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="10dp"
            android:background="@color/bg_grey"
            android:clickable="true"
            android:columnWidth="100dp"
            android:gravity="center"
            android:horizontalSpacing="10dp"
            android:numColumns="2"
            android:stretchMode="columnWidth"
            android:verticalSpacing="10dp" />

        <TextView
            android:id="@+id/drag_drop_button"
            android:layout_width="80dp"
            android:layout_height="80dp"
            android:layout_marginLeft="255dp"
            android:layout_marginTop="155dp"
            android:background="@drawable/circle_button"
            android:clickable="true"
            android:focusable="true"
            android:focusableInTouchMode="true"
            android:gravity="center"
            android:text="@string/brand"
            android:textColor="@color/sign_blue"
            android:textSize="15sp"
            android:textStyle="bold" />

    </FrameLayout>


class ButtonDragListener implements OnDragListener {
        Drawable normalShape = getResources().getDrawable(
                R.drawable.circle_button);

        @Override
        public boolean onDrag(View v, DragEvent event) {
            int action = event.getAction();
            switch (event.getAction()) {
            case DragEvent.ACTION_DRAG_STARTED:
                // do nothing
                break;
            case DragEvent.ACTION_DRAG_ENTERED:
                // do nothing
                break;
            case DragEvent.ACTION_DRAG_EXITED:
                // do nothing
                break;
            case DragEvent.ACTION_DROP:
                // Dropped, reassign View to ViewGroup
                View view = (View) event.getLocalState();
                view.setX(event.getX());
                view.setY(event.getY());
                ViewGroup owner = (ViewGroup) view.getParent();
                owner.removeView(view);
                FrameLayout container = (FrameLayout) v;
                container.addView(view);
                view.setVisibility(View.VISIBLE);
                break;
            case DragEvent.ACTION_DRAG_ENDED:
                // do nothing
                break;
            default:
                break;
            }
            return true;
        }
    }

        private final class ButtonTouchListener implements OnTouchListener {

            private static final int MAX_CLICK_DURATION = 200;
            private long startClickTime;

            public boolean onTouch(View view, MotionEvent motionEvent) {
                if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
                    startClickTime = Calendar.getInstance().getTimeInMillis();
                    ClipData data = ClipData.newPlainText("", "");
                    DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(
                            view);
                    view.startDrag(data, shadowBuilder, view, 0);
                    view.setVisibility(View.VISIBLE);

                } else if (motionEvent.getAction() == MotionEvent.ACTION_UP) {
                    long clickDuration = Calendar.getInstance().getTimeInMillis()
                            - startClickTime;

                    if (clickDuration < MAX_CLICK_DURATION){ 
                        view.performClick();
                    }
                }
                return true;
            }
        }

        private final class ButtonOnClickListener implements OnClickListener {

            @Override
            public void onClick(View v) {
                Log.e(TAG, "ALOHAAAAA");

            }

        }

        TextView dragDrop = (TextView) view.findViewById(R.id.drag_drop_button);
            dragDrop.setAlpha(0.7f);
            dragDrop.setOnClickListener(new ButtonOnClickListener());
            dragDrop.setOnTouchListener(new ButtonTouchListener());
        frameLayout = (FrameLayout) view.findViewById(R.id.root);
        frameLayout.setOnDragListener(new ButtonDragListener());

But MotionEvent.ACTION_UP never be called, I'm also trying to move view.performClick() inside MotionEvent_ACTION_DOWN, the click listener calling fine but whenever I'm dragging the text view the click listener also run. I also have setOnTouchListener and setOnClickListener directly from textView but the result is same.

What I need here is smooth drag and drop and also click function. Kindly advise what I'm doing wrong here. Thank you and really appreciate for any kind help.

9 Answers

This is a kind of a problem where although there is provided solutions, but also you have to think of how these solutions will fit your needs. For my case I used the ideas above and came up with my own solution that fits my needs.

  /**
   * Max value of X to move before we declare that we are going to do
   * drag. 
   */
   private static final float MAX_X_MOVE = 120;

  /**
   * Max value of Y to move before we declare that we are going to do 
   * drag.
   */
   private static final float MAX_Y_MOVE = 120;

   private final static int NONE = 0;

   private final static int DRAG = 1;

   private int m_mode = NONE;

   icon.setOnTouchListener(new View.OnTouchListener() {
     @Override
     public boolean onTouch(View v, MotionEvent event) {

        final int action = event.getAction();

         switch (action){
           case MotionEvent.ACTION_DOWN:
            m_mode = NONE;
           break;

           case MotionEvent.ACTION_MOVE:
             if (Math.abs(event.getX()) > MAX_X_MOVE || 
                 Math.abs(event.getY()) > MAX_Y_MOVE) {
                 m_mode = DRAG;
                 _startDragOnFling();
             }
           break;

           case MotionEvent.ACTION_UP:
              if(m_mode == DRAG){
               break;
              }
              v.performClick();
           break;
         }

        return true;
      }
   });

In my application, a series of buttons can be clicked or dragged. To achieve this I have

  • defined the buttons in XML with the "onClick" attribute referencing a method in my main activity (in your application, attach a "View.OnClickListener" to your TextView instead)
  • defined an "OnDragListener" (it just returns true because all drag events are handled in a separate "OnDragListener" attached to the target View of the drop)
  • defined an "OnLongClickListener"
  • overridden the "onClick()" in my main activity (I can not remember where this is documented)
  • assigned the "OnLongClickListener" to the buttons
  • assigned the "OnDragListener" to the buttons

This is my implementation:

@Override
public void onClick(View v) {  }

class ScDragListenerForButton implements OnDragListener {
    @Override
    public boolean onDrag(View v, DragEvent event) {
        return true;
    }
}

ScDragListenerForButton mDragListenerForButton = new ScDragListenerForButton();

View.OnLongClickListener onLongClickListener = new View.OnLongClickListener() {
    @Override
    public boolean onLongClick(View v) {
        Button b = (Button) v;
        ClipData.Item item = new ClipData.Item(b.getText());
        String[] mimeTypes = {ClipDescription.MIMETYPE_TEXT_PLAIN};

        ClipData dragData = new ClipData(b.getText(), mimeTypes, item);
        View.DragShadowBuilder myShadow = new View.DragShadowBuilder(v);

        if (Build.VERSION.SDK_INT < VERSION_CODES.N) {
            v.startDrag(dragData, myShadow, null, 0);
        } else {
            v.startDragAndDrop(dragData, myShadow, null, 0);
        }
        return true;
    }
};

button1.setOnLongClickListener(onLongClickListener);
button1.setOnDragListener(mDragListenerForButton);

Try this:

public class MainActivity extends AppCompatActivity implements View.OnTouchListener {
  float dX;
  float dY;
  int lastAction;

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

    final View dragView = findViewById(R.id.draggable_view);
    dragView.setOnTouchListener(this);
  }

  @Override
  public boolean onTouch(View view, MotionEvent event) {
    switch (event.getActionMasked()) {
      case MotionEvent.ACTION_DOWN:
        dX = view.getX() - event.getRawX();
        dY = view.getY() - event.getRawY();
        lastAction = MotionEvent.ACTION_DOWN;
        break;

      case MotionEvent.ACTION_MOVE:
        view.setY(event.getRawY() + dY);
        view.setX(event.getRawX() + dX);
        lastAction = MotionEvent.ACTION_MOVE;
        break;

      case MotionEvent.ACTION_UP:
        if (lastAction == MotionEvent.ACTION_DOWN)
          Toast.makeText(DraggableView.this, "Clicked!", Toast.LENGTH_SHORT).show();
        break;

      default:
        return false;
    }
    return true;
  }
}

And the XML:

<ImageButton
        android:id="@+id/draggable_view"
        android:background="@mipmap/ic_launcher"
        android:layout_gravity="bottom|right"
        android:layout_marginBottom="20dp"
        android:layout_marginEnd="20dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

You can make any View Draggable and Clickable.

Try this:

        public class MainActivity extends AppCompatActivity implements View.OnTouchListener, View.OnDragListener , View.OnClickListener {

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

            final View dragView = findViewById(R.id.draggable_view);
            dragView.setOnTouchListener(this);
            dragView.setOnclickListener(this);

          }

     @Override
        public boolean onTouch(View v, MotionEvent event) {

            //if move detected it start Drag event and it's handler or click wil be handled by click listener
            if(event.getAction()  == MotionEvent.ACTION_MOVE){
                View.DragShadowBuilder mShadow = new View.DragShadowBuilder(v);

                    dragView = v;
                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                        v.startDragAndDrop(null, mShadow, null, 0);
                    } else {
                        v.startDrag(null, mShadow, null, 0);
                    }
                 }
       return false;
    }

 @Override
    public void onClick(View view) {
        switch (view.getId()){
            case R.id.draggable_view:{
                showToast("click detected");
            }
            break;
           }
       }
}
Related