Module 4 Task 1: Implementing Flick Scrolling


Launch JavaFX Media Browser Application Download NetBeans Project

Introduction

Module 4 task 1 probes further into the capabilities of mouse events and animation by using a physics engine to add flick scrolling to the scroll control functionality that was introduced in Module 2 Task 2 in the ScrollControl.fx node. Before you complete the procedures in the current task, you should review the scroll control function in Module 2 Task 2.

Flick scrolling works by transforming a mouse drag into a scroll animation when the mouse is released. The animation updates the scroll control elevator's position, thereby moving the wall of thumbnail images. If the wall of images scrolls past the end of the thumbnail array, the bounce animation effect occurs.

Running the Project

  1. Download the Module 4 Task 1, NetBeans project and open the NetBeans IDE.

  2. Run the project.

Wall with search text box Figure 1

Architecture

This task adds flick scrolling functionality to ScrollControl.fx.

Architecture for Module 4 Task 1 Figure 2

Handling the Mouse Events

Flick scrolling is initiated by a drag gesture, that is, click the left mouse button, hold it, drag it and then release it. When the mouse button is released, if the gesture had sufficient velocity, the flick animation is initiated. Otherwise, the drag gesture simply scrolls the thumbnails of the wall.

The local variables lastDragTime, lastDragX and dragSpeed are used to monitor the mouse motion throughout the drag gesture. Three functions are involved: onMousePressed, onMouseDragged and onMouseReleased. These variables are initialized in onMousePressed. They are updated in onMouseDragged as the user drags the mouse. Finally, they are evaluated in onMouseReleased to determine whether to play the animation.

The onMouseDragged function, shown in the following source code sample, is invoked as the user drags the mouse. The dragX value of the MouseEvent function is a delta from the previous value. Small movements of the mouse do not cause the wall to scroll, so the dragX value of the MouseEvent must exceed a certain threshold.

The calculation of dragSpeed is simply the distance the mouse moved, divided by the duration of the movement Note that distance = rate * time.

Source Code
    onMouseDragged: function(evt: MouseEvent): Void {
        var dragTime = Duration.valueOf(java.lang.System.currentTimeMillis());
        if (java.lang.Math.abs(evt.dragX) > Constants.FLICK_DRAG_THRESHOLD) {
                            
            // Set the elevator's dragging flag to prevent its
            // overlay from recentering on every mouse event.
                 
            dragging = true;
            position = posAnchor - evt.dragX / (contentWidth - windowWidth);

            // Monitor the dragging speed.
			
            dragSpeed = (evt.dragX - lastDragX) / 
			dragTime.sub(lastDragTime).toMillis();
            
            if (dragSpeed > Constants.FLICK_MAX_SPEED) {
               dragSpeed = Constants.FLICK_MAX_SPEED;
               } else if (dragSpeed < -Constants.FLICK_MAX_SPEED) {
                 dragSpeed = -Constants.FLICK_MAX_SPEED;
               }
               lastDragTime = dragTime;
               lastDragX = evt.dragX;
            }
      }

The onMouseReleased function is more complex. When the mouse button is released, onMouseReleased determines whether the flick animation should be played based on these conditions:

  • Did the user drag the mouse more than Constants.FLICK_DRAG_THRESHOLD value? The FLICK_DRAG_THRESHOLD is the minimum distance in pixels the mouse must be dragged to trigger scrolling.

  • Did the user drag the wall outside of its normal scrolling limits?

  • Will the mouse drag speed keep the wall in motion longer than the Constants.FLICK_TIME_THRESHOLD value? The FLICK_TIME_THRESHOLD is the minimum time that the animation takes to play.

If these conditions are met, a Timeline is created to animate the wall scrolling to its new position. The Timeline interpolates the value of flickOffset. When the value of flickOffset changes, the position variable is updated. Because position is bound to Wall.scrollPosition with inverse, updating position causes the wall to scroll. See the Mathematical Calculations section of Module 2 Task 2.

Source Code
  flickTimeline = Timeline {
      keyFrames: [
         at (0s) {
           flickOffset => scrollOffset tween flickInterpolator
          },
          KeyFrame {
            time: Duration.valueOf(restTime)
            action: bounce
            values: [
            flickOffset => restOffset tween flickInterpolator
            ]                        
          }
       ];
    };

