Making Android Views Draggable: A Practical Guide
Tags: android, draggable, java, ui
Published: 2023-06-02 20:25:14

In Android, views are fundamental building blocks of UI (User Interface). They define components like buttons, labels, text fields, image views, etc. Typically, these views have fixed positions as defined in layout files or programmatically. **But what if we want to make these views movable or draggable by the user?**

This post focuses on how to make any view in Android draggable. We will be working with Floating Action Button, ImageView, and LottieAnimation View, converting these into custom views using a reference code snippet. This feature can significantly enhance user interactivity, offering a more dynamic UI experience.

We will leverage **Android's onTouch listener** to detect **touch events**, calculate the new position, and then **reposition the view based on the user's drag**. The reference code we're using here illustrates the logic using a Floating Action Button (FAB), a common element that could benefit from this feature.

## The Reference Code
Here's the original Java code that we will modify for our purpose. This code shows how to make a FloatingActionButton draggable:

```java
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;

import com.google.android.material.floatingactionbutton.FloatingActionButton;

public class MovableFloatingActionButton extends FloatingActionButton implements View.OnTouchListener {

    private final static float CLICK_DRAG_TOLERANCE = 10; // Often, there will be a slight, unintentional, drag when the user taps the FAB, so we need to account for this.

    private float downRawX, downRawY;
    private float dX, dY;

    public MovableFloatingActionButton(Context context) {
        super(context);
        init();
    }

    public MovableFloatingActionButton(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public MovableFloatingActionButton(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        setOnTouchListener(this);
    }

    @Override
    public boolean onTouch(View view, MotionEvent motionEvent){

        ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams)view.getLayoutParams();

        int action = motionEvent.getAction();
        if (action == MotionEvent.ACTION_DOWN) {

            downRawX = motionEvent.getRawX();
            downRawY = motionEvent.getRawY();
            dX = view.getX() - downRawX;
            dY = view.getY() - downRawY;

            return true; // Consumed

        }
        else if (action == MotionEvent.ACTION_MOVE) {

            int viewWidth = view.getWidth();
            int viewHeight = view.getHeight();

            View viewParent = (View)view.getParent();
            int parentWidth = viewParent.getWidth();
            int parentHeight = viewParent.getHeight();

            float newX = motionEvent.getRawX() + dX;
            newX = Math.max(layoutParams.leftMargin, newX); // Don't allow the FAB past the left hand side of the parent
            newX = Math.min(parentWidth - viewWidth - layoutParams.rightMargin, newX); // Don't allow the FAB past the right hand side of the parent

            float newY = motionEvent.getRawY() + dY;
            newY = Math.max(layoutParams.topMargin, newY); // Don't allow the FAB past the top of the parent
            newY = Math.min(parentHeight - viewHeight - layoutParams.bottomMargin, newY); // Don't allow the FAB past the bottom of the parent

            view.animate()
                    .x(newX)
                    .y(newY)
                    .setDuration(0)
                    .start();

            return true; // Consumed

        }
        else if (action == MotionEvent.ACTION_UP) {

            float upRawX = motionEvent.getRawX();
            float upRawY = motionEvent.getRawY();

            float upDX = upRawX - downRawX;
            float upDY = upRawY - downRawY;

            if (Math.abs(upDX) < CLICK_DRAG_TOLERANCE && Math.abs(upDY) < CLICK_DRAG_TOLERANCE) { // A click
                return performClick();
            }
            else { // A drag
                return true; // Consumed
            }

        }
        else {
            return super.onTouchEvent(motionEvent);
        }

    }

}
```

## Breaking Down the Code

The above class extends the **FloatingActionButton** and implements the View.OnTouchListener interface. The OnTouchListener interface is crucial here, as it allows us to handle touch events.

In the onTouch method, we handle different touch events:

**ACTION_DOWN:** This is when the user first touches the view. We record the position where the user touched down.

**ACTION_MOVE:** This is when the user moves their finger while still touching the screen. We calculate the new X and Y coordinates for the view, ensuring it doesn't move past the parent's edges.

**ACTION_UP:** This is when the user lifts their finger from the screen. Here, we handle both click and drag actions. If the movement was small enough, we consider it a click and call performClick(). Otherwise, it's a drag event, and we simply consume the event.

## Making Other Views Draggable

We can apply the same concept to make other views draggable. The key change is to extend the view you want to make draggable. For example, to make an **ImageView** draggable, you'd use `public class DraggableImageView extends ImageView implements View.OnTouchListener`.

Similarly, for a **LottieAnimationView**, `public class DraggableLottieAnimationView extends LottieAnimationView implements View.OnTouchListener`.

Remember to replace any `FloatingActionButton` specific code with code applicable to your view. The main concept remains the same - implement the OnTouchListener, handle the different touch events, and update the view's position during a move event.

## Conclusion
By implementing the View.OnTouchListener and handling different touch events, you can make any Android view draggable, significantly improving user interactivity. Whether it's a FloatingActionButton, ImageView, BottomNavigationView, or a LottieAnimationView, the logic remains consistent. The sky is the limit!

## References

1. [Android Developers Documentation](https://developer.android.com/reference/android/view/View.OnTouchListener)
2. [Handling Touches](https://developer.android.com/guide/topics/ui/ui-events)
3. [Floating Action Button](https://material.io/components/buttons-floating-action-button)
4. [Lottie Animation View](http://airbnb.io/lottie/#/android)
5. [Bottom Navigation View](https://developer.android.com/reference/com/google/android/material/bottomnavigation/BottomNavigationView)