Animation by Example: Dragging Images
by David Matuszek

 

In this applet you can grab either the skull or the skeleton and move it around. The skull can be moved anywhere within the applet; the skeleton can be moved horizontally, but not vertically.

To do this, we need to make use of two separate kinds of listeners, MouseListener and MouseMotionListener. The first of these, MouseListener, is used to tell us when the mouse button is pressed and when it is released. The second, MouseMotionListener, is used to tell us when the mouse is moved with the mouse button held down.

To begin, we need two variables to help us keep track of what is going on:

  boolean draggingSkull = false;
  boolean draggingSkeleton = false;

Next we need to attach a MouseListener. Listeners can only be attached to Components, and our images are not Components. In our examples we have a class View that extends Canvas, and Canvas is a Component, so we attach our listeners to the object view of class View.

When the mouse button is pressed inside the View, we check first whether the mouse is over the skull. This is a pretty sloppy check, because the skull is within a 100x100 pixel square, most of which is transparent; the actual skull is really only about 50x50. However, since the skull is moving rapidly, we don't want to make it too hard to catch. If the mouse is not over the skull, then we check whether it is over the skeleton; we don't want to move both images at once (although we could, if that was what we wanted to do).

When the mouse button is released, regardless of where the mouse is, we turn off dragging for both the skull and the skeleton. If they weren't being dragged, no harm is done.

    // We need a MouseListener in order to start and stop dragging
    view.addMouseListener(new MouseAdapter() {
      public void mousePressed(MouseEvent event) {
        // Get mouse position
        int mouseX = event.getX();
        int mouseY = event.getY();
        // Check whether the mouse is inside the skull
        if (mouseX >= model.xPosition &&
            mouseX <= model.xPosition + model.skullWidth &&
            mouseY >= model.yPosition &&
            mouseY <= model.yPosition + model.skullHeight) {
              draggingSkull = true;
            }
        // Check whether the mouse is inside the skeleton
        // (but NOT inside the skull, since we only want to
        // drag one thing at a time)
        else if (mouseX >= model.xSkeleton &&
            mouseX <= model.xSkeleton + model.skeletonWidth &&
            mouseY >= model.ySkeleton &&
            mouseY <= model.ySkeleton + model.skeletonHeight) {
              draggingSkeleton = true;
            }
      }
      
      public void mouseReleased(MouseEvent event) {
        draggingSkull = false;
        draggingSkeleton = false;
      }
    });

All we have done so far is control the setting of two variables, draggingSkull and draggingSkeleton. However, that's the hard part. When the mouse is being moved with a mouse button down, all we have to do is reposition the skull or the skeleton to be under the mouse.

    // Drag an image with the mouse
    view.addMouseMotionListener(new MouseMotionAdapter() {
      public void mouseDragged(MouseEvent event) {
        // Get mouse position
        int mouseX = event.getX();
        int mouseY = event.getY();
        if (draggingSkull) {
          // Center skull over mouse position
          model.xPosition = mouseX - (model.skullWidth / 2);
          model.yPosition = mouseY - (model.skullHeight / 2);
        }
        if (draggingSkeleton) {
          // Center skeleton over horizontal mouse position
          model.xSkeleton = mouseX - (model.skeletonWidth / 2);
        }
      }
    });

There are two minor problems with this code, neither of which I'm going to bother to correct.

First, when the mouse is clicked over any part of an image, the image "jumps" to center itself over the mouse pointer. With a little arithmetic, we could note exactly where in the image the mouse was clicked, and keep that part of the image under the mouse pointer. However, it is arguable whether this is "better." Besides, in the present example, things are moving around so much that you can hardly notice the small jump.

Second, the skull and skeleton are moving. We only get mouseDragged events when the mouse is moving (with the button down), so if we stop moving the mouse, the image we are dragging will move away, only to snap back the next time we move the mouse. This could easily be fixed by adding a couple of lines to the Model:

    if(!Controller.draggingSkull) { ...move skull... }
    if (!Controller.draggingSkeleton) { ...move skeleton... }
Previous