Search code examples
androiddrag-and-droponfling

Drag and Drop + Fling Detector not working


I'm trying to implement a draggable button that should also be flingable.

Unfortunately the system stops sending MotionEvents after the drag is started. Therefore the GestureDetector.OnGestureListener.onFling() method is never called.

Is there a way to intercept those events before they are consumed by the drag system?

I also tried to create my own FlingDetector, but it's not reliable across different devices and screen densities:

public class FlingDetector {

private final int MIN_FLING_SPEED = 3;

private OnFlingListener mOnFlingListener;

private float mCurrentX = 0;
private float mCurrentY = 0;
private long mLastMovementTime = 0;
private double  mCurrentVelocity = 0;

private final float mDensity;

public FlingDetector(OnFlingListener onFlingListener, Context context) {
    mOnFlingListener = onFlingListener;
    mDensity = context.getResources().getDisplayMetrics().density;
}


public void onMovementStart(float x, float y) {
    mCurrentX = x;
    mCurrentY = y;
    mLastMovementTime = System.currentTimeMillis();
    mCurrentVelocity = 0;
}

public void onMovementEnd(float x, float y) {

    long currentTime = System.currentTimeMillis();

    float distanceX = Math.abs(mCurrentX - x) / mDensity;
    float distanceY = Math.abs(mCurrentY - y) / mDensity;

    float distance = (float) Math.sqrt(Math.pow(distanceX, 2) +
            Math.pow(distanceY, 2));

    mCurrentVelocity = (distance / (currentTime - mLastMovementTime));

    if(mCurrentVelocity > MIN_FLING_SPEED) {
        mOnFlingListener.onFling((int) (mCurrentVelocity + 0.5));
    } else {
        Log.d("test", "Distance: " + distance);
        Log.d("test", "Time Delta: " + (currentTime - mLastMovementTime));
        Log.d("test", "Speed: " + mCurrentVelocity);
    }
}

public interface OnFlingListener {
    void onFling(int speed);
}

}

Solution

  • You can achieve both fling and drag with a button using GestureDetector. GestureDetector is a little straight forward,its has it's own Default method for handling the following motion event.

    1. LongPress
    2. Fling
    3. onDown
    4. onShowPress
    5. onSingleTapUp
    6. onScroll

    You can Implement Like this.

    public class MainActivity extends AppCompatActivity {
    
        Button button;
        GestureDetector buttonGestureDetector;
        static final int SWIPE_MIN_DISTANCE = 60;
         static final int SWIPE_THRESHOLD_VELOCITY = 100;
    
    
    
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            button=(Button) findViewById(R.id.button);
    
            buttonGestureDetector=new GestureDetector(this,new GestureDetector.OnGestureListener() {
              @Override
              public boolean onDown(MotionEvent e) {
                  return false;
              }
    
              @Override
              public void onShowPress(MotionEvent e) {
    
              }
    
              @Override
              public boolean onSingleTapUp(MotionEvent e) {
                  return false;
              }
    
              @Override
              public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
                  return false;
              }
    
              @Override
              public void onLongPress(MotionEvent e) {
                  Log.i("Drag","DragListening");
                  View.DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(button);
                  button.startDrag(null, shadowBuilder, button, 0);
                  button.setVisibility(View.INVISIBLE);
    
    
    
              }
    
              @Override
              public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
    
                  Log.i("FlingListened","FlingListened");
    
                  if (e1.getX() - e2.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY) {
                      Toast.makeText(MainActivity.this,"OnRightToLeft Fling",Toast.LENGTH_SHORT).show();
                  }
                  else if (e2.getX() - e1.getX() > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > SWIPE_THRESHOLD_VELOCITY)
                  {
                      Toast.makeText(MainActivity.this,"OnLeftToRight Fling",Toast.LENGTH_SHORT).show();
                  }
                  if (e1.getY() - e2.getY() > SWIPE_MIN_DISTANCE && Math.abs(velocityY) > SWIPE_THRESHOLD_VELOCITY) {
    
                      Toast.makeText(MainActivity.this,"onBottomToTop Fling",Toast.LENGTH_SHORT).show();
    
                  }
                  else if (e2.getY() - e1.getY() > SWIPE_MIN_DISTANCE && Math.abs(velocityY) > SWIPE_THRESHOLD_VELOCITY) {
                      Toast.makeText(MainActivity.this,"OnTopToBottom Fling",Toast.LENGTH_SHORT).show();
    
                  }
                  return true;
    
    
                }
          });
    
            button.setOnTouchListener(new View.OnTouchListener() {
                @Override
                public boolean onTouch(View v, MotionEvent event) {
                    buttonGestureDetector.onTouchEvent(event);
                    return false;
    
    
                }
            });
    
              button.setOnDragListener(new View.OnDragListener() {
                @Override
                public boolean onDrag(View dragView, DragEvent event)
                {
    
                    int action = event.getAction();
                    switch (action) {
                        case DragEvent.ACTION_DRAG_STARTED:
                            Log.d("Drag", "Drag event started");
                            break;
                        case DragEvent.ACTION_DRAG_ENTERED:
                            Log.d("Drag", "Drag event entered into "+dragView.toString());
                            break;
                        case DragEvent.ACTION_DRAG_EXITED:
                            Log.d("Drag", "Drag event exited from "+dragView.toString());
                            break;
                        case DragEvent.ACTION_DROP:
                            Log.d("Drag", "Dropped");
                            View view = (View) event.getLocalState();
                            ViewGroup owner = (ViewGroup) view.getParent();
                            owner.removeView(view);
                            LinearLayout container = (LinearLayout) dragView;
                            container.addView(view);
                            view.setVisibility(View.VISIBLE);
                            break;
                        case DragEvent.ACTION_DRAG_ENDED:
                            Log.d("Drag", "Drag ended");
                            break;
                        default:
                            break;
                    }
                    return true;
                }
    
    
            });
    
        }
    }