computeTargetIndex static method

int computeTargetIndex({
  1. required double currentRelativeX,
  2. required double velocityX,
  3. required double itemWidth,
  4. required int itemCount,
  5. double velocityThreshold = 0.5,
  6. double projectionTime = 0.3,
})

Computes the target item index based on drag position and velocity.

Uses velocity-based snapping for iOS-style flick gestures:

  • High velocity: Project forward and snap to the projected item
  • Low velocity: Snap to nearest item

This creates the satisfying "flick to jump" behavior seen in iOS.

Parameters:

  • currentRelativeX: Current position in 0-1 range
  • velocityX: Horizontal velocity in relative units
  • itemWidth: Width of each item as a fraction (1 / itemCount)
  • itemCount: Total number of items
  • velocityThreshold: Velocity threshold for flick detection (default: 0.5)
  • projectionTime: Time to project velocity forward in seconds (default: 0.3)

Returns: The target item index (0-based).

Example:

final targetIndex = DraggableIndicatorPhysics.computeTargetIndex(
  currentRelativeX: 0.4,
  velocityX: 2.0, // Fast swipe right
  itemWidth: 1.0 / 3,
  itemCount: 3,
);
// Returns 2 (jumped to last item due to high velocity)

Implementation

static int computeTargetIndex({
  required double currentRelativeX,
  required double velocityX,
  required double itemWidth,
  required int itemCount,
  double velocityThreshold = 0.5,
  double projectionTime = 0.3,
}) {
  // Handle overdrag scenarios
  if (currentRelativeX < 0) return 0;
  if (currentRelativeX > 1) return itemCount - 1;

  if (velocityX.abs() > velocityThreshold) {
    // High velocity - project where we would end up
    final projectedX =
        (currentRelativeX + velocityX * projectionTime).clamp(0.0, 1.0);
    var targetIndex =
        (projectedX / itemWidth).round().clamp(0, itemCount - 1);

    // Ensure we move at least one item with strong velocity
    final currentIndex =
        (currentRelativeX / itemWidth).round().clamp(0, itemCount - 1);

    if (velocityX > velocityThreshold &&
        targetIndex <= currentIndex &&
        currentIndex < itemCount - 1) {
      targetIndex = currentIndex + 1;
    } else if (velocityX < -velocityThreshold &&
        targetIndex >= currentIndex &&
        currentIndex > 0) {
      targetIndex = currentIndex - 1;
    }

    return targetIndex;
  }

  // Low velocity - snap to nearest
  return (currentRelativeX / itemWidth).round().clamp(0, itemCount - 1);
}