The interpolator is not a javafx.animation.Interpolator, but a custom FlickInterpolator that provides a constant accleration motion formula for the animation. The FlickInterpolator extends javafx.animation.Interpolator and overrides the interpolate function. The interpolate function is called repeatedly over the duration of a KeyFrame to effectively create a sequence of values between the start value and end value for the given KeyFrame.

The fraction argument represents time as a fraction of the duration of the KeyFrame. For example, if the value of fraction is .5, half of the KeyFrame's duration has elapsed. The startValue is the value at the start of the KeyFrame. The contract of the interpolate function is such that the function returns a value between startValue and endValue.

The interpolate function determines an initial velocity of the wall that causes it to come to rest at the scroll limit using the constant acceleration formula:

x = x0 + v0 * t + a * t^2 / 2.

So, v0 = (x0 - x + a * t^2 / 2) / t, where x0 is the initial position past the end, x is the position of the scroll limit, a is the constant acceleration, and t is the constant amount of time to bounce back.

Source Code
class FlickInterpolator extends Interpolator {

    // Initial position
    var x0: Number;
			
    // Initial velocity
    var v0: Number;
			
    // The time at which the object comes to rest.
    var t1: Number;
			
    override function interpolate(startValue: java.lang.Object,
             endValue: java.lang.Object,fraction: Number) :   
             java.lang.Object {
			
             // The sign of the acceleration parameter is based on the
			 // direction of motion.
   
             var a : Number = bind if (v0 > 0) {
			 Constants.FLICK_ACCELERATION;
             } else {
             -Constants.FLICK_ACCELERATION;
             }
			   
             // The interpolation always begins at t = 0 so
             // t = t0 + (t1 - t0) * fraction becomes
             // t = t1 * fraction
			        
             var t = fraction * t1;
			
             // Apply the motion formula for constant acceleration.
						   
             return x0 + v0 * t + a * t * t / 2;
    }
}                

Bounce Animation

The bounce animation is played if either the flick animation moves or the user drags the wall past the end of the thumbnail array. The bounce animation is a simplification that uses the FlickInterpolator to move the wall back to the scrolling limit in a constant amount of time.

Source Code
    var restOffset = 0.0;
    var a = -Constants.FLICK_ACCELERATION;
    if (scrollOffset <= 0) {
       restOffset = -(contentWidth - windowWidth);
       a *= -1;
    }

    // Compute the initial rebound velocity based on the position and
    // the defined bounce duration.
       
       var v0 = (restOffset - scrollOffset -
       a * Constants.FLICK_BOUNCE_TIME * Constants.FLICK_BOUNCE_TIME / 2) /
       Constants.FLICK_BOUNCE_TIME;

     // Create a motion formula interpolator for the bounce animation.
	 
     var flickInterpolator = FlickInterpolator {
         x0: scrollOffset
         v0: v0
         t1: Constants.FLICK_BOUNCE_TIME;
     }

Tuning the Flick Animation

The variables listed in the following table have been added to Constants.fx and can be used to tune the flick animation behavior.

Variables Used to Tune Flick Animation
Constant Meaning Value
FLICK_DRAG_THRESHOLD The minimum distance in pixels that the mouse has to be dragged to trigger scrolling. 5 pixels
FLICK_TIME_THRESHOLD The minimum duration of the flick animation. Less than this and the animation is too brief to have a visible impact. 500 milliseconds
FLICK_ACCELERATION The scrolling acceleration after a flick in pixels / ms / ms. In the physics of flick, this value (being negative) represents friction. - 0.00035 pixels / ms / ms
FLICK_MAX_SPEED This maximum limits the initial velocity so that the wall stops scrolling within (3500) ms. - FLICK_ACCELERATION * 3500
FLICK_BOUNCE_LIMIT The distance in pixels that a flick animation can overshoot. 100 pixels
FLICK_BOUNCE_TIME The time in milliseconds that it takes to bounce (scroll back) the wall on an overshoot. 750 milliseconds